TransactionSEALED

#!~◇▓●?!~○◆╳□╱╲▓@■?□◇#○▫?$!□*◇■@&▓▒*$*▫#^■□*▓▒□╳*╳□■@╱○╳◇$^○&░@●

Transaction ID

Timestamp

Jan 19, 2026, 07:15:39 AM UTC
1mo ago

Block Height

139,361,104

Computation

0

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

Transaction

Script Arguments

0nftIdentifierString
A.f1ab99c82dee3526.FlowtyDrops.NFT
1childAddress
2ids[UInt256]
[
  "11111",
  "22222"
]

Cadence Script

1import MetadataViews from 0x1d7e57aa55817448
2import ViewResolver from 0x1d7e57aa55817448
3import NonFungibleToken from 0x1d7e57aa55817448
4import FungibleToken from 0xf233dcee88fe0abe
5import FlowToken from 0x1654653399040a61
6import FungibleTokenMetadataViews from 0xf233dcee88fe0abe
7import ScopedFTProviders from 0x1e4aa0b87d10b141
8import EVM from 0xe467b9dd11fa00df
9import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
10import FlowEVMBridge from 0x1e4aa0b87d10b141
11import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
12import HybridCustody from 0xd8a7e05a7ac670c0
13import CapabilityFilter from 0xd8a7e05a7ac670c0
14import CrossVMMetadataViews from 0x1d7e57aa55817448
15
16transaction(nftIdentifier: String, child: Address, ids: [UInt256]) {
17
18    prepare(signer: auth(BorrowValue, CopyValue, IssueStorageCapabilityController, PublishCapability, SaveValue, UnpublishCapability) &Account, payer: auth(BorrowValue, CopyValue, IssueStorageCapabilityController, PublishCapability, SaveValue, UnpublishCapability) &Account) {
19        /* --- Reference the signer's CadenceOwnedAccount --- */
20        //
21        // Borrow a reference to the signer's COA
22        let coa = signer.storage.borrow<auth(EVM.Call, EVM.Bridge) &EVM.CadenceOwnedAccount>(from: /storage/evm)
23            ?? panic("Could not borrow COA from provided gateway address")
24
25        // Construct the NFT type from the provided identifier
26        let nftType = CompositeType(nftIdentifier)
27            ?? panic("Could not construct NFT type from identifier: ".concat(nftIdentifier))
28        let nftContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: nftType)
29            ?? panic("Could not get contract address from identifier: ".concat(nftIdentifier))
30        let nftContractName = FlowEVMBridgeUtils.getContractName(fromType: nftType)
31            ?? panic("Could not get contract name from identifier: ".concat(nftIdentifier))
32
33        let m = signer.storage.borrow<auth(HybridCustody.Manage) &HybridCustody.Manager>(from: HybridCustody.ManagerStoragePath)
34            ?? panic("manager does not exist")
35        let childAcct = m.borrowAccount(addr: child) ?? panic("child account not found")
36
37        /* --- Retrieve the NFT --- */
38        //
39        // Borrow a reference to the NFT collection, configuring if necessary
40        let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName)
41            ?? panic("Could not borrow ViewResolver from NFT contract")
42        let collectionData = viewResolver.resolveContractView(
43                resourceType: nil,
44                viewType: Type<MetadataViews.NFTCollectionData>()
45            ) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view")
46
47        let capType = Type<&{NonFungibleToken.CollectionPublic}>()
48        let controllerID = childAcct.getControllerIDForType(type: capType, forPath: collectionData.storagePath)
49            ?? panic("no controller found for capType")
50
51        let cap = childAcct.getCapability(controllerID: controllerID, type: capType) ?? panic("no cap found")
52        let publicCap = cap as! Capability<&{NonFungibleToken.CollectionPublic}>
53        assert(publicCap.check(), message: "invalid public capability")
54
55        // Get a reference to the child's stored vault
56        let collectionRef = publicCap.borrow()!
57
58        // Calculate the approximate fee for the bridge
59        let approxFee = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 400_000) + (FlowEVMBridgeConfig.baseFee * UFix64(ids.length))
60
61        /* --- Configure a ScopedFTProvider --- */
62        //
63        // Issue and store bridge-dedicated Provider Capability in storage if necessary
64        if payer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil {
65            let providerCap = payer.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>(
66                /storage/flowTokenVault
67            )
68            payer.storage.save(providerCap, to: FlowEVMBridgeConfig.providerCapabilityStoragePath)
69        }
70        // Copy the stored Provider capability and create a ScopedFTProvider
71        let providerCapCopy = payer.storage.copy<Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>>(
72                from: FlowEVMBridgeConfig.providerCapabilityStoragePath
73            ) ?? panic("Invalid Provider Capability found in storage.")
74        let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee)
75        let scopedProvider <- ScopedFTProviders.createScopedFTProvider(
76                provider: providerCapCopy,
77                filters: [ providerFilter ],
78                expiration: getCurrentBlock().timestamp + 1.0
79            )
80
81        // Unwrap NFTs if applicable
82        unwrapNFTsIfApplicable(coa, nftIDs: ids, nftType: nftType, viewResolver: viewResolver)
83
84        // Bridge NFTs from EVM to child flow account
85        for id in ids {
86            let nft: @{NonFungibleToken.NFT} <- coa.withdrawNFT(
87                type: nftType,
88                id: id,
89                feeProvider: &scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
90            )
91
92            assert(
93                nft.getType() == nftType,
94                message: "Bridged nft type mismatch - requested: ".concat(nftType.identifier)
95                    .concat(", received: ").concat(nft.getType().identifier)
96            )
97
98            collectionRef.deposit(token: <- nft)
99        }
100
101        // Destroy the ScopedFTProvider
102        destroy scopedProvider
103    }
104}
105
106/// Unwraps NFTs from a project's custom ERC721 wrapper contract to bridged NFTs on EVM, if applicable.
107/// Enables projects to use their own ERC721 contract while leveraging the bridge's underlying contract,
108/// until direct custom contract support is added to the bridge.
109///
110/// @param coa: The COA of the signer
111/// @param nftIDs: The IDs of the NFTs to wrap
112/// @param nftType: The type of the NFTs to wrap
113/// @param viewResolver: The ViewResolver of the NFT contract
114///
115access(all) fun unwrapNFTsIfApplicable(
116    _ coa: auth(EVM.Call) &EVM.CadenceOwnedAccount,
117    nftIDs: [UInt256],
118    nftType: Type,
119    viewResolver: &{ViewResolver}
120) {
121    // Get the project-defined ERC721 address if it exists
122    if let crossVMPointer = viewResolver.resolveContractView(
123            resourceType: nftType,
124            viewType: Type<CrossVMMetadataViews.EVMPointer>()
125    ) as! CrossVMMetadataViews.EVMPointer? {
126        // Get the underlying ERC721 address if it exists
127        if let underlyingAddress = getUnderlyingERC721Address(coa, crossVMPointer.evmContractAddress) {
128            for id in nftIDs {
129                // Unwrap NFT if it is wrapped
130                if isNFTWrapped(coa,
131                    nftID: id,
132                    underlying: underlyingAddress,
133                    wrapper: crossVMPointer.evmContractAddress
134                ) {
135                    let res = mustCall(coa, crossVMPointer.evmContractAddress,
136                        functionSig: "withdrawTo(address,uint256[])",
137                        args: [coa.address(), [id]]
138                    )
139                    let decodedRes = EVM.decodeABI(types: [Type<Bool>()], data: res.data)
140                    assert(decodedRes.length == 1, message: "Invalid response length")
141                    assert(decodedRes[0] as! Bool, message: "Failed to unwrap NFT")
142                }
143            }
144        }
145    }
146}
147
148/// Gets the underlying ERC721 address if it exists (i.e. if the ERC721 is a wrapper)
149///
150access(all) fun getUnderlyingERC721Address(
151    _ coa: auth(EVM.Call) &EVM.CadenceOwnedAccount,
152    _ wrapperAddress: EVM.EVMAddress
153): EVM.EVMAddress? {
154    let res = coa.call(
155        to: wrapperAddress,
156        data: EVM.encodeABIWithSignature("underlying()", []),
157        gasLimit: 100_000,
158        value: EVM.Balance(attoflow: 0)
159    )
160
161    // If the call fails, return nil
162    if res.status != EVM.Status.successful || res.data.length == 0 {
163        return nil
164    }
165
166    // Decode and return the underlying ERC721 address
167    let decodedResult = EVM.decodeABI(
168        types: [Type<EVM.EVMAddress>()],
169        data: res.data
170    )
171    assert(decodedResult.length == 1, message: "Invalid response length")
172    return decodedResult[0] as! EVM.EVMAddress
173}
174
175/// Checks if the provided NFT is wrapped in the underlying ERC721 contract
176///
177access(all) fun isNFTWrapped(
178    _ coa: auth(EVM.Call) &EVM.CadenceOwnedAccount,
179    nftID: UInt256,
180    underlying: EVM.EVMAddress,
181    wrapper: EVM.EVMAddress
182): Bool {
183    let res = coa.call(
184        to: underlying,
185        data: EVM.encodeABIWithSignature("ownerOf(uint256)", [nftID]),
186        gasLimit: 100_000,
187        value: EVM.Balance(attoflow: 0)
188    )
189
190    // If the call fails, return false
191    if res.status != EVM.Status.successful || res.data.length == 0{
192        return false
193    }
194
195    // Decode and compare the addresses
196    let decodedResult = EVM.decodeABI(
197        types: [Type<EVM.EVMAddress>()],
198        data: res.data
199    )
200    assert(decodedResult.length == 1, message: "Invalid response length")
201    let owner = decodedResult[0] as! EVM.EVMAddress
202    return owner.toString() == wrapper.toString()
203}
204
205/// Calls a function on an EVM contract from provided coa
206///
207access(all) fun mustCall(
208    _ coa: auth(EVM.Call) &EVM.CadenceOwnedAccount,
209    _ contractAddr: EVM.EVMAddress,
210    functionSig: String,
211    args: [AnyStruct]
212): EVM.Result {
213    let res = coa.call(
214        to: contractAddr,
215        data: EVM.encodeABIWithSignature(functionSig, args),
216        gasLimit: 4_000_000,
217        value: EVM.Balance(attoflow: 0)
218    )
219
220    assert(res.status == EVM.Status.successful,
221        message: "Failed to call '".concat(functionSig)
222            .concat("\n\t error code: ").concat(res.errorCode.toString())
223            .concat("\n\t error message: ").concat(res.errorMessage)
224            .concat("\n\t gas used: ").concat(res.gasUsed.toString())
225            .concat("\n\t caller address: 0x").concat(coa.address().toString())
226            .concat("\n\t contract address: 0x").concat(contractAddr.toString())
227    )
228
229    return res
230}