Outblock / flow-swift

The tool to build application in Flow mobile realm 🌊
https://outblock.github.io/flow-swift/
MIT License
13 stars 7 forks source link

[Testnet] Can't successfully sign transaction(transfer flow tokens between accounts) #16

Open Nightstep opened 1 year ago

Nightstep commented 1 year ago

I have 2 accounts on Testnet, previously I was able to send Flow tokens between accounts using Flow CLI. Now trying to send transaction using this SDK, but transaction fails on validation with error:

[Error Code: 1009] error caused by: 1 error occurred:
    * transaction verification failed: [Error Code: 1006] invalid proposal key: public key 0 on account 31bbb88233df243d does not have a valid signature: [Error Code: 1009] invalid envelope key: public key 0 on account 31bbb88233df243d does not have a valid signature: signature is not valid

Info: https://f.dnz.dev/d6ff9c65feb166505043c420843d5504523f7a784f9b151550933dda6bb169db

I'd be grateful If you could point where to dig, because I totally stuck now.

Here's my code in ViewController.swift file:

import UIKit
import Flow
import CryptoKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        flow.configure(chainID: .testnet)
        Task {
            do {
                let aliceAddress = Flow.Address(hex: "0x31bbb88233df243d")
                let bobAddress = Flow.Address(hex: "0x8ce1531666464325")

                // get Alice account info:
                let aliceAccount: Flow.Account = try await flow.getAccountAtLatestBlock(address: aliceAddress)
                print("Alice's Balance:\(Double(aliceAccount.balance) / 100000000)")

                let bobAccount: Flow.Account = try await flow.getAccountAtLatestBlock(address: bobAddress)
                print("Bob's Balance:\(Double(bobAccount.balance) / 100000000)\n")

                // Preparing Alice's transaction:
                let amountArg = Flow.Argument(type: .ufix64, value: .ufix64(120))
                let recipientArg = Flow.Argument(type: .address, value: .address(bobAddress))

                let alicePrivateKey = try P256.Signing.PrivateKey(rawRepresentation: "0xd08382ba70946ee82af61147e6f84c69932e9c69baaf0c7177731fa0ddc28477".hexValue)

                let signer = ECDSA_P256_Signer(
                    address: aliceAccount.address,
                    keyIndex: aliceAccount.keys.first!.index,
                    privateKey: alicePrivateKey
                )
                let txID = try await flow.sendTransaction(chainID: .testnet,
                                     signers: [signer],
                                     script: Transactions.tokenTransfer,
                                     agrument: [amountArg, recipientArg],
                                     authorizer: [aliceAddress],
                                     payerAddress: aliceAddress,
                                     proposerKey: Flow.TransactionProposalKey(
                                        address: aliceAddress,
                                        keyIndex: aliceAccount.keys.first!.index,
                                        sequenceNumber: aliceAccount.keys.first!.sequenceNumber
                                     ),
                                     blockID: nil)

                print("Transaction sent.\nTx ID: \(txID)")

                let transactionResult = try await flow.getTransactionResultById(id: txID)
                print("Transaction Details:\n\(transactionResult)")

                // Check balances for both accounts after transaction:
                let aliceUpdated: Flow.Account = try await flow.getAccountAtLatestBlock(address: aliceAddress)
                print("Alice Updated Balance:\(Double(aliceUpdated.balance) / 100000000)\n")

                let bobUpdated: Flow.Account = try await flow.getAccountAtLatestBlock(address: bobAddress)
                print("Bob's Updated Balance:\(Double(bobUpdated.balance) / 100000000)\n")

                print("End of sequence.")

            } catch let error {
                print("Something went wrong")
                print(error.localizedDescription)
            }

        }
    }
}

private extension String {
    /// Convert hex string to bytes
    var hexValue: [UInt8] {
        var startIndex = self.startIndex
        return (0 ..< count / 2).compactMap { _ in
            let endIndex = index(after: startIndex)
            defer { startIndex = index(after: endIndex) }
            return UInt8(self[startIndex ... endIndex], radix: 16)
        }
    }
}

There was no example of correct FlowSigner implementation, but I found one in TestCases and modified it a bit:

import CryptoKit
import Foundation
import Flow
import CryptoSwift

struct ECDSA_P256_Signer: FlowSigner {
    var address: Flow.Address
    var keyIndex: Int

    var privateKey: P256.Signing.PrivateKey

    init(address: Flow.Address, keyIndex: Int, privateKey: P256.Signing.PrivateKey) {
        self.address = address
        self.keyIndex = keyIndex
        self.privateKey = privateKey
    }

    func sign(transaction _: Flow.Transaction, signableData: Data) throws -> Data {
        do {
            let digest = SHA256.hash(data: signableData.sha3(.sha256))
            return try privateKey.signature(for: digest).rawRepresentation
        } catch {
            throw error
        }
    }
}

Transaction Cadence code:

import Foundation

public struct Transactions {

    private static let fungibleTokenContractAddr: String = "0x9a0766d93b6608b7"
    private static let flowTokenContractAddr: String = "0x7e60df042a9c0868"

    public static let tokenTransfer: String = """

    import FungibleToken from \(fungibleTokenContractAddr)
    import FlowToken from \(flowTokenContractAddr)

    transaction(amount: UFix64, to: Address) {

        // The Vault resource that holds the tokens that are being transferred
        let sentVault: @FungibleToken.Vault

        prepare(signer: AuthAccount) {

            // Get a reference to the signer's stored vault
            let vaultRef = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)
                ?? panic("Could not borrow reference to the owner's Vault!")

            // Withdraw tokens from the signer's stored vault
            self.sentVault <- vaultRef.withdraw(amount: amount)
        }

        execute {

            // Get the recipient's public account object
            let recipient = getAccount(to)

            // Get a reference to the recipient's Receiver
            let receiverRef = recipient.getCapability(/public/flowTokenReceiver)
                .borrow<&FlowToken.Vault{FungibleToken.Receiver}>()
                ?? panic("Could not borrow receiver reference to the recipient's Vault")

            // Deposit the withdrawn tokens in the recipient's receiver
            receiverRef.deposit(from: <-self.sentVault)
        }
    }
    """
}