Skip to content
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ let package = Package(

.target(
name: "SolanaRPC",
dependencies: ["SwiftBorsh", "SolanaTransactions"]),
dependencies: ["SwiftBorsh", "SolanaTransactions", "SolanaWalletAdapterKit"]),
.testTarget(
name: "SolanaRPCTests",
dependencies: ["SolanaRPC"]),
Expand All @@ -72,5 +72,8 @@ let package = Package(
"Salt",
"SolanaTransactions",
]),
.testTarget(
name: "SolanaWalletAdapterKitTests",
dependencies: ["SolanaWalletAdapterKit"]),
]
)
2 changes: 1 addition & 1 deletion Sources/Salt/Salt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import TweetNacl
#else
extension SaltUtil {
public static func isOnCurve(publicKey: Data) throws(NaclUtilError) -> Bool {
fatalError()
fatalError("Cannot find salkt module")
}
}
#endif
Expand Down
4 changes: 2 additions & 2 deletions Sources/SolanaRPC/methods/getLatestBlockHash.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ extension SolanaRPCClient {
}

public struct GetLatestBlockhashResponse: Decodable {
let blockhash: Blockhash
let lastValidBlockHeight: UInt64
public let blockhash: Blockhash
public let lastValidBlockHeight: UInt64
}

/// https://solana.com/docs/rpc/http/getlatestblockhash
Expand Down
4 changes: 2 additions & 2 deletions Sources/SolanaRPC/methods/getVersion.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
extension SolanaRPCClient {
public struct GetVersionResponse: Decodable {
let solanaCore: String
let featureSet: UInt32
public let solanaCore: String
public let featureSet: UInt32

enum CodingKeys: String, CodingKey {
case solanaCore = "solana-core"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,13 @@ extension _CryptographicIdentifier {
}

public init(arrayLiteral elements: UInt8...) {
precondition(elements.count == Self.byteLength)
precondition(elements.count == Self.byteLength, "Invalid CryptographicIdentifier array literal: \(elements)")
self.init(bytes: Data(elements))
}

public init(stringLiteral value: StaticString) {
let bytes = Data(base58Encoded: "\(value)")
precondition(bytes != nil)
precondition(bytes!.count == Self.byteLength)
precondition(bytes != nil && bytes!.count == Self.byteLength, "Invalid CryptographicIdentifier string literal: \(value)")
self.init(bytes: bytes!)
}

Expand Down
42 changes: 29 additions & 13 deletions Sources/SolanaTransactions/Coding/Message.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import Foundation

public enum VersionedMessage: Equatable, Sendable {
case legacyMessage(LegacyMessage)
case v0(V0Message)
}

public struct LegacyMessage: Equatable, Sendable {
let signatureCount: UInt8
let readOnlyAccounts: UInt8
let readOnlyNonSigners: UInt8
let accounts: [PublicKey]
let blockhash: Blockhash
let instructions: [CompiledInstruction]
public let signatureCount: UInt8
public let readOnlyAccounts: UInt8
public let readOnlyNonSigners: UInt8
public let accounts: [PublicKey]
public let blockhash: Blockhash
public let instructions: [CompiledInstruction]

public init(signatureCount: UInt8, readOnlyAccounts: UInt8, readOnlyNonSigners: UInt8, accounts: [PublicKey], blockhash: Blockhash, instructions: [CompiledInstruction]) {
self.signatureCount = signatureCount
Expand All @@ -22,13 +24,13 @@ public struct LegacyMessage: Equatable, Sendable {
}

public struct V0Message: Equatable, Sendable {
let signatureCount: UInt8
let readOnlyAccounts: UInt8
let readOnlyNonSigners: UInt8
let accounts: [PublicKey]
let blockhash: Blockhash
let instructions: [CompiledInstruction]
let addressTableLookups: [AddressTableLookup]
public let signatureCount: UInt8
public let readOnlyAccounts: UInt8
public let readOnlyNonSigners: UInt8
public let accounts: [PublicKey]
public let blockhash: Blockhash
public let instructions: [CompiledInstruction]
public let addressTableLookups: [AddressTableLookup]

public init(
signatureCount: UInt8, readOnlyAccounts: UInt8, readOnlyNonSigners: UInt8,
Expand Down Expand Up @@ -79,6 +81,20 @@ extension VersionedMessage: SolanaTransactionCodable {
}
}

extension VersionedMessage {
public func encode() throws(SolanaTransactionCodingError) -> Data {
var buffer = SolanaTransactionBuffer()
try self.solanaTransactionEncode(to: &buffer)
return Data(buffer.readBytes(length: buffer.readableBytes) ?? [])
}

public init<Bytes: Sequence>(bytes: Bytes) throws(SolanaTransactionCodingError)
where Bytes.Element == UInt8 {
var buffer = SolanaTransactionBuffer(bytes: bytes)
try self.init(fromSolanaTransaction: &buffer)
}
}

extension LegacyMessage: SolanaTransactionCodable {
func solanaTransactionEncode(to buffer: inout SolanaTransactionBuffer)
throws(SolanaTransactionCodingError)
Expand Down
19 changes: 9 additions & 10 deletions Sources/SolanaTransactions/InstructionsBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,26 @@ extension Transaction {
// Fee payer is always a writable signer, and must be the first account
var writableSigners: OrderedSet<PublicKey> = [feePayer]
var readOnlySigners: OrderedSet<PublicKey> = []
var writableNonSigners: OrderedSet<PublicKey> = []
var readOnlyNonSigners: OrderedSet<PublicKey> = []
var accounts: OrderedSet<PublicKey> = [feePayer]
var programIds: OrderedSet<PublicKey> = []

for instruction in instructions {
for account in instruction.accounts {
switch (account.isSigner, account.isWritable) {
case (true, true): writableSigners.append(account.publicKey)
case (true, false): readOnlySigners.append(account.publicKey)
case (false, true): break
case (false, true): writableNonSigners.append(account.publicKey)
case (false, false): readOnlyNonSigners.append(account.publicKey)
}
accounts.append(account.publicKey)
}
// ProgramID needs to be at the end of the accounts array (otherwise, the transaction is invalid)
readOnlyNonSigners.append(instruction.programId)
accounts.append(instruction.programId)
programIds.append(instruction.programId)
}

readOnlyNonSigners.formUnion(programIds)

let signers = writableSigners.union(readOnlySigners)
let accounts = signers.union(writableNonSigners).union(readOnlyNonSigners).union(programIds)

let compiledInstructions = try instructions.map {
CompiledInstruction(
Expand All @@ -83,10 +84,8 @@ extension Transaction {
data: try BorshEncoder.encode($0.data))
}

// 64-byte placeholder array for signatures (otherwise, the transaction is invalid)
signatures = signers.map { _ in
"1111111111111111111111111111111111111111111111111111111111111111"
}
signatures = Array(repeating: Signature.placeholder, count: signers.count)

message = .legacyMessage(
LegacyMessage(
signatureCount: UInt8(signers.count),
Expand Down
10 changes: 5 additions & 5 deletions Sources/SolanaTransactions/Programs/TokenProgram.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,33 +103,33 @@ public enum TokenProgram: Program, Instruction {
[
AccountMeta(publicKey: account, isSigner: false, isWritable: true),
AccountMeta(publicKey: mintAccount, isSigner: false, isWritable: false),
AccountMeta(publicKey: owner, isSigner: false, isWritable: true),
AccountMeta(publicKey: owner, isSigner: false, isWritable: false),
AccountMeta(publicKey: Self.sysvarRentPubkey, isSigner: false, isWritable: false),
]
case .transfer(let from, let to, _, let owner):
[
AccountMeta(publicKey: from, isSigner: false, isWritable: true),
AccountMeta(publicKey: to, isSigner: false, isWritable: true),
AccountMeta(publicKey: owner, isSigner: true, isWritable: true),
AccountMeta(publicKey: owner, isSigner: true, isWritable: false),
]
case .mintTo(let mint, let destination, let mintAuthority, _):
[
AccountMeta(publicKey: mint, isSigner: false, isWritable: true),
AccountMeta(publicKey: destination, isSigner: false, isWritable: true),
AccountMeta(publicKey: mintAuthority, isSigner: true, isWritable: true),
AccountMeta(publicKey: mintAuthority, isSigner: true, isWritable: false),
]
case .closeAccount(let account, let destination, let owner):
[
AccountMeta(publicKey: account, isSigner: false, isWritable: true),
AccountMeta(publicKey: destination, isSigner: false, isWritable: true),
AccountMeta(publicKey: owner, isSigner: true, isWritable: true),
AccountMeta(publicKey: owner, isSigner: true, isWritable: false),
]
case .transferChecked(let from, let to, _, _, let owner, let mint):
[
AccountMeta(publicKey: from, isSigner: false, isWritable: true),
AccountMeta(publicKey: mint, isSigner: false, isWritable: false),
AccountMeta(publicKey: to, isSigner: false, isWritable: true),
AccountMeta(publicKey: owner, isSigner: true, isWritable: true),
AccountMeta(publicKey: owner, isSigner: true, isWritable: false),
]
}
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/SolanaTransactions/Signature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ import Foundation
public struct Signature: CryptographicIdentifier, _CryptographicIdentifier {
public static let byteLength = 64
public let bytes: Data

public static let placeholder = Signature(bytes: Data(repeating: 0, count: Signature.byteLength))
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Foundation
import Salt

extension DeeplinkWallet {
@discardableResult
public mutating func connect() async throws -> DeeplinkWalletConnection? {
guard connection == nil else { throw SolanaWalletAdapterError.alreadyConnected }

Expand Down
12 changes: 12 additions & 0 deletions Sources/SolanaWalletAdapterKit/Deeplink/ResponseTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@ public struct ConnectResponseData: Decodable {

public struct SignAndSendTransactionResponseData: Decodable, Sendable {
public let signature: Signature

public init(signature: Signature) {
self.signature = signature
}
}

public struct SignAllTransactionsResponseData: Decodable, Sendable {
public let transactions: [Transaction]

public init(transactions: [Transaction]) {
self.transactions = transactions
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let strings: [String] = try container.decode([String].self, forKey: .transactions)
Expand All @@ -43,6 +51,10 @@ public struct SignAllTransactionsResponseData: Decodable, Sendable {
public struct SignTransactionResponseData: Decodable, Sendable {
public let transaction: Transaction

public init(transaction: Transaction) {
self.transaction = transaction
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let string: String = try container.decode(String.self, forKey: .transaction)
Expand Down
Loading