DeploySEALED

░#░▓▫@*○○░□~?*?$◆█%#@█▪╲█▓%◆&◆╱▒╱◆▪□▪▫%~*○#^&^╱~○◆▓╲■░▪●█@■□□░■@

Transaction ID

Timestamp

Feb 13, 2026, 02:42:24 AM UTC
2w ago

Block Height

142,038,823

Computation

0

Execution Fee

0.0057 FLOW

Transaction Summary

Deploy

Contract deployment

Contract deployment

Script Arguments

0nameString
MorphoERC4626SinkConnectors
1codeString
import Burner from 0xf233dcee88fe0abe import FungibleToken from 0xf233dcee88fe0abe import EVM from 0xe467b9dd11fa00df import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141 import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141 import FlowToken from 0x1654653399040a61 import DeFiActions from 0x6d888f175c158410 import DeFiActionsUtils from 0x6d888f175c158410 import EVMTokenConnectors from 0x1a771b21fcceadc2 import ERC4626Utils from 0x04f5ae6bef48c1fc /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! /// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! /// /// MorphoERC4626SinkConnectors /// access(all) contract MorphoERC4626SinkConnectors { /// AssetSink /// /// Deposits assets to a Morpho ERC4626 vault (which accepts the asset as a deposit denomination) to the contained COA's /// vault share balance /// access(all) struct AssetSink : DeFiActions.Sink { /// The asset type serving as the price basis in the ERC4626 vault access(self) let assetType: Type /// The EVM address of the asset ERC20 contract access(self) let assetEVMAddress: EVM.EVMAddress /// The address of the ERC4626 vault access(self) let vaultEVMAddress: EVM.EVMAddress /// The COA capability to use for the ERC4626 vault access(self) let coa: Capability<auth(EVM.Call) &EVM.CadenceOwnedAccount> /// The token sink to use for the ERC4626 vault access(self) let tokenSink: EVMTokenConnectors.Sink /// The optional UniqueIdentifier of the ERC4626 vault access(contract) var uniqueID: DeFiActions.UniqueIdentifier? init( vaultEVMAddress: EVM.EVMAddress, coa: Capability<auth(EVM.Call) &EVM.CadenceOwnedAccount>, feeSource: {DeFiActions.Sink, DeFiActions.Source}, uniqueID: DeFiActions.UniqueIdentifier? ) { pre { coa.check(): "Provided COA Capability is invalid - need Capability<&EVM.CadenceOwnedAccount>" feeSource.getSourceType() == Type<@FlowToken.Vault>(): "Invalid feeSource - given Source must provide FlowToken Vault, but provides \(feeSource.getSourceType().identifier)" } self.vaultEVMAddress = vaultEVMAddress self.assetEVMAddress = ERC4626Utils.underlyingAssetEVMAddress(vault: self.vaultEVMAddress) ?? panic("Cannot get an underlying asset EVM address from the vault") self.assetType = FlowEVMBridgeConfig.getTypeAssociated(with: self.assetEVMAddress) ?? panic("Underlying asset for vault \(self.vaultEVMAddress.toString()) (asset \(self.assetEVMAddress.toString())) is not associated with a Cadence FungibleToken - ensure the type & underlying asset contracts are associated via the VM bridge") assert( DeFiActionsUtils.definingContractIsFungibleToken(self.assetType), message: "Derived asset type \(self.assetType.identifier) not FungibleToken type" ) self.coa = coa self.tokenSink = EVMTokenConnectors.Sink( max: nil, depositVaultType: self.assetType, address: coa.borrow()!.address(), feeSource: feeSource, uniqueID: uniqueID ) self.uniqueID = uniqueID } /// Returns the Vault type accepted by this Sink access(all) view fun getSinkType(): Type { return self.assetType } /// Returns an estimate of how much can be withdrawn from the depositing Vault for this Sink to reach capacity access(all) fun minimumCapacity(): UFix64 { // Check the EVMTokenConnectors Sink has capacity to bridge the assets to EVM // TODO: Update EVMTokenConnector.Sink to return 0.0 if it doesn't have fees to pay for the bridge call let coa = self.coa.borrow() if coa == nil { return 0.0 } let tokenSinkCapacity = self.tokenSink.minimumCapacity() return tokenSinkCapacity } /// Deposits up to the Sink's capacity from the provided Vault access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) { // check capacity & early return if none let capacity = self.minimumCapacity() if capacity == 0.0 || from.balance == 0.0 { return; } // withdraw the appropriate amount from the referenced vault & deposit to the EVMTokenConnectors Sink var amount = capacity <= from.balance ? capacity : from.balance // TODO: pass from through and skip the intermediary withdrawal // depositCapacity can deposit less than requested (capacity/fees/bridge constraints), and it doesn’t return "actualDeposited". Without the intermediary vault there's no way to safely compute the amount let deposit <- from.withdraw(amount: amount) self.tokenSink.depositCapacity(from: &deposit as auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) if deposit.balance == amount { // nothing was deposited to the EVMTokenConnectors Sink Burner.burn(<-deposit) return } else if deposit.balance > 0.0 { // update deposit amount & deposit the residual amount = amount - deposit.balance from.deposit(from: <-deposit) } else { // nothing left - burn & execute vault's burnCallback() Burner.burn(<-deposit) } // approve the ERC4626 vault to spend the assets on deposit let uintAmount = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(amount, erc20Address: self.assetEVMAddress) let approveRes = self._call( dry: false, to: self.assetEVMAddress, signature: "approve(address,uint256)", args: [self.vaultEVMAddress, uintAmount], gasLimit: 500_000 )! if approveRes.status != EVM.Status.successful { // TODO: consider more graceful handling of this error panic(self._approveErrorMessage(ufixAmount: amount, uintAmount: uintAmount, approveRes: approveRes)) } // deposit the assets to the ERC4626 vault let depositRes = self._call( dry: false, to: self.vaultEVMAddress, signature: "deposit(uint256,address)", args: [uintAmount, self.coa.borrow()!.address()], gasLimit: 1_000_000 )! if depositRes.status != EVM.Status.successful { panic(self._depositErrorMessage(ufixAmount: amount, uintAmount: uintAmount, depositRes: depositRes)) } } /// Returns a ComponentInfo struct containing information about this component and a list of ComponentInfo for /// each inner component in the stack. access(all) fun getComponentInfo(): DeFiActions.ComponentInfo { return DeFiActions.ComponentInfo( type: self.getType(), id: self.id(), innerComponents: [ self.tokenSink.getComponentInfo() ] ) } /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in /// a DeFiActions stack. See DeFiActions.align() for more information. /// /// @return a copy of the struct's UniqueIdentifier /// access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? { return self.uniqueID } /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information. /// /// @param id: the UniqueIdentifier to set for this component /// access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) { self.uniqueID = id } /// Performs a dry call to the ERC4626 vault /// /// @param to The address of the ERC4626 vault /// @param signature The signature of the function to call /// @param args The arguments to pass to the function /// @param gasLimit The gas limit to use for the call /// /// @return The result of the dry call or `nil` if the COA capability is invalid access(self) fun _call(dry: Bool, to: EVM.EVMAddress, signature: String, args: [AnyStruct], gasLimit: UInt64): EVM.Result? { let calldata = EVM.encodeABIWithSignature(signature, args) let valueBalance = EVM.Balance(attoflow: 0) if let coa = self.coa.borrow() { return dry ? coa.dryCall(to: to, data: calldata, gasLimit: gasLimit, value: valueBalance) : coa.call(to: to, data: calldata, gasLimit: gasLimit, value: valueBalance) } return nil } /// Returns an error message for a failed approve call /// /// @param ufixAmount: the amount of assets to approve /// @param uintAmount: the amount of assets to approve in uint256 format /// @param approveRes: the result of the approve call /// /// @return an error message for a failed approve call /// access(self) fun _approveErrorMessage(ufixAmount: UFix64, uintAmount: UInt256, approveRes: EVM.Result): String { return "Failed to approve ERC4626 vault \(self.vaultEVMAddress.toString()) to spend \(ufixAmount) assets \(self.assetEVMAddress.toString()). " .concat("approvee: \(self.vaultEVMAddress.toString()), amount: \(uintAmount). ") .concat("Error code: \(approveRes.errorCode) Error message: \(approveRes.errorMessage)") } /// Returns an error message for a failed deposit call /// /// @param ufixAmount: the amount of assets to deposit /// @param uintAmount: the amount of assets to deposit in uint256 format /// @param depositRes: the result of the deposit call /// /// @return an error message for a failed deposit call /// access(self) fun _depositErrorMessage(ufixAmount: UFix64, uintAmount: UInt256, depositRes: EVM.Result): String { let coaHex = self.coa.borrow()!.address().toString() return "Failed to deposit \(ufixAmount) assets \(self.assetEVMAddress.toString()) to ERC4626 vault \(self.vaultEVMAddress.toString()). " .concat("amount: \(uintAmount), to: \(coaHex). ") .concat("Error code: \(depositRes.errorCode) Error message: \(depositRes.errorMessage)") } } /// ShareSink /// /// Redeems shares from a Morpho ERC4626 vault to the contained COA's underlying asset balance /// access(all) struct ShareSink : DeFiActions.Sink { /// The share vault type serving as the price basis in the ERC4626 vault access(self) let vaultType: Type /// The EVM address of the asset ERC20 contract access(self) let assetEVMAddress: EVM.EVMAddress /// The address of the ERC4626 vault access(self) let vaultEVMAddress: EVM.EVMAddress /// The COA capability to use for the ERC4626 vault access(self) let coa: Capability<auth(EVM.Call) &EVM.CadenceOwnedAccount> /// The token sink to use for the vault shares access(self) let shareSink: EVMTokenConnectors.Sink /// The optional UniqueIdentifier of the ERC4626 vault access(contract) var uniqueID: DeFiActions.UniqueIdentifier? init( vaultEVMAddress: EVM.EVMAddress, coa: Capability<auth(EVM.Call) &EVM.CadenceOwnedAccount>, feeSource: {DeFiActions.Sink, DeFiActions.Source}, uniqueID: DeFiActions.UniqueIdentifier? ) { pre { coa.check(): "Provided COA Capability is invalid - need Capability<&EVM.CadenceOwnedAccount>" feeSource.getSourceType() == Type<@FlowToken.Vault>(): "Invalid feeSource - given Source must provide FlowToken Vault, but provides \(feeSource.getSourceType().identifier)" } self.vaultEVMAddress = vaultEVMAddress self.vaultType = FlowEVMBridgeConfig.getTypeAssociated(with: vaultEVMAddress) ?? panic("Provided ERC4626 Vault \(vaultEVMAddress.toString()) is not associated with a Cadence FungibleToken - ensure the type & ERC4626 contracts are associated via the VM bridge") assert( DeFiActionsUtils.definingContractIsFungibleToken(self.vaultType), message: "Derived vault type \(self.vaultType.identifier) not FungibleToken type" ) self.assetEVMAddress = ERC4626Utils.underlyingAssetEVMAddress(vault: vaultEVMAddress) ?? panic("Cannot get an underlying asset EVM address from the vault") let assetType = FlowEVMBridgeConfig.getTypeAssociated(with: self.assetEVMAddress) ?? panic("Underlying asset for vault \(self.vaultEVMAddress.toString()) (asset \(self.assetEVMAddress.toString())) is not associated with a Cadence FungibleToken - ensure the type & underlying asset contracts are associated via the VM bridge") assert( DeFiActionsUtils.definingContractIsFungibleToken(assetType), message: "Derived asset type \(assetType.identifier) not FungibleToken type" ) self.coa = coa self.shareSink = EVMTokenConnectors.Sink( max: nil, depositVaultType: self.vaultType, address: coa.borrow()!.address(), feeSource: feeSource, uniqueID: uniqueID ) self.uniqueID = uniqueID } /// Returns the Vault type accepted by this Sink access(all) view fun getSinkType(): Type { return self.vaultType } /// Returns an estimate of how much can be withdrawn from the depositing Vault for this Sink to reach capacity access(all) fun minimumCapacity(): UFix64 { // Check the EVMTokenConnectors Sink has capacity to bridge the shares to EVM // TODO: Update EVMTokenConnector.Sink to return 0.0 if it doesn't have fees to pay for the bridge call let coa = self.coa.borrow() if coa == nil { return 0.0 } let shareSinkCapacity = self.shareSink.minimumCapacity() return shareSinkCapacity } /// Deposits up to the Sink's capacity from the provided Vault access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) { // check capacity & early return if none let capacity = self.minimumCapacity() if capacity == 0.0 || from.balance == 0.0 { return; } // withdraw the appropriate amount from the referenced vault & deposit to the EVMTokenConnectors Sink var amount = capacity <= from.balance ? capacity : from.balance // TODO: pass from through and skip the intermediary withdrawal let deposit <- from.withdraw(amount: amount) self.shareSink.depositCapacity(from: &deposit as auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) if deposit.balance == amount { // nothing was deposited to the EVMTokenConnectors Sink Burner.burn(<-deposit) return } else if deposit.balance > 0.0 { // update deposit amount & deposit the residual amount = amount - deposit.balance from.deposit(from: <-deposit) } else { // nothing left - burn & execute vault's burnCallback() Burner.burn(<-deposit) } let uintShares = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(amount, erc20Address: self.vaultEVMAddress) let coa = self.coa.borrow() ?? panic("can't borrow COA") // redeem the shares from the ERC4626 vault let redeemRes = self._call( dry: false, to: self.vaultEVMAddress, signature: "redeem(uint256,address,address)", args: [uintShares, coa.address(), coa.address()], gasLimit: 1_000_000 )! if redeemRes.status != EVM.Status.successful { // TODO: Consider unwinding the redeem & returning to the from vault // - would require {Sink, Source} instead of just Sink panic(self._redeemErrorMessage(ufixShares: amount, uintShares: uintShares, redeemRes: redeemRes)) } } /// Returns a ComponentInfo struct containing information about this component and a list of ComponentInfo for /// each inner component in the stack. access(all) fun getComponentInfo(): DeFiActions.ComponentInfo { return DeFiActions.ComponentInfo( type: self.getType(), id: self.id(), innerComponents: [ self.shareSink.getComponentInfo() ] ) } /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in /// a DeFiActions stack. See DeFiActions.align() for more information. /// /// @return a copy of the struct's UniqueIdentifier /// access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? { return self.uniqueID } /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information. /// /// @param id: the UniqueIdentifier to set for this component /// access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) { self.uniqueID = id } /// Performs a dry call to the ERC4626 vault /// /// @param to The address of the ERC4626 vault /// @param signature The signature of the function to call /// @param args The arguments to pass to the function /// @param gasLimit The gas limit to use for the call /// /// @return The result of the dry call or `nil` if the COA capability is invalid access(self) fun _call(dry: Bool, to: EVM.EVMAddress, signature: String, args: [AnyStruct], gasLimit: UInt64): EVM.Result? { let calldata = EVM.encodeABIWithSignature(signature, args) let valueBalance = EVM.Balance(attoflow: 0) if let coa = self.coa.borrow() { return dry ? coa.dryCall(to: to, data: calldata, gasLimit: gasLimit, value: valueBalance) : coa.call(to: to, data: calldata, gasLimit: gasLimit, value: valueBalance) } return nil } /// Returns an error message for a failed redeem call /// /// @param ufixShares: the amount of shares to redeem /// @param uintShares: the amount of shares to redeem in uint256 format /// @param depositRes: the result of the redeem call /// /// @return an error message for a failed redeem call /// access(self) fun _redeemErrorMessage(ufixShares: UFix64, uintShares: UInt256, redeemRes: EVM.Result): String { let coaHex = self.coa.borrow()!.address().toString() return "Failed to redeem \(ufixShares) shares \(self.vaultEVMAddress.toString()) from ERC4626 vault for \(self.assetEVMAddress.toString()). " .concat("amount: \(uintShares), to: \(coaHex). ") .concat("Error code: \(redeemRes.errorCode) Error message: \(redeemRes.errorMessage)") } } }

Cadence Script

1transaction(name: String, code: String ) {
2		prepare(signer: auth(AddContract) &Account) {
3			signer.contracts.add(name: name, code: code.utf8 )
4		}
5	}