Smart Contract

FlowEVMBridgeTokenEscrow

A.1e4aa0b87d10b141.FlowEVMBridgeTokenEscrow

Valid From

85,982,189

Deployed

1w ago
Feb 15, 2026, 04:46:24 AM UTC

Dependents

0 imports
1
2import Burner from 0xf233dcee88fe0abe
3import FungibleToken from 0xf233dcee88fe0abe
4import NonFungibleToken from 0x1d7e57aa55817448
5import MetadataViews from 0x1d7e57aa55817448
6import ViewResolver from 0x1d7e57aa55817448
7import FlowToken from 0x1654653399040a61
8
9import EVM from 0xe467b9dd11fa00df
10
11import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
12import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
13import CrossVMToken from 0x1e4aa0b87d10b141
14
15/// This escrow contract handles the locking of fungible tokens that are bridged from Cadence to EVM and retrieval of
16/// locked assets in escrow when they are bridged back to Cadence.
17///
18access(all) contract FlowEVMBridgeTokenEscrow {
19
20    /**********************
21            Getters
22    ***********************/
23
24    /// Returns whether the Locker has been initialized for the given fungible token type
25    ///
26    /// @param forType: Type of the locked fungible tokens
27    ///
28    /// @returns true if the Locker has been initialized for the given fungible token type, otherwise false
29    ///
30    access(all) view fun isInitialized(forType: Type): Bool {
31        return self.borrowLocker(forType: forType) != nil
32    }
33
34    /// Returns the balance of locked tokens for the given fungible token type
35    ///
36    /// @param tokenType: Type of the locked fungible tokens
37    ///
38    /// @returns The balance of locked tokens for the given fungible token type or nil if the locker doesn't exist
39    ///
40    access(all) view fun getLockedTokenBalance(tokenType: Type): UFix64? {
41        return self.borrowLocker(forType: tokenType)?.getLockedBalance() ?? nil
42    }
43
44    /// Returns the type of the locked vault for the given fungible token type
45    ///
46    /// @param tokenType: Type of the locked fungible tokens
47    ///
48    /// @returns The type of the locked vault for the given fungible token type or nil if the locker doesn't exist
49    ///
50    access(all) view fun getViews(tokenType: Type): [Type]? {
51        return self.borrowLocker(forType: tokenType)?.getViews() ?? []
52    }
53
54    /**********************
55        Bridge Methods
56    ***********************/
57
58    /// Initializes the Locker for the given fungible token type if it hasn't been initialized yet
59    ///
60    access(account) fun initializeEscrow(
61        with vault: @{FungibleToken.Vault},
62        name: String,
63        symbol: String,
64        decimals: UInt8,
65        evmTokenAddress: EVM.EVMAddress
66    ) {
67        pre {
68            vault.balance == 0.0:
69            "Vault contains a balance=".concat(vault.balance.toString())
70                .concat(" - can only initialize Escrow with an empty vault")
71        }
72        let lockedType = vault.getType()
73        let lockerPath = FlowEVMBridgeUtils.deriveEscrowStoragePath(fromType: lockedType)
74            ?? panic("Problem deriving Locker path for Vault type identifier=".concat(lockedType.identifier))
75        if self.account.storage.type(at: lockerPath) != nil {
76            panic("Token Locker already stored at storage path=".concat(lockedType.identifier))
77        }
78
79        // Create the Locker, lock a new vault of given type and save at the derived path
80        let locker <- create Locker(
81            name: name,
82            symbol: symbol,
83            decimals: decimals,
84            lockedVault: <-vault,
85            evmTokenAddress: evmTokenAddress
86        )
87        self.account.storage.save(<-locker, to: lockerPath)
88    }
89
90    /// Locks the fungible tokens in escrow returning the storage used by locking the Vault
91    ///
92    access(account) fun lockTokens(_ vault: @{FungibleToken.Vault}): UInt64 {
93        let locker = self.borrowLocker(forType: vault.getType())
94            ?? panic("Locker doesn't exist for given type=".concat(vault.getType().identifier))
95
96        let preStorageSnapshot = self.account.storage.used
97        locker.deposit(from: <-vault)
98        let postStorageSnapshot = self.account.storage.used
99
100        // Return the amount of storage used by the locker after storing the NFT
101        if postStorageSnapshot < preStorageSnapshot {
102            // Due to atree inlining, account storage usage may counterintuitively decrease at times - return 0
103            return 0
104        } else {
105            // Otherwise, return the storage usage delta
106            return postStorageSnapshot - preStorageSnapshot
107        }
108    }
109
110    /// Unlocks the tokens of the given type and amount, reverting if it isn't in escrow
111    ///
112    access(account) fun unlockTokens(type: Type, amount: UFix64): @{FungibleToken.Vault} {
113        let locker = self.borrowLocker(forType: type)
114            ?? panic("Locker doesn't exist for given type=".concat(type.identifier))
115        return <- locker.withdraw(amount: amount)
116            
117    }
118
119    /// Retrieves an entitled locker for the given type or nil if it doesn't exist
120    ///
121    access(self) view fun borrowLocker(forType: Type): auth(FungibleToken.Withdraw) &Locker? {
122        if let lockerPath = FlowEVMBridgeUtils.deriveEscrowStoragePath(fromType: forType) {
123            if self.account.storage.type(at: lockerPath) == Type<@Locker>() {
124                return self.account.storage.borrow<auth(FungibleToken.Withdraw) &Locker>(from: lockerPath)
125            }
126        }
127        return nil
128    }
129
130    /*********************
131            Locker
132    *********************/
133
134    /// The resource managing the locking & unlocking of FTs via this contract's interface.
135    ///
136    access(all) resource Locker : CrossVMToken.EVMTokenInfo, FungibleToken.Receiver, FungibleToken.Provider, ViewResolver.Resolver {
137        /// Corresponding name assigned in the tokens' corresponding ERC20 contract
138        access(all) let name: String
139        /// Corresponding symbol assigned in the tokens' corresponding ERC20 contract
140        access(all) let symbol: String
141        /// Corresponding decimals assigned in the tokens' corresponding ERC20 contract. While Cadence support floating
142        /// point numbers, EVM does not, so we need to keep track of the decimals to convert between the two.
143        access(all) let decimals: UInt8
144        /// Corresponding ERC20 address for the locked tokens
145        access(all) let evmTokenAddress: EVM.EVMAddress
146        // Vault to hold all locked tokens
147        access(self) let lockedVault: @{FungibleToken.Vault}
148
149
150        init(name: String, symbol: String, decimals: UInt8, lockedVault: @{FungibleToken.Vault}, evmTokenAddress: EVM.EVMAddress) {
151            self.decimals = decimals
152            self.name = name
153            self.symbol = symbol
154            self.evmTokenAddress = evmTokenAddress
155
156            self.lockedVault <- lockedVault
157            // Locked Vaults must accept their own type as Lockers escrow Vaults on a 1:1 type basis
158            assert(
159                self.lockedVault.isSupportedVaultType(type: self.lockedVault.getType()),
160                message: "Locked Vault does not accept its own type=".concat(self.lockedVault.getType().identifier)
161            )
162        }
163
164        /// Gets the ERC20 name value
165        access(all) view fun getName(): String {
166            return self.name
167        }
168        /// Gets the ERC20 symbol value
169        access(all) view fun getSymbol(): String {
170            return self.symbol
171        }
172        /// Gets the ERC20 decimals value
173        access(all) view fun getDecimals(): UInt8 {
174            return self.decimals
175        }
176        /// Get the EVM contract address of the locked Vault's corresponding ERC20 contract address
177        ///
178        access(all) view fun getEVMContractAddress(): EVM.EVMAddress {
179            return self.evmTokenAddress
180        }
181
182        /// Returns the balance of tokens in the locked Vault
183        ///
184        access(all) view fun getLockedBalance(): UFix64 {
185            return self.lockedVault.balance
186        }
187
188        /// Returns the type of the locked vault
189        ///
190        access(all) view fun getLockedType(): Type {
191            return self.lockedVault.getType()
192        }
193
194        /// Function to ask a provider if a specific amount of tokens is available to be withdrawn from the locked vault
195        ///
196        access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
197            return self.lockedVault.isAvailableToWithdraw(amount: amount)
198        }
199
200        /// Returns a mapping of vault types that this locker accepts. This Locker will only accept Vaults of the same
201        ///
202        access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
203            return {self.lockedVault.getType(): true}
204        }
205
206        /// Returns whether or not the given type is accepted by the Receiver
207        ///
208        access(all) view fun isSupportedVaultType(type: Type): Bool {
209            return self.getSupportedVaultTypes()[type] ?? false
210        }
211
212        /// Returns the views supported by the locked Vault
213        ///
214        access(all) view fun getViews(): [Type] {
215            return self.lockedVault.getViews()
216        }
217
218        /// Resolves the requested view type on the locked FT if it supports the requested view type
219        ///
220        access(all) fun resolveView(_ view: Type): AnyStruct? {
221            return self.lockedVault.resolveView(view)
222        }
223
224        /// Deposits the given token vault into the contained locked Vault
225        ///
226        access(all) fun deposit(from: @{FungibleToken.Vault}) {
227            self.lockedVault.deposit(from: <-from)
228        }
229
230        /// Withdraws an amount of tokens from this locker, removing it from the vault and returning it
231        ///
232        access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
233            return <-self.lockedVault.withdraw(amount: amount)
234        }
235    }
236}
237