This repository was archived by the owner on Mar 11, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
feat: add swap strategy #56
Merged
Merged
Changes from 2 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
a506332
feat: add swap strategy
royvardhan 2cd8065
feat: use batch txn feature for swaps and fill order
royvardhan bb89f74
refactor: replace 1inch calls with UniswapRouter02
royvardhan c4b3e31
feat: calculate swap operations based on current balances
royvardhan e6a8d8e
feat: update config with deployed addresses, add test
royvardhan 54b87c1
test: handle pair creation and approvals
royvardhan 5f339db
test: fix univ2 contract issues and update deployments
royvardhan 41ccabe
feat: deploy BatchExecutor and use storage ovveride for token balances
royvardhan d75dd42
Merge branch 'main' into feat/filler-swap-strategy
royvardhan 0ad4159
test: use indexer stream to check filled status
royvardhan 1d3af00
test: update link
royvardhan 2687b93
test: fix relaying and use erc20 token as inputs
royvardhan 4da62b2
filler tests pass
royvardhan 3c69c7a
feat: add fill,post,swap gas estimate caching to speed up execution
royvardhan 4f212d5
refactor: use in-memory cache
royvardhan 5db23d9
feat: add safe service, resolve pr comment
royvardhan 60d0b01
feat: rm safeService, deploy universal router and add it, debug
royvardhan 050e5da
feat: update deployments after fixing initCodeHash
royvardhan d012a78
test pass with universal router integration
royvardhan e172b1a
feat: add findBestProtocol
royvardhan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,243 @@ | ||
| import { ChainConfigService, ChainClientManager, ContractInteractionService } from "@/services" | ||
| import { | ||
| bytes32ToBytes20, | ||
| constructRedeemEscrowRequestBody, | ||
| estimateGasForPost, | ||
| ExecutionResult, | ||
| FillOptions, | ||
| HexString, | ||
| IPostRequest, | ||
| Order, | ||
| } from "hyperbridge-sdk" | ||
| import { FillerStrategy } from "./base" | ||
| import { privateKeyToAccount, privateKeyToAddress } from "viem/accounts" | ||
| import { INTENT_GATEWAY_ABI } from "@/config/abis/IntentGateway" | ||
| import { get1inchExactOutputQuote } from "@/utils" | ||
| import { encodeFunctionData } from "viem" | ||
| import { erc7821Actions } from "viem/experimental" | ||
|
|
||
| export class BasicFiller implements FillerStrategy { | ||
| name = "BasicFiller" | ||
| private privateKey: HexString | ||
| private clientManager: ChainClientManager | ||
| private contractService: ContractInteractionService | ||
| private configService: ChainConfigService | ||
|
|
||
| constructor(privateKey: HexString) { | ||
| this.privateKey = privateKey | ||
| this.configService = new ChainConfigService() | ||
| this.clientManager = new ChainClientManager(privateKey) | ||
| this.contractService = new ContractInteractionService(this.clientManager, privateKey) | ||
| } | ||
|
|
||
| /** | ||
| * Checks the USD value of the filler's balance against the order's USD value | ||
| * @param order The order to check if it can be filled | ||
| * @returns True if the filler has enough balance, false otherwise | ||
| */ | ||
| async canFill(order: Order): Promise<boolean> { | ||
| try { | ||
| const destClient = this.clientManager.getPublicClient(order.destChain) | ||
| const currentBlock = await destClient.getBlockNumber() | ||
| const deadline = BigInt(order.deadline) | ||
|
|
||
| if (deadline < currentBlock) { | ||
| console.debug(`Order expired at block ${deadline}, current block ${currentBlock}`) | ||
| return false | ||
| } | ||
|
|
||
| const isAlreadyFilled = await this.contractService.checkIfOrderFilled(order) | ||
| if (isAlreadyFilled) { | ||
| console.debug(`Order is already filled`) | ||
| return false | ||
| } | ||
|
|
||
| const fillerBalanceUsd = await this.contractService.getFillerBalanceUSD(order, order.destChain) | ||
|
|
||
| // Check if the filler has enough USD value to fill the order | ||
| const { outputUsdValue } = await this.contractService.getTokenUsdValue(order) | ||
|
|
||
| if (fillerBalanceUsd < outputUsdValue) { | ||
| console.debug(`Insufficient USD value for order`) | ||
| return false | ||
| } | ||
|
|
||
| return true | ||
| } catch (error) { | ||
| console.error(`Error in canFill:`, error) | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Calculates the USD value of the order's inputs, outputs, fees and compares | ||
| * what will the filler receive and what will the filler pay | ||
| * @param order The order to calculate the USD value for | ||
| * @returns The profit in USD (BigInt) | ||
| */ | ||
| async calculateProfitability(order: Order): Promise<bigint> { | ||
| try { | ||
| const { fillGas, postGas } = await this.contractService.estimateGasFillPost(order) | ||
|
royvardhan marked this conversation as resolved.
|
||
| const nativeTokenPriceUsd = await this.contractService.getNativeTokenPriceUsd(order) | ||
|
|
||
| const relayerFeeEth = postGas + (postGas * BigInt(200)) / BigInt(10000) | ||
|
|
||
| const protocolFeeUSD = await this.contractService.getProtocolFeeUSD(order, relayerFeeEth) | ||
|
|
||
| const totalGasWei = fillGas + relayerFeeEth | ||
|
|
||
| const gasCostUsd = (totalGasWei * nativeTokenPriceUsd) / BigInt(10 ** 18) | ||
|
|
||
| const totalGasCostUsd = gasCostUsd + protocolFeeUSD | ||
|
|
||
| const { outputUsdValue, inputUsdValue } = await this.contractService.getTokenUsdValue(order) | ||
|
|
||
| const toReceive = outputUsdValue + order.fees | ||
| const toPay = inputUsdValue + totalGasCostUsd | ||
|
|
||
| const profit = toReceive - toPay | ||
|
|
||
| return profit | ||
| } catch (error) { | ||
| console.error(`Error calculating profitability:`, error) | ||
| return BigInt(0) | ||
| } | ||
| } | ||
|
|
||
| async executeOrder(order: Order): Promise<ExecutionResult> { | ||
| try { | ||
| const { destClient, walletClient } = this.clientManager.getClientsForOrder(order) | ||
| const startTime = Date.now() | ||
|
|
||
| const fillerWalletAddress = privateKeyToAddress(this.privateKey) | ||
|
|
||
| const operations: { calls: { to: HexString; data: HexString; value?: bigint }[] }[] = [] | ||
|
|
||
| for (const token of order.outputs) { | ||
| const tokenAddress = bytes32ToBytes20(token.token) | ||
| const tokenBalance = await this.contractService.getTokenBalance( | ||
| tokenAddress, | ||
| fillerWalletAddress, | ||
| order.destChain, | ||
| ) | ||
| if (tokenBalance === BigInt(0)) { | ||
| const chainId = Number(order.destChain.split("-")[1]) | ||
|
|
||
| const swapData = await get1inchExactOutputQuote({ | ||
| chainId, | ||
| srcToken: this.configService.getDaiAsset(order.destChain), | ||
| dstToken: tokenAddress, | ||
| amount: token.amount.toString(), | ||
| fromAddress: fillerWalletAddress, | ||
| slippage: 200, | ||
| isExactOut: true, | ||
| }) | ||
|
|
||
| try { | ||
| // Simulating individual swaps for early debugging | ||
| await destClient.simulateCalls({ | ||
| account: fillerWalletAddress, | ||
| calls: [ | ||
| { | ||
| to: swapData.tx.to as HexString, | ||
| data: swapData.tx.data as HexString, | ||
| value: BigInt(swapData.tx.value || 0), | ||
| }, | ||
| ], | ||
| }) | ||
|
|
||
| operations.push({ | ||
| calls: [ | ||
| { | ||
| to: swapData.tx.to as HexString, | ||
| data: swapData.tx.data as HexString, | ||
| value: BigInt(swapData.tx.value || 0), | ||
| }, | ||
| ], | ||
| }) | ||
| } catch (simulationError) { | ||
| console.error("Swap simulation failed:", simulationError) | ||
| throw new Error("Swap simulation failed") | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const postRequest: IPostRequest = { | ||
| source: order.destChain, | ||
| dest: order.sourceChain, | ||
| body: constructRedeemEscrowRequestBody(order, privateKeyToAddress(this.privateKey)), | ||
| timeoutTimestamp: 0n, | ||
| nonce: await this.contractService.getHostNonce(order.sourceChain), | ||
| from: this.configService.getIntentGatewayAddress(order.sourceChain), | ||
| to: this.configService.getIntentGatewayAddress(order.destChain), | ||
| } | ||
|
|
||
| const postGasEstimate = await estimateGasForPost({ | ||
| postRequest: postRequest, | ||
| sourceClient: this.clientManager.getPublicClient(order.sourceChain) as any, | ||
| hostLatestStateMachineHeight: await this.contractService.getHostLatestStateMachineHeight(), | ||
| hostAddress: this.configService.getHostAddress(order.sourceChain), | ||
| }) | ||
| const fillOptions: FillOptions = { | ||
| relayerFee: postGasEstimate + (postGasEstimate * BigInt(200)) / BigInt(10000), | ||
| } | ||
|
|
||
| await this.contractService.approveTokensIfNeeded(order) | ||
|
|
||
| const fillOrderData = encodeFunctionData({ | ||
| abi: INTENT_GATEWAY_ABI, | ||
| functionName: "fillOrder", | ||
| args: [this.contractService.transformOrderForContract(order), fillOptions as any], | ||
| }) | ||
|
|
||
| operations.push({ | ||
| calls: [ | ||
| { | ||
| to: this.configService.getIntentGatewayAddress(order.destChain), | ||
| data: fillOrderData, | ||
| value: this.contractService.calculateRequiredEthValue(order.outputs), | ||
| }, | ||
| ], | ||
| }) | ||
|
|
||
| try { | ||
| // Simulating all calls together | ||
| await destClient.simulateCalls({ | ||
| account: fillerWalletAddress, | ||
| calls: operations.flatMap((op) => op.calls), | ||
| }) | ||
| } catch (batchSimulationError) { | ||
| console.error("Batch simulation failed:", batchSimulationError) | ||
| throw new Error("Batch simulation failed") | ||
| } | ||
|
|
||
| const tx = await walletClient.extend(erc7821Actions()).executeBatches({ | ||
| address: fillerWalletAddress, | ||
| batches: operations, | ||
| account: privateKeyToAccount(this.privateKey), | ||
| chain: destClient.chain, | ||
| }) | ||
|
|
||
| const endTime = Date.now() | ||
| const processingTimeMs = endTime - startTime | ||
|
|
||
| const receipt = await destClient.waitForTransactionReceipt({ hash: tx }) | ||
|
|
||
| return { | ||
| success: true, | ||
| txHash: receipt.transactionHash, | ||
| gasUsed: receipt.gasUsed.toString(), | ||
| gasPrice: receipt.effectiveGasPrice.toString(), | ||
| confirmedAtBlock: Number(receipt.blockNumber), | ||
| confirmedAt: new Date(), | ||
| strategyUsed: this.name, | ||
| processingTimeMs, | ||
| } | ||
| } catch (error) { | ||
| console.error(`Error executing order:`, error) | ||
| return { | ||
| success: false, | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.