Skip to content

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

@Nightstep

Description

@Nightstep

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)
        }
    }
    """
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions