-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
47f8732
commit 3df1914
Showing
2 changed files
with
245 additions
and
0 deletions.
There are no files selected for viewing
124 changes: 124 additions & 0 deletions
124
cadence/transactions/bridge/nft/batch_bridge_nft_to_any_evm_address.cdc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import "FungibleToken" | ||
import "NonFungibleToken" | ||
import "ViewResolver" | ||
import "MetadataViews" | ||
import "FlowToken" | ||
|
||
import "ScopedFTProviders" | ||
|
||
import "EVM" | ||
|
||
import "FlowEVMBridge" | ||
import "FlowEVMBridgeConfig" | ||
import "FlowEVMBridgeUtils" | ||
|
||
/// Bridges an NFT from the signer's collection in Cadence to the provided recipient in FlowEVM | ||
/// | ||
/// NOTE: This transaction also onboards the NFT to the bridge if necessary which may incur additional fees | ||
/// than bridging an asset that has already been onboarded. | ||
/// | ||
/// @param nftIdentifier: The Cadence type identifier of the NFT to bridge - e.g. nft.getType().identifier | ||
/// @param id: The Cadence NFT.id of the NFT to bridge to EVM | ||
/// @param recipient: The hex-encoded EVM address to receive the NFT | ||
/// | ||
transaction(nftIdentifier: String, ids: [UInt64], recipient: String) { | ||
|
||
let nftType: Type | ||
let collection: auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection} | ||
let coa: auth(EVM.Bridge) &EVM.CadenceOwnedAccount | ||
let requiresOnboarding: Bool | ||
let scopedProvider: @ScopedFTProviders.ScopedFTProvider | ||
|
||
prepare(signer: auth(CopyValue, BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { | ||
/* --- Reference the signer's CadenceOwnedAccount --- */ | ||
// | ||
// Borrow a reference to the signer's COA | ||
self.coa = signer.storage.borrow<auth(EVM.Bridge) &EVM.CadenceOwnedAccount>(from: /storage/evm) | ||
?? panic("Could not borrow COA from provided gateway address") | ||
|
||
/* --- Construct the NFT type --- */ | ||
// | ||
// Construct the NFT type from the provided identifier | ||
self.nftType = CompositeType(nftIdentifier) | ||
?? panic("Could not construct NFT type from identifier: ".concat(nftIdentifier)) | ||
// Parse the NFT identifier into its components | ||
let nftContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: self.nftType) | ||
?? panic("Could not get contract address from identifier: ".concat(nftIdentifier)) | ||
let nftContractName = FlowEVMBridgeUtils.getContractName(fromType: self.nftType) | ||
?? panic("Could not get contract name from identifier: ".concat(nftIdentifier)) | ||
|
||
/* --- Retrieve the NFT --- */ | ||
// | ||
// Borrow a reference to the NFT collection, configuring if necessary | ||
let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) | ||
?? panic("Could not borrow ViewResolver from NFT contract") | ||
let collectionData = viewResolver.resolveContractView( | ||
resourceType: self.nftType, | ||
viewType: Type<MetadataViews.NFTCollectionData>() | ||
) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") | ||
self.collection = signer.storage.borrow<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}>( | ||
from: collectionData.storagePath | ||
) ?? panic("Could not access signer's NFT Collection") | ||
|
||
// Withdraw the requested NFT & set a cap on the withdrawable bridge fee | ||
var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( | ||
bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction | ||
) | ||
// Determine if the NFT requires onboarding - this impacts the fee required | ||
self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nftType) | ||
?? panic("Bridge does not support this asset type") | ||
// Add the onboarding fee if onboarding is necessary | ||
if self.requiresOnboarding { | ||
approxFee = approxFee + FlowEVMBridgeConfig.onboardFee | ||
} | ||
|
||
/* --- Configure a ScopedFTProvider --- */ | ||
// | ||
// Issue and store bridge-dedicated Provider Capability in storage if necessary | ||
if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { | ||
let providerCap = signer.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>( | ||
/storage/flowTokenVault | ||
) | ||
signer.storage.save(providerCap, to: FlowEVMBridgeConfig.providerCapabilityStoragePath) | ||
} | ||
// Copy the stored Provider capability and create a ScopedFTProvider | ||
let providerCapCopy = signer.storage.copy<Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>>( | ||
from: FlowEVMBridgeConfig.providerCapabilityStoragePath | ||
) ?? panic("Invalid Provider Capability found in storage.") | ||
let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) | ||
self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( | ||
provider: providerCapCopy, | ||
filters: [ providerFilter ], | ||
expiration: getCurrentBlock().timestamp + 1.0 | ||
) | ||
} | ||
|
||
execute { | ||
if self.requiresOnboarding { | ||
// Onboard the NFT to the bridge | ||
FlowEVMBridge.onboardByType( | ||
self.nftType, | ||
feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} | ||
) | ||
} | ||
|
||
for id in ids { | ||
// Withdraw the NFT & ensure it is the correct type | ||
let nft <-self.collection.withdraw(withdrawID: id) | ||
assert( | ||
nft.getType() == self.nftType, | ||
message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier).concat(", received: ").concat(nft.getType().identifier) | ||
) | ||
// Execute the bridge to EVM | ||
let recipientEVMAddress = EVM.addressFromString(recipient) | ||
FlowEVMBridge.bridgeNFTToEVM( | ||
token: <-nft, | ||
to: EVM.addressFromString(recipient), | ||
feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} | ||
) | ||
} | ||
|
||
// Destroy the ScopedFTProvider | ||
destroy self.scopedProvider | ||
} | ||
} |
121 changes: 121 additions & 0 deletions
121
cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import "FungibleToken" | ||
import "NonFungibleToken" | ||
import "ViewResolver" | ||
import "MetadataViews" | ||
import "FlowToken" | ||
|
||
import "ScopedFTProviders" | ||
|
||
import "EVM" | ||
|
||
import "FlowEVMBridge" | ||
import "FlowEVMBridgeConfig" | ||
import "FlowEVMBridgeUtils" | ||
|
||
/// Bridges NFTs (from the same collection) from the signer's collection in Cadence to the signer's COA in FlowEVM | ||
/// | ||
/// NOTE: This transaction also onboards the NFT to the bridge if necessary which may incur additional fees | ||
/// than bridging an asset that has already been onboarded. | ||
/// | ||
/// @param nftIdentifier: The Cadence type identifier of the NFT to bridge - e.g. nft.getType().identifier | ||
/// @param id: The Cadence NFT.id of the NFT to bridge to EVM | ||
/// | ||
transaction(nftIdentifier: String, ids: [UInt64]) { | ||
|
||
let nftType: Type | ||
let collection: auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection} | ||
let coa: auth(EVM.Bridge) &EVM.CadenceOwnedAccount | ||
let requiresOnboarding: Bool | ||
let scopedProvider: @ScopedFTProviders.ScopedFTProvider | ||
|
||
prepare(signer: auth(CopyValue, BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) { | ||
/* --- Reference the signer's CadenceOwnedAccount --- */ | ||
// | ||
// Borrow a reference to the signer's COA | ||
self.coa = signer.storage.borrow<auth(EVM.Bridge) &EVM.CadenceOwnedAccount>(from: /storage/evm) | ||
?? panic("Could not borrow COA from provided gateway address") | ||
|
||
/* --- Construct the NFT type --- */ | ||
// | ||
// Construct the NFT type from the provided identifier | ||
self.nftType = CompositeType(nftIdentifier) | ||
?? panic("Could not construct NFT type from identifier: ".concat(nftIdentifier)) | ||
// Parse the NFT identifier into its components | ||
let nftContractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: self.nftType) | ||
?? panic("Could not get contract address from identifier: ".concat(nftIdentifier)) | ||
let nftContractName = FlowEVMBridgeUtils.getContractName(fromType: self.nftType) | ||
?? panic("Could not get contract name from identifier: ".concat(nftIdentifier)) | ||
|
||
/* --- Retrieve the NFT --- */ | ||
// | ||
// Borrow a reference to the NFT collection, configuring if necessary | ||
let viewResolver = getAccount(nftContractAddress).contracts.borrow<&{ViewResolver}>(name: nftContractName) | ||
?? panic("Could not borrow ViewResolver from NFT contract") | ||
let collectionData = viewResolver.resolveContractView( | ||
resourceType: self.nftType, | ||
viewType: Type<MetadataViews.NFTCollectionData>() | ||
) as! MetadataViews.NFTCollectionData? ?? panic("Could not resolve NFTCollectionData view") | ||
self.collection = signer.storage.borrow<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}>( | ||
from: collectionData.storagePath | ||
) ?? panic("Could not access signer's NFT Collection") | ||
|
||
// Withdraw the requested NFT & set a cap on the withdrawable bridge fee | ||
var approxFee = FlowEVMBridgeUtils.calculateBridgeFee( | ||
bytes: 200_000 // 200 kB as upper bound on movable storage used in a single transaction | ||
) | ||
// Determine if the NFT requires onboarding - this impacts the fee required | ||
self.requiresOnboarding = FlowEVMBridge.typeRequiresOnboarding(self.nftType) | ||
?? panic("Bridge does not support this asset type") | ||
// Add the onboarding fee if onboarding is necessary | ||
if self.requiresOnboarding { | ||
approxFee = approxFee + FlowEVMBridgeConfig.onboardFee | ||
} | ||
|
||
/* --- Configure a ScopedFTProvider --- */ | ||
// | ||
// Issue and store bridge-dedicated Provider Capability in storage if necessary | ||
if signer.storage.type(at: FlowEVMBridgeConfig.providerCapabilityStoragePath) == nil { | ||
let providerCap = signer.capabilities.storage.issue<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>( | ||
/storage/flowTokenVault | ||
) | ||
signer.storage.save(providerCap, to: FlowEVMBridgeConfig.providerCapabilityStoragePath) | ||
} | ||
// Copy the stored Provider capability and create a ScopedFTProvider | ||
let providerCapCopy = signer.storage.copy<Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Provider}>>( | ||
from: FlowEVMBridgeConfig.providerCapabilityStoragePath | ||
) ?? panic("Invalid Provider Capability found in storage.") | ||
let providerFilter = ScopedFTProviders.AllowanceFilter(approxFee) | ||
self.scopedProvider <- ScopedFTProviders.createScopedFTProvider( | ||
provider: providerCapCopy, | ||
filters: [ providerFilter ], | ||
expiration: getCurrentBlock().timestamp + 1.0 | ||
) | ||
} | ||
|
||
execute { | ||
if self.requiresOnboarding { | ||
// Onboard the NFT to the bridge | ||
FlowEVMBridge.onboardByType( | ||
self.nftType, | ||
feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} | ||
) | ||
} | ||
|
||
for id in ids { | ||
// Withdraw the NFT & ensure it is the correct type | ||
let nft <-self.collection.withdraw(withdrawID: id) | ||
assert( | ||
nft.getType() == self.nftType, | ||
message: "Bridged nft type mismatch - requested: ".concat(self.nftType.identifier).concat(", received: ").concat(nft.getType().identifier) | ||
) | ||
// Execute the bridge to EVM for the current ID | ||
self.coa.depositNFT( | ||
nft: <-nft, | ||
feeProvider: &self.scopedProvider as auth(FungibleToken.Withdraw) &{FungibleToken.Provider} | ||
) | ||
} | ||
|
||
// Destroy the ScopedFTProvider | ||
destroy self.scopedProvider | ||
} | ||
} |