Smart Contract

ERC4626SwapConnectors

A.04f5ae6bef48c1fc.ERC4626SwapConnectors

Valid From

142,038,867

Deployed

2w ago
Feb 13, 2026, 02:43:07 AM UTC

Dependents

0 imports
1import Burner from 0xf233dcee88fe0abe
2import FungibleToken from 0xf233dcee88fe0abe
3import EVM from 0xe467b9dd11fa00df
4import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
5import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
6import DeFiActions from 0x6d888f175c158410
7import DeFiActionsUtils from 0x6d888f175c158410
8import ERC4626SinkConnectors from 0x04f5ae6bef48c1fc
9import SwapConnectors from 0xe1a479f0cb911df9
10import EVMTokenConnectors from 0x1a771b21fcceadc2
11import ERC4626Utils from 0x04f5ae6bef48c1fc
12
13/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
14/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
15/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16///
17/// ERC4626SwapConnectors
18///
19/// Implements the DeFiActions.Swapper interface to swap asset tokens to 4626 shares, integrating the connector with an
20/// EVM ERC4626 Vault.
21///
22access(all) contract ERC4626SwapConnectors {
23
24    /// Swapper
25    ///
26    /// An implementation of the DeFiActions.Swapper interface to swap assets to 4626 shares where the input token is
27    /// underlying asset in the 4626 vault. Both the asset & the 4626 shares must be onboarded to the VM bridge in order
28    /// for liquidity to flow between Cadnece & EVM. These "swaps" are performed by depositing the input asset into the
29    /// ERC4626 vault and withdrawing the resulting shares from the ERC4626 vault.
30    ///
31    /// NOTE: Since ERC4626 vaults typically do not support synchronous withdrawals, this Swapper only supports the
32    ///     default inType -> outType path via swap() and reverts on swapBack() since the withdrawal cannot be returned
33    ///     synchronously.
34    ///
35    access(all) struct Swapper : DeFiActions.Swapper {
36        /// The asset type serving as the price basis in the ERC4626 vault
37        access(self) let asset: Type
38        /// The EVM address of the asset ERC20 asset underlying the ERC4626 vault
39        access(self) let assetEVMAddress: EVM.EVMAddress
40        /// The address of the ERC4626 vault
41        access(self) let vault: EVM.EVMAddress
42        /// The type of the bridged ERC4626 vault
43        access(self) let vaultType: Type
44        /// The token sink to use for the ERC4626 vault
45        access(self) let assetSink: ERC4626SinkConnectors.AssetSink
46        /// The token source to use for the ERC4626 vault
47        access(self) let shareSource: EVMTokenConnectors.Source
48        /// The optional UniqueIdentifier of the ERC4626 vault
49        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
50
51        init(
52            asset: Type,
53            vault: EVM.EVMAddress,
54            coa: Capability<auth(EVM.Call, EVM.Bridge) &EVM.CadenceOwnedAccount>,
55            feeSource: {DeFiActions.Sink, DeFiActions.Source},
56            uniqueID: DeFiActions.UniqueIdentifier?
57        ) {
58            pre {
59                DeFiActionsUtils.definingContractIsFungibleToken(asset):
60                "Provided asset \(asset.identifier) is not a Vault type"
61                coa.check():
62                "Provided COA Capability is invalid - need Capability<&EVM.CadenceOwnedAccount>"
63            }
64            self.asset = asset
65            self.assetEVMAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: asset)
66                ?? panic("Provided asset \(asset.identifier) is not associated with ERC20 - ensure the type & ERC20 contracts are associated via the VM bridge")
67            self.vault = vault
68            self.vaultType = FlowEVMBridgeConfig.getTypeAssociated(with: vault)
69                ?? panic("Provided ERC4626 Vault \(vault.toString()) is not associated with a Cadence FungibleToken - ensure the type & ERC4626 contracts are associated via the VM bridge")
70
71            self.assetSink = ERC4626SinkConnectors.AssetSink(
72                asset: asset,
73                vault: vault,
74                coa: coa,
75                feeSource: feeSource,
76                uniqueID: uniqueID
77            )
78            self.shareSource = EVMTokenConnectors.Source(
79                min: nil,
80                withdrawVaultType: self.vaultType,
81                coa: coa,
82                feeSource: feeSource,
83                uniqueID: uniqueID
84            )
85            
86            self.uniqueID = uniqueID
87        }
88
89        /// The type of Vault this Swapper accepts when performing a swap
90        access(all) view fun inType(): Type {
91            return self.asset
92        }
93        /// The type of Vault this Swapper provides when performing a swap
94        access(all) view fun outType(): Type {
95            return self.vaultType
96        }
97        /// The estimated amount required to provide a Vault with the desired output balance
98        access(all) fun quoteIn(forDesired: UFix64, reverse: Bool): {DeFiActions.Quote} {
99            if reverse { // unsupported
100                return SwapConnectors.BasicQuote(
101                    inType: self.vaultType,
102                    outType: self.asset,
103                    inAmount: 0.0,
104                    outAmount: 0.0
105                )
106            }
107            let uintForDesired = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(forDesired, erc20Address: self.vault)
108            if let uintRequired = ERC4626Utils.previewMint(vault: self.vault, shares: uintForDesired) {
109                let uintMaxAllowed = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(UFix64.max, erc20Address: self.assetEVMAddress)
110
111                if uintRequired > uintMaxAllowed {
112                    return SwapConnectors.BasicQuote(
113                        inType: self.asset,
114                        outType: self.vaultType,
115                        inAmount: UFix64.max,
116                        outAmount: forDesired
117                    )
118                }
119                let ufixRequired = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(uintRequired, erc20Address: self.assetEVMAddress)
120                return SwapConnectors.BasicQuote(
121                    inType: self.asset,
122                    outType: self.vaultType,
123                    inAmount: ufixRequired,
124                    outAmount: forDesired
125                )
126            }
127            return SwapConnectors.BasicQuote(
128                inType: self.asset,
129                outType: self.vaultType,
130                inAmount: 0.0,
131                outAmount: 0.0
132            )
133        }
134        /// The estimated amount delivered out for a provided input balance
135        access(all) fun quoteOut(forProvided: UFix64, reverse: Bool): {DeFiActions.Quote} {
136            if reverse { // unsupported
137                return SwapConnectors.BasicQuote(
138                    inType: self.vaultType,
139                    outType: self.asset,
140                    inAmount: 0.0,
141                    outAmount: 0.0
142                )
143            }
144            
145            // ensure the provided amount is not greater than the maximum deposit capacity
146            let maxCapacity = self.assetSink.minimumCapacity()
147            var _forProvided = forProvided
148            if maxCapacity == 0.0 {
149                _forProvided = 0.0
150            } else if _forProvided > maxCapacity {
151                _forProvided = maxCapacity
152            }
153            let uintForProvided = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(_forProvided, erc20Address: self.assetEVMAddress)
154
155            
156            if let uintShares = ERC4626Utils.previewDeposit(vault: self.vault, assets: uintForProvided) {
157                let ufixShares = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(uintShares, erc20Address: self.vault)
158                return SwapConnectors.BasicQuote(
159                    inType: self.asset,
160                    outType: self.vaultType,
161                    inAmount: _forProvided,
162                    outAmount: ufixShares
163                )
164            }
165            return SwapConnectors.BasicQuote(
166                inType: self.asset,
167                outType: self.vaultType,
168                inAmount: 0.0,
169                outAmount: 0.0
170            )
171        }
172        /// Performs a swap taking a Vault of type inVault, outputting a resulting outVault. Implementations may choose
173        /// to swap along a pre-set path or an optimal path of a set of paths or even set of contained Swappers adapted
174        /// to use multiple Flow swap protocols.
175        access(all) fun swap(quote: {DeFiActions.Quote}?, inVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
176            if inVault.balance == 0.0 {
177                // nothing to swap - burn the inVault and return an empty outVault
178                Burner.burn(<-inVault)
179                return <- DeFiActionsUtils.getEmptyVault(self.vaultType)
180            }
181
182            // assign or get the quote for the swap
183            let _quote = quote ?? self.quoteOut(forProvided: inVault.balance, reverse: false)
184
185            // get the before available shares
186            let beforeAvailable = self.shareSource.minimumAvailable()
187
188            // deposit the inVault into the asset sink
189            self.assetSink.depositCapacity(from: &inVault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault})
190            Burner.burn(<-inVault)
191
192            // get the after available shares
193            let afterAvailable = self.shareSource.minimumAvailable()
194            assert(afterAvailable > beforeAvailable, message: "Expected ERC4626 Vault \(self.vault.toString()) to have more shares after depositing")
195
196            // withdraw the available difference in shares
197            let availableDiff = afterAvailable - beforeAvailable
198            let sharesVault <- self.shareSource.withdrawAvailable(maxAmount: availableDiff)
199            return <- sharesVault
200        }
201        /// Performs a swap taking a Vault of type outVault, outputting a resulting inVault. Implementations may choose
202        /// to swap along a pre-set path or an optimal path of a set of paths or even set of contained Swappers adapted
203        /// to use multiple Flow swap protocols.
204        // TODO: Impl detail - accept quote that was just used by swap() but reverse the direction assuming swap() was just called
205        access(all) fun swapBack(quote: {DeFiActions.Quote}?, residual: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
206            panic("ERC4626SwapConnectors.Swapper.swapBack() is not supported - ERC4626 Vaults do not support synchronous withdrawals")
207        }
208        /// Returns a ComponentInfo struct containing information about this component and a list of ComponentInfo for
209        /// each inner component in the stack.
210        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
211            return DeFiActions.ComponentInfo(
212                type: self.getType(),
213                id: self.id(),
214                innerComponents: [
215                    self.assetSink.getComponentInfo(),
216                    self.shareSource.getComponentInfo()
217                ]
218            )
219        }
220        /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
221        /// a DeFiActions stack. See DeFiActions.align() for more information.
222        ///
223        /// @return a copy of the struct's UniqueIdentifier
224        ///
225        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
226            return self.uniqueID
227        }
228        /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
229        /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
230        ///
231        /// @param id: the UniqueIdentifier to set for this component
232        ///
233        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
234            self.uniqueID = id
235        }
236    }
237}
238