Skip to content

Commit

Permalink
add batch nft bridging to evm txns
Browse files Browse the repository at this point in the history
  • Loading branch information
sisyphusSmiling committed Oct 25, 2024
1 parent 47f8732 commit 3df1914
Show file tree
Hide file tree
Showing 2 changed files with 245 additions and 0 deletions.
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 cadence/transactions/bridge/nft/batch_bridge_nft_to_evm.cdc
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
}
}

0 comments on commit 3df1914

Please sign in to comment.