DeploySEALED

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

Transaction ID

Timestamp

Aug 11, 2025, 11:46:44 PM UTC
6mo ago

Block Height

122,643,930

Computation

0

Execution Fee

0.00000599 FLOW

Transaction Summary

Deploy

Contract deployment

Contract deployment

Script Arguments

0nameString
UniswapV2SwapConnectors
1codeString
import FungibleToken from 0xf233dcee88fe0abe import FlowToken from 0x1654653399040a61 import Burner from 0xf233dcee88fe0abe import EVM from 0xe467b9dd11fa00df import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141 import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141 import FlowEVMBridge from 0x1e4aa0b87d10b141 import DeFiActions from 0x92195d814edf9cb0 import SwapConnectors from 0x0bce04a00aedf132 /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! /// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT /// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! /// /// UniswapV2SwapConnectors /// /// DeFiActions Swapper connector implementation fitting UniswapV2 EVM-based swap protocols for use in DeFiActions /// workflows. /// access(all) contract UniswapV2SwapConnectors { /// Swapper /// /// A DeFiActions connector that swaps between tokens using an EVM-based UniswapV2Router contract /// access(all) struct Swapper : DeFiActions.Swapper { /// UniswapV2Router contract's EVM address access(all) let routerAddress: EVM.EVMAddress /// A swap path defining the route followed for facilitated swaps. Each element should be a valid token address /// for which there is a pool available with the previous and subsequent token address via the defined Router access(all) let addressPath: [EVM.EVMAddress] /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol- /// specific Identifier to associated connectors on construction access(contract) var uniqueID: DeFiActions.UniqueIdentifier? /// The pre-conversion currency accepted for a swap access(self) let inVault: Type /// The post-conversion currency returned by a swap access(self) let outVault: Type /// An authorized Capability on the CadenceOwnedAccount which this Swapper executes swaps on behalf of access(self) let coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount> init( routerAddress: EVM.EVMAddress, path: [EVM.EVMAddress], inVault: Type, outVault: Type, coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>, uniqueID: DeFiActions.UniqueIdentifier? ) { pre { path.length >= 2: "Provided path with length of \(path.length) - path must contain at least two EVM addresses)" FlowEVMBridgeConfig.getTypeAssociated(with: path[0]) == inVault: "Provided inVault \(inVault.identifier) is not associated with ERC20 at path[0] \(path[0].toString()) - " .concat("Ensure the type & ERC20 contracts are associated via the VM bridge") FlowEVMBridgeConfig.getTypeAssociated(with: path[path.length - 1]) == outVault: "Provided outVault \(outVault.identifier) is not associated with ERC20 at path[\(path.length - 1)] \(path[path.length - 1].toString()) - " .concat("Ensure the type & ERC20 contracts are associated via the VM bridge") coaCapability.check(): "Provided COA Capability is invalid - provided an active, unrevoked Capability<auth(EVM.Call) &EVM.CadenceOwnedAccount>" } self.routerAddress = routerAddress self.addressPath = path self.uniqueID = uniqueID self.inVault = inVault self.outVault = outVault self.coaCapability = coaCapability } /// Returns a ComponentInfo struct containing information about this Swapper and its inner DFA components /// /// @return 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.uniqueID?.id, innerComponents: [] ) } /// 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 } /// The type of Vault this Swapper accepts when performing a swap access(all) view fun inType(): Type { return self.inVault } /// The type of Vault this Swapper provides when performing a swap access(all) view fun outType(): Type { return self.outVault } /// The estimated amount required to provide a Vault with the desired output balance returned as a BasicQuote /// struct containing the in and out Vault types and quoted in and out amounts /// NOTE: Cadence only supports decimal precision of 8 /// /// @param forDesired: The amount out desired of the post-conversion currency as a result of the swap /// @param reverse: If false, the default inVault -> outVault is used, otherwise, the method estimates a swap /// in the opposite direction, outVault -> inVault /// /// @return a SwapConnectors.BasicQuote containing estimate data. In order to prevent upstream reversion, /// result.inAmount and result.outAmount will be 0.0 if an estimate is not available /// access(all) fun quoteIn(forDesired: UFix64, reverse: Bool): {DeFiActions.Quote} { let amountIn = self.getAmount(out: false, amount: forDesired, path: reverse ? self.addressPath.reverse() : self.addressPath) return SwapConnectors.BasicQuote( inType: reverse ? self.outType() : self.inType(), outType: reverse ? self.inType() : self.outType(), inAmount: amountIn != nil ? amountIn! : 0.0, outAmount: amountIn != nil ? forDesired : 0.0 ) } /// The estimated amount delivered out for a provided input balance returned as a BasicQuote returned as a /// BasicQuote struct containing the in and out Vault types and quoted in and out amounts /// NOTE: Cadence only supports decimal precision of 8 /// /// @param forProvided: The amount provided of the relevant pre-conversion currency /// @param reverse: If false, the default inVault -> outVault is used, otherwise, the method estimates a swap /// in the opposite direction, outVault -> inVault /// /// @return a SwapConnectors.BasicQuote containing estimate data. In order to prevent upstream reversion, /// result.inAmount and result.outAmount will be 0.0 if an estimate is not available /// access(all) fun quoteOut(forProvided: UFix64, reverse: Bool): {DeFiActions.Quote} { let amountOut = self.getAmount(out: true, amount: forProvided, path: reverse ? self.addressPath.reverse() : self.addressPath) return SwapConnectors.BasicQuote( inType: reverse ? self.outType() : self.inType(), outType: reverse ? self.inType() : self.outType(), inAmount: amountOut != nil ? forProvided : 0.0, outAmount: amountOut != nil ? amountOut! : 0.0 ) } /// Performs a swap taking a Vault of type inVault, outputting a resulting outVault. This implementation swaps /// along a path defined on init routing the swap to the pre-defined UniswapV2Router implementation on Flow EVM. /// Any Quote provided defines the amountOutMin value - if none is provided, the current quoted outAmount is /// used. /// NOTE: Cadence only supports decimal precision of 8 /// /// @param quote: A `DeFiActions.Quote` data structure. If provided, quote.outAmount is used as the minimum amount out /// desired otherwise a new quote is generated from current state /// @param inVault: Tokens of type `inVault` to swap for a vault of type `outVault` /// /// @return a Vault of type `outVault` containing the swapped currency. /// access(all) fun swap(quote: {DeFiActions.Quote}?, inVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} { let amountOutMin = quote?.outAmount ?? self.quoteOut(forProvided: inVault.balance, reverse: true).outAmount return <-self.swapExactTokensForTokens(exactVaultIn: <-inVault, amountOutMin: amountOutMin, reverse: false) } /// Performs a swap taking a Vault of type outVault, outputting a resulting inVault. Implementations may choose /// to swap along a pre-set path or an optimal path of a set of paths or even set of contained Swappers adapted /// to use multiple Flow swap protocols. /// Any Quote provided defines the amountOutMin value - if none is provided, the current quoted outAmount is /// used. /// NOTE: Cadence only supports decimal precision of 8 /// /// @param quote: A `DeFiActions.Quote` data structure. If provided, quote.outAmount is used as the minimum amount out /// desired otherwise a new quote is generated from current state /// @param residual: Tokens of type `outVault` to swap back to `inVault` /// /// @return a Vault of type `inVault` containing the swapped currency. /// access(all) fun swapBack(quote: {DeFiActions.Quote}?, residual: @{FungibleToken.Vault}): @{FungibleToken.Vault} { let amountOutMin = quote?.outAmount ?? self.quoteOut(forProvided: residual.balance, reverse: true).outAmount return <-self.swapExactTokensForTokens( exactVaultIn: <-residual, amountOutMin: amountOutMin, reverse: true ) } /// Port of UniswapV2Router.swapExactTokensForTokens swapping the exact amount provided along the given path, /// returning the final output Vault /// /// @param exactVaultIn: The pre-conversion currency to swap /// @param amountOutMin: The minimum amount of post-conversion tokens to swap for /// @param reverse: If false, the default inVault -> outVault is used, otherwise, the method swaps in the /// opposite direction, outVault -> inVault /// /// @return the resulting Vault containing the swapped tokens /// access(self) fun swapExactTokensForTokens( exactVaultIn: @{FungibleToken.Vault}, amountOutMin: UFix64, reverse: Bool ): @{FungibleToken.Vault} { let id = self.uniqueID?.id?.toString() ?? "UNASSIGNED" let idType = self.uniqueID?.getType()?.identifier ?? "UNASSIGNED" let coa = self.borrowCOA() ?? panic("The COA Capability contained by Swapper \(self.getType().identifier) with UniqueIdentifier " .concat("\(idType) ID \(id) is invalid - cannot perform an EVM swap without a valid COA Capability")) // withdraw FLOW from the COA to cover the VM bridge fee let bridgeFeeBalance = EVM.Balance(attoflow: 0) bridgeFeeBalance.setFLOW(flow: 2.0 * FlowEVMBridgeUtils.calculateBridgeFee(bytes: 128)) // bridging to EVM then from EVM, hence factor of 2 let feeVault <- coa.withdraw(balance: bridgeFeeBalance) let feeVaultRef = &feeVault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault} // bridge the provided to the COA's EVM address let inTokenAddress = reverse ? self.addressPath[self.addressPath.length - 1] : self.addressPath[0] let evmAmountIn = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount( exactVaultIn.balance, erc20Address: inTokenAddress ) coa.depositTokens(vault: <-exactVaultIn, feeProvider: feeVaultRef) // approve the router to swap tokens var res = self.call(to: inTokenAddress, signature: "approve(address,uint256)", args: [self.routerAddress, evmAmountIn], gasLimit: 15_000_000, value: 0, dryCall: false )! if res.status != EVM.Status.successful { UniswapV2SwapConnectors._callError("approve(address,uint256)", res, inTokenAddress, idType, id, self.getType()) } // perform the swap res = self.call(to: self.routerAddress, signature: "swapExactTokensForTokens(uint,uint,address[],address,uint)", // amountIn, amountOutMin, path, to, deadline (timestamp) args: [evmAmountIn, UInt256(0), (reverse ? self.addressPath.reverse() : self.addressPath), coa.address(), UInt256(getCurrentBlock().timestamp)], gasLimit: 15_000_000, value: 0, dryCall: false )! if res.status != EVM.Status.successful { // revert because the funds have already been deposited to the COA - a no-op would leave the funds in EVM UniswapV2SwapConnectors._callError("swapExactTokensForTokens(uint,uint,address[],address,uint)", res, self.routerAddress, idType, id, self.getType()) } let decoded = EVM.decodeABI(types: [Type<[UInt256]>()], data: res.data) let amountsOut = decoded[0] as! [UInt256] // withdraw tokens from EVM let outVault <- coa.withdrawTokens(type: self.outType(), amount: amountsOut[amountsOut.length - 1], feeProvider: feeVaultRef ) // clean up the remaining feeVault & return the swap output Vault self.handleRemainingFeeVault(<-feeVault) return <- outVault } /* --- Internal --- */ /// Internal method used to retrieve router.getAmountsIn and .getAmountsOut estimates. The returned array is the /// estimate returned from the router where each value is a swapped amount corresponding to the swap along the /// provided path. /// /// @param out: If true, getAmountsOut is called, otherwise getAmountsIn is called /// @param amount: The amount in or out. If out is true, the amount will be used as the amount in provided, /// otherwise amount defines the desired amount out for the estimate /// @param path: The path of ERC20 token addresses defining the sequence of swaps executed to arrive at the /// desired token out /// /// @return An estimate of the amounts for each swap along the path. If out is true, the return value contains /// the values in, otherwise the array contains the values out for each swap along the path /// access(self) fun getAmount(out: Bool, amount: UFix64, path: [EVM.EVMAddress]): UFix64? { let callRes = self.call(to: self.routerAddress, signature: out ? "getAmountsOut(uint,address[])" : "getAmountsIn(uint,address[])", args: [amount], gasLimit: 5_000_000, value: UInt(0), dryCall: true ) if callRes == nil || callRes!.status != EVM.Status.successful { return nil } let decoded = EVM.decodeABI(types: [Type<[UInt256]>()], data: callRes!.data) // can revert if the type cannot be decoded let uintAmounts: [UInt256] = decoded.length > 0 ? decoded[0] as! [UInt256] : [] if uintAmounts.length == 0 { return nil } else if out { return FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(uintAmounts[uintAmounts.length - 1], erc20Address: path[path.length - 1]) } else { return FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(uintAmounts[0], erc20Address: path[0]) } } /// Deposits any remainder in the provided Vault or burns if it it's empty access(self) fun handleRemainingFeeVault(_ vault: @FlowToken.Vault) { if vault.balance > 0.0 { self.borrowCOA()!.deposit(from: <-vault) } else { Burner.burn(<-vault) } } /// Returns a reference to the Swapper's COA or `nil` if the contained Capability is invalid access(self) view fun borrowCOA(): auth(EVM.Owner) &EVM.CadenceOwnedAccount? { return self.coaCapability.borrow() } /// Makes a call to the Swapper's routerEVMAddress via the contained COA Capability with the provided signature, /// args, and value. If flagged as dryCall, the more efficient and non-mutating COA.dryCall is used. A result is /// returned as long as the COA Capability is valid, otherwise `nil` is returned. access(self) fun call( to: EVM.EVMAddress, signature: String, args: [AnyStruct], gasLimit: UInt64, value: UInt, dryCall: Bool ): EVM.Result? { let calldata = EVM.encodeABIWithSignature(signature, args) let valueBalance = EVM.Balance(attoflow: value) if let coa = self.borrowCOA() { let res: EVM.Result = dryCall ? coa.dryCall(to: to, data: calldata, gasLimit: gasLimit, value: valueBalance) : coa.call(to: to, data: calldata, gasLimit: gasLimit, value: valueBalance) return res } return nil } } /// Converts the given amounts from their ERC20 UInt256 to UFix64 amounts according to the ERC20 defined decimals. /// Assumes each EVM address in path is an ERC20 contract access(self) fun _convertEVMAmountsToCadenceAmounts(_ amounts: [UInt256], path: [EVM.EVMAddress]): [UFix64] { let convertedAmounts: [UFix64]= [] for i, amount in amounts { convertedAmounts.append(FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(amount, erc20Address: path[i])) } return convertedAmounts } /// Reverts with a message constructed from the provided args. Used in the event of a coa.call() error access(self) fun _callError(_ signature: String, _ res: EVM.Result,_ target: EVM.EVMAddress, _ uniqueIDType: String, _ id: String, _ swapperType: Type) { panic("Call to \(target.toString()).\(signature) from Swapper \(swapperType.identifier) " .concat("with UniqueIdentifier \(uniqueIDType) ID \(id) failed: \n\t" .concat("Status value: \(res.status.rawValue)\n\t")) .concat("Error code: \(res.errorCode)\n\t") .concat("ErrorMessage: \(res.errorMessage)\n")) } }

Cadence Script

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