TransactionSEALED
^%◆■^▒▫▓█!□^◆!▫■░@◆█$@#◆%▫&^╲#◆▓&■▒╱░◆~@▪▒▒□╲■╱$▫*▓◆╳%╱╳~░╳■▫◇◇▪
Transaction ID
Execution Error
Error Code: 1009
error caused by: 1 error occurred:
Raw Error
[Error Code: 1009] error caused by: 1 error occurred: * transaction verification failed: [Error Code: 1006] invalid proposal key: public key 6 on account f380b22ef386ac7e does not have a valid signature: [Error Code: 1009] invalid envelope key: public key 6 on account f380b22ef386ac7e does not have a valid signature: signature is not valid
Transaction Summary
TransactionScript Arguments
0vaultIdentifierString
A.f1ab99c82dee3526.USDCFlow.Vault
1amountUInt256
100000
2recipientAddress
Cadence Script
1import MetadataViews from 0x1d7e57aa55817448
2import ViewResolver from 0x1d7e57aa55817448
3import NonFungibleToken from 0x1d7e57aa55817448
4
5import FungibleToken from 0xf233dcee88fe0abe
6import FlowToken from 0x1654653399040a61
7import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
8
9import ScopedFTProviders from 0x1e4aa0b87d10b141
10
11import EVM from 0xe467b9dd11fa00df
12
13import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
14import FlowEVMBridge from 0x1e4aa0b87d10b141
15import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
16import StorageRent from 0x707adbad1428c624
17
18/// This transaction bridges fungible tokens from EVM to Cadence assuming it has already been onboarded to the
19/// FlowEVMBridge. The full amount to be transferred is sourced from EVM, so it's assumed the signer has sufficient
20/// balance of the ERC20 to bridging into Cadence. Also know that the recipient Flow account must have a Receiver
21/// capable of receiving the bridged tokens accessible via published Capability at the token's standard path.
22///
23/// NOTE: The ERC20 must have first been onboarded to the bridge. This can be checked via the method
24/// FlowEVMBridge.evmAddressRequiresOnboarding(address: self.evmContractAddress)
25///
26/// @param vaultIdentifier: The Cadence type identifier of the FungibleToken Vault to bridge
27/// - e.g. vault.getType().identifier
28/// @param amount: The amount of tokens to bridge from EVM and transfer to the recipient
29/// @param recipient: The Flow account address to receive the bridged tokens
30///
31transaction(vaultIdentifier: String, amount: UInt256, recipient: Address) {
32
33 let vaultType: Type
34 let receiver: &{FungibleToken.Vault}
35 let scopedProvider: @ScopedFTProviders.ScopedFTProvider
36 let coa: auth(EVM.Bridge) &EVM.CadenceOwnedAccount
37
38 prepare(signer: auth(BorrowValue, CopyValue, IssueStorageCapabilityController, PublishCapability, SaveValue, UnpublishCapability) &Account) {
39 /* --- Reference the signer's CadenceOwnedAccount --- */
40 //
41 // Borrow a reference to the signer's COA
42 self.coa = signer.storage.borrow<auth(EVM.Bridge) &EVM.CadenceOwnedAccount>(from: /storage/evm)
43 ?? panic("Could not borrow COA from provided gateway address")
44
45 /* --- Construct the Vault type --- */
46 //
47 // Construct the Vault type from the provided identifier
48 self.vaultType = CompositeType(vaultIdentifier)
49 ?? panic("Could not construct Vault type from identifier: ".concat(vaultIdentifier))
50 // Parse the Vault identifier into its components
51 let tokenContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: self.vaultType)
52 ?? panic("Could not get contract address from identifier: ".concat(vaultIdentifier))
53 let tokenContractName = FlowEVMBridgeUtils.getContractName(fromType: self.vaultType)
54 ?? panic("Could not get contract name from identifier: ".concat(vaultIdentifier))
55
56 /* --- Reference the signer's Vault --- */
57 //
58 // Borrow a reference to the FungibleToken Vault, configuring if necessary
59 let viewResolver = getAccount(tokenContractAddress).contracts.borrow<&{ViewResolver}>(name: tokenContractName)
60 ?? panic("Could not borrow ViewResolver from FungibleToken contract")
61 let vaultData = viewResolver.resolveContractView(
62 resourceType: self.vaultType,
63 viewType: Type<FungibleTokenMetadataViews.FTVaultData>()
64 ) as! FungibleTokenMetadataViews.FTVaultData? ?? panic("Could not resolve FTVaultData view")
65 // If the vault does not exist, create it and publish according to the contract's defined configuration
66 if signer.storage.borrow<&{FungibleToken.Vault}>(from: vaultData.storagePath) == nil {
67 signer.storage.save(<-vaultData.createEmptyVault(), to: vaultData.storagePath)
68
69 signer.capabilities.unpublish(vaultData.receiverPath)
70 signer.capabilities.unpublish(vaultData.metadataPath)
71
72 let receiverCap = signer.capabilities.storage.issue<&{FungibleToken.Vault}>(vaultData.storagePath)
73 let metadataCap = signer.capabilities.storage.issue<&{FungibleToken.Vault}>(vaultData.storagePath)
74
75 signer.capabilities.publish(receiverCap, at: vaultData.receiverPath)
76 signer.capabilities.publish(metadataCap, at: vaultData.metadataPath)
77 }
78 self.receiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Vault}>(vaultData.receiverPath)
79 ?? panic("Could not borrow Vault from recipient's account")
80
81 /* --- Configure a ScopedFTProvider --- */
82 //
83 // Calculate the bridge fee - bridging from EVM consumes no storage, so flat fee
84 let approxFee = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 400_000)
85 // Issue and store bridge-dedicated Provider Capability in storage if necessary
86 if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil {
87 let providerCap = signer.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>(
88 /storage/flowTokenVault
89 )
90 signer.storage.save(providerCap, to: FlowEVMBridgeConfig.providerCapabilityStoragePath)
91 }
92 // Copy the stored Provider capability and create a ScopedFTProvider
93 let providerCapCopy = signer.storage.copy<Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>>(
94 from: FlowEVMBridgeConfig.providerCapabilityStoragePath
95 ) ?? panic("Invalid Provider Capability found in storage.")
96 let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee)
97 self.scopedProvider <- ScopedFTProviders.createScopedFTProvider(
98 provider: providerCapCopy,
99 filters: [ providerFilter ],
100 expiration: getCurrentBlock().timestamp + 1.0
101 )
102 }
103
104 execute {
105 // Execute the bridge request
106 let vault: @{FungibleToken.Vault} <- self.coa.withdrawTokens(
107 type: self.vaultType,
108 amount: amount,
109 feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
110 )
111 // Ensure the bridged vault is the correct type
112 assert(vault.getType() == self.vaultType, message: "Bridged vault type mismatch")
113 // Deposit the bridged token into the signer's vault
114 self.receiver.deposit(from: <-vault)
115 // Destroy the ScopedFTProvider
116 destroy self.scopedProvider
117 StorageRent.tryRefill(recipient)
118 }
119}