SPB Git

spb/os-vault Public

Self-custody, multi-chain crypto wallet for macOS. One recovery phrase, six chain families, zero API keys — nothing leaves your Mac.

Swift 96% Shell 3.4% Makefile 0.6%
5.2 KB · 156 lines swift
Raw Blame History
1import Foundation23/// Blockhash as Base58 string.4public typealias BlockHash = String56/// Lecagy message7public struct Message: IMessage, Equatable {8    public var version: TransactionVersion { .legacy }910    // MARK: - Constants1112    private static let RECENT_BLOCK_HASH_LENGTH = 321314    // MARK: - Properties1516    public internal(set) var header: MessageHeader17    public internal(set) var accountKeys: [PublicKey]18    public internal(set) var recentBlockhash: BlockHash19    public internal(set) var instructions: [CompiledInstruction]20    public var staticAccountKeys: [PublicKey] { accountKeys }2122    init(header: MessageHeader, accountKeys: [PublicKey], recentBlockhash: BlockHash,23         instructions: [CompiledInstruction])24    {25        self.header = header26        self.accountKeys = accountKeys27        self.recentBlockhash = recentBlockhash28        self.instructions = instructions29    }3031    public func serialize() throws -> Data {32        // Header33        let header = encodeHeader()3435        // Account keys36        let accountKeys = encodeAccountKeys()3738        // RecentBlockHash39        let recentBlockhash = encodeRecentBlockhash()4041        // Compiled instruction42        let compiledInstruction = try encodeInstructions()4344        // Construct data45        //            let bufferSize: Int =46        //                Header.LENGTH // header47        //                + keyCount.count // number of account keys48        //                + Int(accountKeys.count) * PublicKey.LENGTH // account keys49        //                + RECENT_BLOCK_HASH_LENGTH // recent block hash50        //                + instructionsLength.count51        //                + compiledInstructionsLength5253        var data = Data( /* capacity: bufferSize */ )5455        // Append data56        data.append(header)57        data.append(accountKeys)58        data.append(recentBlockhash)59        data.append(compiledInstruction)6061        return data62    }6364    static func from(data: Data) throws -> Message {65        var data = data6667        let numRequiredSignatures = data.removeFirst()68        let numReadonlySignedAccounts = data.removeFirst()69        let numReadonlyUnsignedAccounts = data.removeFirst()7071        let accountCount = data.decodeLength()72        var accountKeys: [PublicKey] = []73        for _ in 0 ..< accountCount {74            let account = data.prefix(PublicKey.numberOfBytes)75            data = data.dropFirst(PublicKey.numberOfBytes)76            try accountKeys.append(PublicKey(string: Base58.encode(account.bytes)))77        }7879        let recentBlockhash = data.prefix(PublicKey.numberOfBytes)80        data = data.dropFirst(PublicKey.numberOfBytes)8182        let instructionCount = data.decodeLength()83        var instructions: [CompiledInstruction] = []84        for _ in 0 ..< instructionCount {85            let programIdIndex = data.removeFirst()86            let accountCount = data.decodeLength()87            let accounts = data.prefix(accountCount)88            data = data.dropFirst(accountCount)89            let dataLength = data.decodeLength()90            let dataSlice = data.prefix(dataLength)91            data = data.dropFirst(dataLength)92            instructions.append(93                CompiledInstruction(94                    programIdIndex: programIdIndex,95                    keyIndicesCount: Data.encodeLength(accountCount).bytes,96                    keyIndices: accounts.bytes,97                    dataLength: Data.encodeLength(dataLength).bytes,98                    data: dataSlice.bytes99                )100            )101        }102103        return Message(104            header: MessageHeader(numRequiredSignatures: Int(numRequiredSignatures),105                                  numReadonlySignedAccounts: Int(numReadonlySignedAccounts),106                                  numReadonlyUnsignedAccounts: Int(numReadonlyUnsignedAccounts)),107            accountKeys: accountKeys,108            recentBlockhash: Base58.encode(recentBlockhash.bytes),109            instructions: instructions110        )111    }112113    public func isAccountWritable(index: Int) -> Bool {114        (index < header.numRequiredSignatures - header.numReadonlySignedAccounts)115            ||116            (index >= header.numRequiredSignatures && index < accountKeys.count - header117                .numReadonlyUnsignedAccounts)118    }119120    public func isAccountSigner(index: Int) -> Bool {121        index < header.numRequiredSignatures122    }123124    private func encodeHeader() -> Data {125        Data(header.bytes)126    }127128    private func encodeAccountKeys() -> Data {129        // length130        let keyCount = encodeLength(accountKeys.count)131132        // construct data133        var data = Data(capacity: keyCount.count + accountKeys.count * PublicKey.numberOfBytes)134135        // append data136        data.append(keyCount)137        for key in accountKeys {138            data.append(key.data)139        }140        return data141    }142143    private func encodeRecentBlockhash() -> Data {144        Data(Base58.decode(recentBlockhash))145    }146147    private func encodeInstructions() throws -> Data {148        let instructionsLength = Data.encodeLength(instructions.count)149        return instructionsLength + instructions.reduce(Data()) { $0 + $1.serializedData }150    }151152    private func encodeLength(_ length: Int) -> Data {153        Data.encodeLength(length)154    }155}156