diff --git a/.changeset/gold-socks-dream.md b/.changeset/gold-socks-dream.md new file mode 100644 index 000000000..d20bf094d --- /dev/null +++ b/.changeset/gold-socks-dream.md @@ -0,0 +1,6 @@ +--- +"@rabbitholegg/questdk-plugin-handlefi": minor +"@rabbitholegg/questdk-plugin-registry": minor +--- + +add support for handlefi swap plugin to questdk diff --git a/packages/handlefi/CHANGELOG.md b/packages/handlefi/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/handlefi/README.md b/packages/handlefi/README.md new file mode 100644 index 000000000..7d0796c0e --- /dev/null +++ b/packages/handlefi/README.md @@ -0,0 +1,28 @@ +Limitations + +- amountOut may not be accurate due to some methods using `amountOutMin` which takes into account the amount of slippage set. +- amountOut will not be considered in the swap is routed through the HPSM2 contract. +- trades routed through curve V2 factory may have some false positives. Since there is not a really good way to tell what pool it is without having the contract address passed in as an argument. A false positive can occur if the tokenOut is selected as USDT and USDC.e is traded, and vice-versa. + +# HandleFi Plugin + +## Overview +HandleFi is a decentralised multi currency stablecoin protocol based on Arbitrum. They offer several products including perpetual swaps, convert, staking, and fxTokens. + +### Swap Plugin + +The swap plugin tragets handlefis [convert feature](https://app.handle.fi/convert) which uses a mix of native contracts and external aggregators such as paraswap. + +##### Limitations +- amountOut may not be accurate due to some methods using `amountOutMin` which takes into account the amount of slippage set. +- amountOut will not be considered if the swap is routed through the HPSM2 contract. +- trades routed through curve V2 factory may have some false positives. Since there is not a really good way to tell what pool it is without having the contract address passed in as an argument. A false positive can occur if the tokenOut is selected as USDT and USDC.e is traded, and vice-versa. + +##### Sample Transactions +- [Paraswap](https://dashboard.tenderly.co/tx/arbitrum/0xdc4f726560293b41a0ee72048e2d94970a45a046d88447444dab8bc54cb25a94) +- [V2Router](https://arbiscan.io/tx/0xb4e506b5373ce01c71518e9e0c3fefb87952ad3ab28362b69c083cbe63d56094) +- [hPSM2](https://arbiscan.io/tx/0x8b880dd0805ed4767a9a770149bbe9402d44ae334374f2afef8bb5fd257585a8) +- [routerHpsmHlpCurveV2](https://arbiscan.io/tx/0x819ec6afd60b26412e830feb80b5abe1dab1229fc6ef6a42224e59fb85385d51) +- [routerEthHlpBalancer](https://arbiscan.io/tx/0x58afb2cc0908f7049700bc10bbd144dc12df7baa2ac616abd0f4eefb22012b73) +- [curveFactory (fxUSD_FRAX)](https://arbiscan.io/tx/0x254e3dcea9a376f67340b9141c1c013aea5f1820de629847aa549c29cf4b599c) +- [curveFactory (fxUSD_3pool)](https://arbiscan.io/tx/0xa7f8d500da701b028d037740a5f74a73eaaff7d344526484812e96ef354cae26) \ No newline at end of file diff --git a/packages/handlefi/package.json b/packages/handlefi/package.json new file mode 100644 index 000000000..69ff0aac2 --- /dev/null +++ b/packages/handlefi/package.json @@ -0,0 +1,49 @@ +{ + "name": "@rabbitholegg/questdk-plugin-handlefi", + "version": "1.0.0-alpha.0", + "type": "module", + "exports": { + "require": "./dist/cjs/index.js", + "import": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts" + }, + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "packageManager": "pnpm@8.3.1", + "description": "", + "scripts": { + "bench": "vitest bench", + "bench:ci": "CI=true vitest bench", + "build": "pnpm run clean && pnpm run build:cjs && pnpm run build:esm && pnpm run build:types", + "build:cjs": "tsc --project tsconfig.build.json --module commonjs --outDir ./dist/cjs --removeComments --verbatimModuleSyntax false && echo > ./dist/cjs/package.json '{\"type\":\"commonjs\"}'", + "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir ./dist/esm && echo > ./dist/esm/package.json '{\"type\":\"module\",\"sideEffects\":false}'", + "build:types": "tsc --project tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", + "clean": "rimraf dist", + "format": "rome format . --write", + "lint": "rome check .", + "lint:fix": "pnpm lint --apply", + "test": "vitest dev", + "test:cov": "vitest dev --coverage", + "test:ci": "CI=true vitest --coverage", + "test:ui": "vitest dev --ui" + }, + "keywords": [], + "author": "", + "license": "ISC", + "types": "./dist/types/index.d.ts", + "typings": "./dist/types/index.d.ts", + "devDependencies": { + "@types/node": "^20.8.7", + "@vitest/coverage-v8": "^0.33.0", + "rimraf": "^5.0.5", + "rome": "^12.1.3", + "ts-node": "^10.9.1", + "tsconfig": "workspace:*", + "typescript": "^5.2.2", + "vitest": "^0.33.0" + }, + "dependencies": { + "@rabbitholegg/questdk": "2.0.0-alpha.28", + "viem": "^1.16.6" + } +} diff --git a/packages/handlefi/src/HandleFi.test.ts b/packages/handlefi/src/HandleFi.test.ts new file mode 100644 index 000000000..ad6c877da --- /dev/null +++ b/packages/handlefi/src/HandleFi.test.ts @@ -0,0 +1,236 @@ +import { apply } from '@rabbitholegg/questdk/filter' +import { describe, expect, test } from 'vitest' +import { swap, getSupportedTokenAddresses } from './HandleFi' +import { passingTestCases, failingTestCases } from './test-transactions' +import { ARBITRUM_ONE } from './constants' +import { + PARASWAP_ABI, + V2_ROUTER_ABI, + HPSM2_ABI, + HLP_BALANCER_ABI, + HLP_CURVE_V2_ABI, + CURVE_FACTORY_ABI, +} from './abi' + +describe('Given the handlefi plugin', () => { + describe('When handling the swap action', () => { + describe('should return a valid action filter', () => { + test('when using convert fetaure on handlefi', async () => { + const { params } = passingTestCases[0] + const filter = await swap(params) + expect(filter).to.deep.equal({ + chainId: 42161, + from: '0x865c301c46d64de5c9b124ec1a97ef1efc1bcbd1', + to: { + $or: [ + '0xdef171fe48cf0115b1d80b88dc8eab59176fee57', + '0x434b5245f6fe54d0c9f881d55c2ba27fe7132d89', + '0x0f330a53874cea3e5a0dee5d291c49275fdc3260', + '0x559844b1df66e247f83ba58bc39fa488a1af1093', + '0x9bdc4094860c97d9e5f1c18c4602a4a907d0a916', + '0xab174ffa530c888649c44c4d21c849bbaabc723f', + '0xd0dd5d76cf0fc06dabc48632735566dca241a35e', + ], + }, + input: { + $or: [ + { + $abi: PARASWAP_ABI, + $or: [ + { + data: { + fromToken: '0x912CE59144191C1204E64559FE8253a0e49E6548', + fromAmount: { + $gte: '1000000000000000000', + }, + toAmount: { + $gte: '1110000', + }, + toToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + partner: '0xFa2c1bE677BE4BEc8851D1577B343F7060B51E3A', + }, + }, + { + data: { + fromToken: '0x912CE59144191C1204E64559FE8253a0e49E6548', + fromAmount: { + $gte: '1000000000000000000', + }, + toAmount: { + $gte: '1110000', + }, + path: { + $last: { + to: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + }, + }, + partner: '0xFa2c1bE677BE4BEc8851D1577B343F7060B51E3A', + }, + }, + { + data: { + fromToken: '0x912CE59144191C1204E64559FE8253a0e49E6548', + fromAmount: { + $gte: '1000000000000000000', + }, + toAmount: { + $gte: '1110000', + }, + path: { + $last: { + path: { + $last: { + to: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + }, + }, + }, + }, + partner: '0xFa2c1bE677BE4BEc8851D1577B343F7060B51E3A', + }, + }, + { + data: { + assets: { + $and: [ + { + $first: + '0x912ce59144191c1204e64559fe8253a0e49e6548', + }, + { + $last: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', + }, + ], + }, + fromAmount: { + $gte: '1000000000000000000', + }, + toAmount: { + $gte: '1110000', + }, + partner: '0xFa2c1bE677BE4BEc8851D1577B343F7060B51E3A', + }, + }, + ], + }, + { + $abi: V2_ROUTER_ABI, + _path: { + $and: [ + { + $first: '0x912CE59144191C1204E64559FE8253a0e49E6548', + }, + { + $last: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + }, + ], + }, + _amountIn: { + $gte: '1000000000000000000', + }, + _minOut: { + $gte: '1110000', + }, + _receiver: '0x865c301c46d64de5c9b124ec1a97ef1efc1bcbd1', + }, + { + $abi: HPSM2_ABI, + amount: { + $gte: '1000000000000000000', + }, + fxTokenAddress: { + $or: [ + '0x912CE59144191C1204E64559FE8253a0e49E6548', + '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + ], + }, + peggedTokenAddress: { + $or: [ + '0x912CE59144191C1204E64559FE8253a0e49E6548', + '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + ], + }, + }, + { + $abi: HLP_CURVE_V2_ABI, + tokenOut: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', + amountIn: { + $gte: '1000000000000000000', + }, + minOut: { + $gte: '1110000', + }, + receiver: '0x865c301c46d64de5c9b124ec1a97ef1efc1bcbd1', + $or: [ + { + peggedToken: '0x912CE59144191C1204E64559FE8253a0e49E6548', + }, + { + hlpToken: '0x912CE59144191C1204E64559FE8253a0e49E6548', + }, + ], + }, + { + $abi: HLP_BALANCER_ABI, + minOut: { + $gte: '1110000', + }, + amountIn: { + $gte: '1000000000000000000', + }, + receiver: '0x865c301c46d64de5c9b124ec1a97ef1efc1bcbd1', + tokenIn: '0x912CE59144191C1204E64559FE8253a0e49E6548', + }, + { + $abi: CURVE_FACTORY_ABI, + i: null, + j: null, + _dx: { + $gte: '1000000000000000000', + }, + _min_dy: { + $gte: '1110000', + }, + }, + ], + }, + }) + }) + }) + + describe('should pass filter with valid transactions', () => { + passingTestCases.forEach((testCase) => { + const { transaction, description, params } = testCase + test(description, async () => { + const filter = await swap(params) + expect(apply(transaction, filter)).to.be.true + }) + }) + }) + + describe('should not pass filter with invalid transactions', () => { + failingTestCases.forEach((testCase) => { + const { transaction, description, params } = testCase + test(description, async () => { + const filter = await swap(params) + expect(apply(transaction, filter)).to.be.false + }) + }) + }) + + describe('should return a valid list of tokens for each supported chain', () => { + test(`for chainId: ${ARBITRUM_ONE}`, async () => { + const tokens = await getSupportedTokenAddresses(ARBITRUM_ONE) + const addressRegex = /^0x[a-fA-F0-9]{40}$/ + expect(tokens).to.be.an('array') + expect(tokens).to.have.length.greaterThan(0) + expect(tokens).to.have.length.lessThan(100) + tokens.forEach((token) => { + expect(token).to.match( + addressRegex, + `Token address ${token} is not a valid Ethereum address`, + ) + }) + }) + }) + }) +}) diff --git a/packages/handlefi/src/HandleFi.ts b/packages/handlefi/src/HandleFi.ts new file mode 100644 index 000000000..4a3c4a1bd --- /dev/null +++ b/packages/handlefi/src/HandleFi.ts @@ -0,0 +1,54 @@ +import { + type TransactionFilter, + type SwapActionParams, + compressJson, +} from '@rabbitholegg/questdk' +import { zeroAddress, type Address } from 'viem' +import { + ARBITRUM_ONE, + SWAP_CONTRACTS, + TOKEN_ADDRESSES, + PARASWAP_PARTNER, +} from './constants' +import { + getParaSwapFilter, + getV2RouterFilter, + getHPSM2Filter, + getHlpCurveV2Filter, + getHlpBalancerFilter, + getCurveV2FactoryFilter, +} from './input-filters' + +export const swap = async ( + swap: SwapActionParams, +): Promise => { + const { chainId, tokenIn, amountIn, recipient } = swap + return compressJson({ + chainId, + value: tokenIn === zeroAddress ? amountIn : undefined, + from: recipient, + to: { + $or: SWAP_CONTRACTS.map((address) => address.toLowerCase()), + }, + input: { + $or: [ + getParaSwapFilter(swap, PARASWAP_PARTNER), + getV2RouterFilter(swap), + getHPSM2Filter(swap), + getHlpCurveV2Filter(swap), + getHlpBalancerFilter(swap), + getCurveV2FactoryFilter(swap), + ], + }, + }) +} + +export const getSupportedTokenAddresses = async ( + _chainId: number, +): Promise => { + return _chainId === ARBITRUM_ONE ? TOKEN_ADDRESSES : [] +} + +export const getSupportedChainIds = async (): Promise => { + return [ARBITRUM_ONE] +} diff --git a/packages/handlefi/src/abi.ts b/packages/handlefi/src/abi.ts new file mode 100644 index 000000000..2b3b2c695 --- /dev/null +++ b/packages/handlefi/src/abi.ts @@ -0,0 +1,730 @@ +export const V2_ROUTER_ABI = [ + { + inputs: [ + { internalType: 'address[]', name: '_path', type: 'address[]' }, + { internalType: 'uint256', name: '_amountIn', type: 'uint256' }, + { internalType: 'uint256', name: '_minOut', type: 'uint256' }, + { internalType: 'address', name: '_receiver', type: 'address' }, + { internalType: 'bytes', name: 'signedQuoteData', type: 'bytes' }, + ], + name: 'swap', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address[]', name: '_path', type: 'address[]' }, + { internalType: 'uint256', name: '_minOut', type: 'uint256' }, + { internalType: 'address', name: '_receiver', type: 'address' }, + { internalType: 'bytes', name: 'signedQuoteData', type: 'bytes' }, + ], + name: 'swapETHToTokens', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address[]', name: '_path', type: 'address[]' }, + { internalType: 'uint256', name: '_amountIn', type: 'uint256' }, + { internalType: 'uint256', name: '_minOut', type: 'uint256' }, + { internalType: 'address payable', name: '_receiver', type: 'address' }, + { internalType: 'bytes', name: 'signedQuoteData', type: 'bytes' }, + ], + name: 'swapTokensToETH', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] + +export const PARASWAP_ABI = [ + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'fromToken', type: 'address' }, + { internalType: 'address', name: 'toToken', type: 'address' }, + { internalType: 'uint256', name: 'fromAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'toAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'expectedAmount', type: 'uint256' }, + { internalType: 'address[]', name: 'callees', type: 'address[]' }, + { internalType: 'bytes', name: 'exchangeData', type: 'bytes' }, + { + internalType: 'uint256[]', + name: 'startIndexes', + type: 'uint256[]', + }, + { internalType: 'uint256[]', name: 'values', type: 'uint256[]' }, + { + internalType: 'address payable', + name: 'beneficiary', + type: 'address', + }, + { internalType: 'address payable', name: 'partner', type: 'address' }, + { internalType: 'uint256', name: 'feePercent', type: 'uint256' }, + { internalType: 'bytes', name: 'permit', type: 'bytes' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'bytes16', name: 'uuid', type: 'bytes16' }, + ], + internalType: 'struct Utils.SimpleData', + name: 'data', + type: 'tuple', + }, + ], + name: 'simpleBuy', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'fromToken', type: 'address' }, + { internalType: 'address', name: 'toToken', type: 'address' }, + { internalType: 'uint256', name: 'fromAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'toAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'expectedAmount', type: 'uint256' }, + { internalType: 'address[]', name: 'callees', type: 'address[]' }, + { internalType: 'bytes', name: 'exchangeData', type: 'bytes' }, + { + internalType: 'uint256[]', + name: 'startIndexes', + type: 'uint256[]', + }, + { internalType: 'uint256[]', name: 'values', type: 'uint256[]' }, + { + internalType: 'address payable', + name: 'beneficiary', + type: 'address', + }, + { internalType: 'address payable', name: 'partner', type: 'address' }, + { internalType: 'uint256', name: 'feePercent', type: 'uint256' }, + { internalType: 'bytes', name: 'permit', type: 'bytes' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'bytes16', name: 'uuid', type: 'bytes16' }, + ], + internalType: 'struct Utils.SimpleData', + name: 'data', + type: 'tuple', + }, + ], + name: 'simpleSwap', + outputs: [ + { internalType: 'uint256', name: 'receivedAmount', type: 'uint256' }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'fromToken', type: 'address' }, + { internalType: 'uint256', name: 'fromAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'toAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'expectedAmount', type: 'uint256' }, + { + internalType: 'address payable', + name: 'beneficiary', + type: 'address', + }, + { + components: [ + { + internalType: 'uint256', + name: 'fromAmountPercent', + type: 'uint256', + }, + { + components: [ + { internalType: 'address', name: 'to', type: 'address' }, + { + internalType: 'uint256', + name: 'totalNetworkFee', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address payable', + name: 'adapter', + type: 'address', + }, + { + internalType: 'uint256', + name: 'percent', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'networkFee', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + { + internalType: 'address', + name: 'targetExchange', + type: 'address', + }, + { + internalType: 'uint256', + name: 'percent', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'payload', + type: 'bytes', + }, + { + internalType: 'uint256', + name: 'networkFee', + type: 'uint256', + }, + ], + internalType: 'struct Utils.Route[]', + name: 'route', + type: 'tuple[]', + }, + ], + internalType: 'struct Utils.Adapter[]', + name: 'adapters', + type: 'tuple[]', + }, + ], + internalType: 'struct Utils.Path[]', + name: 'path', + type: 'tuple[]', + }, + ], + internalType: 'struct Utils.MegaSwapPath[]', + name: 'path', + type: 'tuple[]', + }, + { internalType: 'address payable', name: 'partner', type: 'address' }, + { internalType: 'uint256', name: 'feePercent', type: 'uint256' }, + { internalType: 'bytes', name: 'permit', type: 'bytes' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'bytes16', name: 'uuid', type: 'bytes16' }, + ], + internalType: 'struct Utils.MegaSwapSellData', + name: 'data', + type: 'tuple', + }, + ], + name: 'megaSwap', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'fromToken', type: 'address' }, + { internalType: 'uint256', name: 'fromAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'toAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'expectedAmount', type: 'uint256' }, + { + internalType: 'address payable', + name: 'beneficiary', + type: 'address', + }, + { + components: [ + { internalType: 'address', name: 'to', type: 'address' }, + { + internalType: 'uint256', + name: 'totalNetworkFee', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address payable', + name: 'adapter', + type: 'address', + }, + { internalType: 'uint256', name: 'percent', type: 'uint256' }, + { + internalType: 'uint256', + name: 'networkFee', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + { + internalType: 'address', + name: 'targetExchange', + type: 'address', + }, + { + internalType: 'uint256', + name: 'percent', + type: 'uint256', + }, + { internalType: 'bytes', name: 'payload', type: 'bytes' }, + { + internalType: 'uint256', + name: 'networkFee', + type: 'uint256', + }, + ], + internalType: 'struct Utils.Route[]', + name: 'route', + type: 'tuple[]', + }, + ], + internalType: 'struct Utils.Adapter[]', + name: 'adapters', + type: 'tuple[]', + }, + ], + internalType: 'struct Utils.Path[]', + name: 'path', + type: 'tuple[]', + }, + { internalType: 'address payable', name: 'partner', type: 'address' }, + { internalType: 'uint256', name: 'feePercent', type: 'uint256' }, + { internalType: 'bytes', name: 'permit', type: 'bytes' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'bytes16', name: 'uuid', type: 'bytes16' }, + ], + internalType: 'struct Utils.SellData', + name: 'data', + type: 'tuple', + }, + ], + name: 'multiSwap', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: 'bytes32', name: 'poolId', type: 'bytes32' }, + { + internalType: 'uint256', + name: 'assetInIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'assetOutIndex', + type: 'uint256', + }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { internalType: 'bytes', name: 'userData', type: 'bytes' }, + ], + internalType: 'struct IBalancerV2Vault.BatchSwapStep[]', + name: 'swaps', + type: 'tuple[]', + }, + { internalType: 'address[]', name: 'assets', type: 'address[]' }, + { + components: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { + internalType: 'bool', + name: 'fromInternalBalance', + type: 'bool', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + { internalType: 'bool', name: 'toInternalBalance', type: 'bool' }, + ], + internalType: 'struct IBalancerV2Vault.FundManagement', + name: 'funds', + type: 'tuple', + }, + { internalType: 'int256[]', name: 'limits', type: 'int256[]' }, + { internalType: 'uint256', name: 'fromAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'toAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'expectedAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint256', name: 'feePercent', type: 'uint256' }, + { internalType: 'address', name: 'vault', type: 'address' }, + { internalType: 'address payable', name: 'partner', type: 'address' }, + { internalType: 'bool', name: 'isApproved', type: 'bool' }, + { + internalType: 'address payable', + name: 'beneficiary', + type: 'address', + }, + { internalType: 'bytes', name: 'permit', type: 'bytes' }, + { internalType: 'bytes16', name: 'uuid', type: 'bytes16' }, + ], + internalType: 'struct Utils.DirectBalancerV2', + name: 'data', + type: 'tuple', + }, + ], + name: 'directBalancerV2GivenInSwap', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: 'bytes32', name: 'poolId', type: 'bytes32' }, + { + internalType: 'uint256', + name: 'assetInIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'assetOutIndex', + type: 'uint256', + }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { internalType: 'bytes', name: 'userData', type: 'bytes' }, + ], + internalType: 'struct IBalancerV2Vault.BatchSwapStep[]', + name: 'swaps', + type: 'tuple[]', + }, + { internalType: 'address[]', name: 'assets', type: 'address[]' }, + { + components: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { + internalType: 'bool', + name: 'fromInternalBalance', + type: 'bool', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + { internalType: 'bool', name: 'toInternalBalance', type: 'bool' }, + ], + internalType: 'struct IBalancerV2Vault.FundManagement', + name: 'funds', + type: 'tuple', + }, + { internalType: 'int256[]', name: 'limits', type: 'int256[]' }, + { internalType: 'uint256', name: 'fromAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'toAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'expectedAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'uint256', name: 'feePercent', type: 'uint256' }, + { internalType: 'address', name: 'vault', type: 'address' }, + { internalType: 'address payable', name: 'partner', type: 'address' }, + { internalType: 'bool', name: 'isApproved', type: 'bool' }, + { + internalType: 'address payable', + name: 'beneficiary', + type: 'address', + }, + { internalType: 'bytes', name: 'permit', type: 'bytes' }, + { internalType: 'bytes16', name: 'uuid', type: 'bytes16' }, + ], + internalType: 'struct Utils.DirectBalancerV2', + name: 'data', + type: 'tuple', + }, + ], + name: 'directBalancerV2GivenOutSwap', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'fromToken', type: 'address' }, + { internalType: 'address', name: 'toToken', type: 'address' }, + { internalType: 'address', name: 'exchange', type: 'address' }, + { internalType: 'uint256', name: 'fromAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'toAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'expectedAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'feePercent', type: 'uint256' }, + { internalType: 'int128', name: 'i', type: 'int128' }, + { internalType: 'int128', name: 'j', type: 'int128' }, + { internalType: 'address payable', name: 'partner', type: 'address' }, + { internalType: 'bool', name: 'isApproved', type: 'bool' }, + { + internalType: 'enum Utils.CurveSwapType', + name: 'swapType', + type: 'uint8', + }, + { + internalType: 'address payable', + name: 'beneficiary', + type: 'address', + }, + { internalType: 'bool', name: 'needWrapNative', type: 'bool' }, + { internalType: 'bytes', name: 'permit', type: 'bytes' }, + { internalType: 'bytes16', name: 'uuid', type: 'bytes16' }, + ], + internalType: 'struct Utils.DirectCurveV1', + name: 'data', + type: 'tuple', + }, + ], + name: 'directCurveV1Swap', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'fromToken', type: 'address' }, + { internalType: 'address', name: 'toToken', type: 'address' }, + { internalType: 'address', name: 'exchange', type: 'address' }, + { internalType: 'address', name: 'poolAddress', type: 'address' }, + { internalType: 'uint256', name: 'fromAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'toAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'expectedAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'feePercent', type: 'uint256' }, + { internalType: 'uint256', name: 'i', type: 'uint256' }, + { internalType: 'uint256', name: 'j', type: 'uint256' }, + { internalType: 'address payable', name: 'partner', type: 'address' }, + { internalType: 'bool', name: 'isApproved', type: 'bool' }, + { + internalType: 'enum Utils.CurveSwapType', + name: 'swapType', + type: 'uint8', + }, + { + internalType: 'address payable', + name: 'beneficiary', + type: 'address', + }, + { internalType: 'bool', name: 'needWrapNative', type: 'bool' }, + { internalType: 'bytes', name: 'permit', type: 'bytes' }, + { internalType: 'bytes16', name: 'uuid', type: 'bytes16' }, + ], + internalType: 'struct Utils.DirectCurveV2', + name: 'data', + type: 'tuple', + }, + ], + name: 'directCurveV2Swap', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'fromToken', type: 'address' }, + { internalType: 'address', name: 'toToken', type: 'address' }, + { internalType: 'address', name: 'exchange', type: 'address' }, + { internalType: 'uint256', name: 'fromAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'toAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'expectedAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'feePercent', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'address payable', name: 'partner', type: 'address' }, + { internalType: 'bool', name: 'isApproved', type: 'bool' }, + { + internalType: 'address payable', + name: 'beneficiary', + type: 'address', + }, + { internalType: 'bytes', name: 'path', type: 'bytes' }, + { internalType: 'bytes', name: 'permit', type: 'bytes' }, + { internalType: 'bytes16', name: 'uuid', type: 'bytes16' }, + ], + internalType: 'struct Utils.DirectUniV3', + name: 'data', + type: 'tuple', + }, + ], + name: 'directUniV3Buy', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'fromToken', type: 'address' }, + { internalType: 'address', name: 'toToken', type: 'address' }, + { internalType: 'address', name: 'exchange', type: 'address' }, + { internalType: 'uint256', name: 'fromAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'toAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'expectedAmount', type: 'uint256' }, + { internalType: 'uint256', name: 'feePercent', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline', type: 'uint256' }, + { internalType: 'address payable', name: 'partner', type: 'address' }, + { internalType: 'bool', name: 'isApproved', type: 'bool' }, + { + internalType: 'address payable', + name: 'beneficiary', + type: 'address', + }, + { internalType: 'bytes', name: 'path', type: 'bytes' }, + { internalType: 'bytes', name: 'permit', type: 'bytes' }, + { internalType: 'bytes16', name: 'uuid', type: 'bytes16' }, + ], + internalType: 'struct Utils.DirectUniV3', + name: 'data', + type: 'tuple', + }, + ], + name: 'directUniV3Swap', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, +] + +export const HPSM2_ABI = [ + { + inputs: [ + { internalType: 'address', name: 'fxTokenAddress', type: 'address' }, + { internalType: 'address', name: 'peggedTokenAddress', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'deposit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'fxTokenAddress', type: 'address' }, + { internalType: 'address', name: 'peggedTokenAddress', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'withdraw', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] + +export const HLP_CURVE_V2_ABI = [ + { + inputs: [ + { internalType: 'address', name: 'hlpCurveToken', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'address', name: 'receiver', type: 'address' }, + { internalType: 'uint256', name: 'minOut', type: 'uint256' }, + { internalType: 'address', name: 'metapoolFactory', type: 'address' }, + { internalType: 'address', name: 'pool', type: 'address' }, + { internalType: 'bytes', name: 'signedQuoteData', type: 'bytes' }, + ], + name: 'swapEthToCurveToken', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'hlpToken', type: 'address' }, + { internalType: 'address', name: 'hlpCurveToken', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'address', name: 'receiver', type: 'address' }, + { internalType: 'uint256', name: 'minOut', type: 'uint256' }, + { internalType: 'address', name: 'metapoolFactory', type: 'address' }, + { internalType: 'address', name: 'pool', type: 'address' }, + { internalType: 'bytes', name: 'signedQuoteData', type: 'bytes' }, + ], + name: 'swapHlpTokenToCurveToken', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'peggedToken', type: 'address' }, + { internalType: 'address', name: 'fxToken', type: 'address' }, + { internalType: 'address', name: 'hlpToken', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'address', name: 'receiver', type: 'address' }, + { internalType: 'uint256', name: 'minOut', type: 'uint256' }, + { internalType: 'address', name: 'metapoolFactory', type: 'address' }, + { internalType: 'address', name: 'pool', type: 'address' }, + { internalType: 'bytes', name: 'signedQuoteData', type: 'bytes' }, + ], + name: 'swapPeggedTokenToCurveToken', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] + +export const HLP_BALANCER_ABI = [ + { + inputs: [ + { internalType: 'address', name: 'tokenIn', type: 'address' }, + { internalType: 'address', name: 'hlpBalancerToken', type: 'address' }, + { internalType: 'bytes32', name: 'poolId', type: 'bytes32' }, + { internalType: 'uint256', name: 'amountIn', type: 'uint256' }, + { internalType: 'uint256', name: 'minOut', type: 'uint256' }, + { internalType: 'address', name: 'receiver', type: 'address' }, + { internalType: 'bytes', name: 'signedQuoteData', type: 'bytes' }, + ], + name: 'swapBalancerToEth', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'hlpBalancerToken', type: 'address' }, + { internalType: 'address', name: 'tokenOut', type: 'address' }, + { internalType: 'bytes32', name: 'poolId', type: 'bytes32' }, + { internalType: 'uint256', name: 'minOut', type: 'uint256' }, + { internalType: 'address', name: 'receiver', type: 'address' }, + { internalType: 'bytes', name: 'signedQuoteData', type: 'bytes' }, + ], + name: 'swapEthToBalancer', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, +] + +export const CURVE_FACTORY_ABI = [ + { + stateMutability: 'nonpayable', + type: 'function', + name: 'exchange_underlying', + inputs: [ + { name: 'i', type: 'int128' }, + { name: 'j', type: 'int128' }, + { name: '_dx', type: 'uint256' }, + { name: '_min_dy', type: 'uint256' }, + ], + outputs: [{ name: '', type: 'uint256' }], + gas: 1323223, + }, +] diff --git a/packages/handlefi/src/constants.ts b/packages/handlefi/src/constants.ts new file mode 100644 index 000000000..5087b9a16 --- /dev/null +++ b/packages/handlefi/src/constants.ts @@ -0,0 +1,35 @@ +import type { Address } from 'viem' + +// Chains +export const ARBITRUM_ONE = 42161 + +// Swap Contract Addresses +const AUGUSTUS_SWAPPER = '0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57' +const V2_ROUTER = '0x434b5245f6Fe54D0C9F881d55c2Ba27fe7132d89' +const HPSM2_CONTRACT = '0x0f330a53874cea3e5a0dee5d291c49275fdc3260' +const HLP_CURVE_CONTRACT = '0x559844b1Df66e247F83Ba58bc39fa488A1AF1093' +const HLP_BALANCER_CONTRACT = '0x9bDc4094860C97d9e5f1C18C4602a4a907d0a916' +const HANDLE_FRAX_BP = '0xab174ffa530c888649c44c4d21c849bbaabc723f' +const HANDLE_3POOL = '0xd0dd5d76cf0fc06dabc48632735566dca241a35e' + +export const SWAP_CONTRACTS = [ + AUGUSTUS_SWAPPER, + V2_ROUTER, + HPSM2_CONTRACT, + HLP_CURVE_CONTRACT, + HLP_BALANCER_CONTRACT, + HANDLE_FRAX_BP, + HANDLE_3POOL, +] + +// Token Addresses +export const WETH = '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1' +const FX_USD = '0x8616E8EA83f048ab9A5eC513c9412Dd2993bcE3F' +const FOREX = '0xdb298285fe4c5410b05390ca80e8fbe9de1f259b' +const ARB = '0x912CE59144191C1204E64559FE8253a0e49E6548' +const USDC = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' + +export const TOKEN_ADDRESSES = [FX_USD, FOREX, ARB, USDC, WETH] as Address[] + +// Paraswap Partner Address +export const PARASWAP_PARTNER = '0xFa2c1bE677BE4BEc8851D1577B343F7060B51E3A' diff --git a/packages/handlefi/src/index.ts b/packages/handlefi/src/index.ts new file mode 100644 index 000000000..5825c8560 --- /dev/null +++ b/packages/handlefi/src/index.ts @@ -0,0 +1,19 @@ +import { + type IActionPlugin, + PluginActionNotImplementedError, +} from '@rabbitholegg/questdk' + +import { + swap, + getSupportedChainIds, + getSupportedTokenAddresses, +} from './HandleFi.js' + +export const HandleFi: IActionPlugin = { + pluginId: 'handlefi', + getSupportedTokenAddresses, + getSupportedChainIds, + swap, + bridge: async () => new PluginActionNotImplementedError(), + mint: async () => new PluginActionNotImplementedError(), +} diff --git a/packages/handlefi/src/input-filters.ts b/packages/handlefi/src/input-filters.ts new file mode 100644 index 000000000..eda8ab489 --- /dev/null +++ b/packages/handlefi/src/input-filters.ts @@ -0,0 +1,193 @@ +import { type SwapActionParams } from '@rabbitholegg/questdk' +import { getAddress, zeroAddress, type Address } from 'viem' +import { buildV2PathQueryWithCase } from './utils' +import { + PARASWAP_ABI, + V2_ROUTER_ABI, + HPSM2_ABI, + HLP_CURVE_V2_ABI, + HLP_BALANCER_ABI, + CURVE_FACTORY_ABI, +} from './abi' +import { WETH } from './constants' + +export function getParaSwapFilter( + params: SwapActionParams, + partner: Address | undefined, +) { + const { tokenIn, tokenOut, amountIn, amountOut } = params + const internalEthAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' + const tokenInUsed = tokenIn === zeroAddress ? internalEthAddress : tokenIn + const tokenOutUsed = tokenOut === zeroAddress ? internalEthAddress : tokenOut + + return { + $abi: PARASWAP_ABI, + $or: [ + { + // simpleswap, directUniV3Swap, directCurveSwap + data: { + fromToken: tokenInUsed, + fromAmount: amountIn, + toAmount: amountOut, + toToken: tokenOutUsed, + partner, + }, + }, + { + // multiswap + data: { + fromToken: tokenInUsed, + fromAmount: amountIn, + toAmount: amountOut, + path: { + $last: { + to: tokenOutUsed, + }, + }, + partner, + }, + }, + { + // megaswap + data: { + fromToken: tokenInUsed, + fromAmount: amountIn, + toAmount: amountOut, + path: { + $last: { + path: { + $last: { + to: tokenOutUsed, + }, + }, + }, + }, + partner, + }, + }, + { + // directBalancerV2 + data: { + assets: buildV2PathQueryWithCase('lower', tokenIn, tokenOut), + fromAmount: amountIn, + toAmount: amountOut, + partner, + }, + }, + ], + } as const +} + +export function getV2RouterFilter(params: SwapActionParams) { + const { tokenIn, tokenOut, amountIn, amountOut, recipient } = params + const ethIn = tokenIn === zeroAddress + const tokenInUsed = tokenIn === zeroAddress ? WETH : tokenIn + const tokenOutUsed = tokenOut === zeroAddress ? WETH : tokenOut + return { + $abi: V2_ROUTER_ABI, + _path: buildV2PathQueryWithCase('checksum', tokenInUsed, tokenOutUsed), + _amountIn: ethIn ? undefined : amountIn, + _minOut: amountOut, + _receiver: recipient, + } as const +} + +export function getHPSM2Filter(params: SwapActionParams) { + // amount is only for amountIn + const { tokenIn, tokenOut, amountIn } = params + const tokenInAddress = tokenIn ? getAddress(tokenIn) : undefined + const tokenOutAddress = tokenOut ? getAddress(tokenOut) : undefined + + let inputs = {} + + if (tokenIn && tokenOut) { + inputs = { + fxTokenAddress: { $or: [tokenInAddress, tokenOutAddress] }, + peggedTokenAddress: { $or: [tokenInAddress, tokenOutAddress] }, + } + } else if (tokenIn || tokenOut) { + const address = tokenInAddress || tokenOutAddress + inputs = { + $or: [{ fxTokenAddress: address }, { peggedTokenAddress: address }], + } + } + + return { + $abi: HPSM2_ABI, + amount: amountIn, + ...inputs, + } as const +} + +export function getHlpCurveV2Filter(params: SwapActionParams) { + const { tokenIn, tokenOut, amountIn, amountOut, recipient } = params + const minOut = tokenOut ? amountOut : undefined + if (tokenIn === zeroAddress) { + return { + $abi: HLP_CURVE_V2_ABI, + tokenOut, + minOut, + receiver: recipient, + } + } + return { + $abi: HLP_CURVE_V2_ABI, + tokenOut, + amountIn: tokenIn ? amountIn : undefined, + minOut, + receiver: recipient, + $or: [ + { + peggedToken: tokenIn, + }, + { + hlpToken: tokenIn, + }, + ], + } +} + +export function getHlpBalancerFilter(params: SwapActionParams) { + const { tokenIn, tokenOut, amountIn, amountOut, recipient } = params + const minOut = tokenOut ? amountOut : undefined + const ethUsedIn = tokenIn === zeroAddress + const tokenInput = ethUsedIn ? { tokenOut } : { tokenIn } + return { + $abi: HLP_BALANCER_ABI, + minOut, + amountIn: tokenIn && !ethUsedIn ? amountIn : undefined, + receiver: recipient, + ...tokenInput, + } +} + +export function getCurveV2FactoryFilter(params: SwapActionParams) { + const { tokenIn, tokenOut, amountIn, amountOut } = params + + // There isnt really a good way to tell which pool you are in without knowing the contract address of the pool + // i will always be fxUSD (0) + // j can either be FRAX, USDC.e, or USDT (may get false posiitves here) + + const i = !tokenIn + ? undefined + : tokenIn.toLowerCase() === '0x8616e8ea83f048ab9a5ec513c9412dd2993bce3f' + ? 0 // fxUSD + : null + + const j = !tokenOut + ? undefined + : tokenOut.toLowerCase() === '0x17fc002b466eec40dae837fc4be5c67993ddbd6f' + ? 1 // FRAX + : tokenOut.toLowerCase() === '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8' || + tokenOut.toLowerCase() === '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9' + ? 2 // USDC.e || USDT + : null + + return { + $abi: CURVE_FACTORY_ABI, + i, + j, + _dx: amountIn, + _min_dy: amountOut, + } +} diff --git a/packages/handlefi/src/test-transactions.ts b/packages/handlefi/src/test-transactions.ts new file mode 100644 index 000000000..354c1bbf6 --- /dev/null +++ b/packages/handlefi/src/test-transactions.ts @@ -0,0 +1,989 @@ +import { + type SwapActionParams, + GreaterThanOrEqual, +} from '@rabbitholegg/questdk' +import { + parseEther, + parseUnits, + zeroAddress, + getAddress, + type Address, +} from 'viem' +import { createTestCase, type TestParams } from './utils' +import { ARBITRUM_ONE } from './constants' + +// Paraswap Test Txs +const PARASWAP_SIMPLESWAP: TestParams = { + transaction: { + chainId: 42161, + from: '0x865c301c46d64de5c9b124ec1a97ef1efc1bcbd1', + to: '0xdef171fe48cf0115b1d80b88dc8eab59176fee57', + hash: '0xfe3d97d0132b73fa9cfcb4ea4fe7eafdc70bae19a1912b14a46356420866f7e9', + input: + '0x54e3f31b0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000912ce59144191c1204e64559fe8253a0e49e6548000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000e3ea36e8f91d5530000000000000000000000000000000000000000000000000000000000110a310000000000000000000000000000000000000000000000000000000000112d4f00000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000865c301c46d64de5c9b124ec1a97ef1efc1bcbd1000000000000000000000000fa2c1be677be4bec8851d1577b343f7060b51e3a010000000000000000000000000000000000000000000000000000000000401e00000000000000000000000000000000000000000000000000000000000004600000000000000000000000000000000000000000000000000000000065842857e5365097d06f420e915f1f8fb8777c3e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000912ce59144191c1204e64559fe8253a0e49e6548000000000000000000000000eff23b4be1091b53205e35f3afcd9c7182bf30620000000000000000000000000000000000000000000000000000000000000108a9059cbb000000000000000000000000eff23b4be1091b53205e35f3afcd9c7182bf30620000000000000000000000000000000000000000000000000e3ea36e8f91d5537dc20382000000000000000000000000912ce59144191c1204e64559fe8253a0e49e6548000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000000000000000000000000000000e3ea36e8f91d5530000000000000000000000000000000000000000000000000000000000000001000000000000000000000000def171fe48cf0115b1d80b88dc8eab59176fee57000000000000000000000000d5b927956057075377263aab7f8afc12f85100db00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + value: '0', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: '0x912CE59144191C1204E64559FE8253a0e49E6548', // ARB + tokenOut: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC + amountIn: GreaterThanOrEqual(parseUnits('1', 18)), + amountOut: GreaterThanOrEqual(parseUnits('1.11', 6)), + recipient: '0x865c301c46d64de5c9b124ec1a97ef1efc1bcbd1', + }, +} + +const PARASWAP_MULTISWAP: TestParams = { + transaction: { + chainId: 42161, + from: '0x865c301c46d64de5c9b124ec1a97ef1efc1bcbd1', + to: '0xdef171fe48cf0115b1d80b88dc8eab59176fee57', + hash: '0x4345c0080add9f7e06db72efc82b5dafc045618d9b8369be68586401f4fc7911', + input: + '0xa94e78ef0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000539bde0d7dbd336b79148aa742883198bbf6034200000000000000000000000000000000000000000000000029a2241af62c0000000000000000000000000000000000000000000000000000239da6ab876c8d5300000000000000000000000000000000000000000000000023e70aa1250b1880000000000000000000000000865c301c46d64de5c9b124ec1a97ef1efc1bcbd10000000000000000000000000000000000000000000000000000000000000160000000000000000000000000fa2c1be677be4bec8851d1577b343f7060b51e3a010000000000000000000000000000000000000000000000000000000000401e0000000000000000000000000000000000000000000000000000000000000740000000000000000000000000000000000000000000000000000000006584240629e09fed404b4c03ad7c8d37697b223800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000320000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000369a2fdb910d432f0a07381a5e3d27572c876713000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000658d0a25000000000000000000000000000000000000000000000000000000000000002b539bde0d7dbd336b79148aa742883198bbf60342002710ff970a61a04b1ca14834a43f5de4533ebddb5cc80000000000000000000000000000000000000000000000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000369a2fdb910d432f0a07381a5e3d27572c876713000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006000000000000000000000000d0dd5d76cf0fc06dabc48632735566dca241a35e000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000', + value: '0', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: '0x539bde0d7dbd336b79148aa742883198bbf60342', // MAGIC + tokenOut: '0x8616e8ea83f048ab9a5ec513c9412dd2993bce3f', // fxUSD + amountIn: GreaterThanOrEqual(parseUnits('3', 18)), + amountOut: GreaterThanOrEqual(parseUnits('2.51', 18)), + recipient: '0x865c301c46d64de5c9b124ec1a97ef1efc1bcbd1', + }, +} + +const PARASWAP_UNI_V3: TestParams = { + transaction: { + chainId: 42161, + from: '0x865c301c46d64de5c9b124ec1a97ef1efc1bcbd1', + to: '0xdef171fe48cf0115b1d80b88dc8eab59176fee57', + hash: '0xdc4f726560293b41a0ee72048e2d94970a45a046d88447444dab8bc54cb25a94', + input: + '0xa6886da90000000000000000000000000000000000000000000000000000000000000020000000000000000000000000539bde0d7dbd336b79148aa742883198bbf60342000000000000000000000000912ce59144191c1204e64559fe8253a0e49e6548000000000000000000000000e592427a0aece92de3edee1f18e0157c058615640000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000a90764f1672a2ca0000000000000000000000000000000000000000000000000aa63b29581ed266010000000000000000000000000000000000000000000000000000000000401e00000000000000000000000000000000000000000000000000000000658404ab000000000000000000000000fa2c1be677be4bec8851d1577b343f7060b51e3a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000865c301c46d64de5c9b124ec1a97ef1efc1bcbd100000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000220e2986323ad904dbf827dc23846a7149700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b539bde0d7dbd336b79148aa742883198bbf60342002710912ce59144191c1204e64559fe8253a0e49e65480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + value: '0', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: '0x539bde0d7dbd336b79148aa742883198bbf60342', // MAGIC + tokenOut: '0x912ce59144191c1204e64559fe8253a0e49e6548', // ARB + amountIn: GreaterThanOrEqual(parseUnits('1', 18)), + amountOut: GreaterThanOrEqual(parseUnits('0.74', 18)), + recipient: '0x865c301c46d64de5c9b124ec1a97ef1efc1bcbd1', + }, +} + +const PARASWAP_FAIL: TestParams = { + transaction: { + chainId: 42161, + from: '0x9a1385dded2c1c00b8ac11e6597e86f3879a3403', + to: '0xdef171fe48cf0115b1d80b88dc8eab59176fee57', + hash: '0x87724762847aa87653a3ee3b06ea0ea2ea2c0bf5a08012d9fc005fdc9241b7d1', + input: + '0x54e3f31b0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000d77b108d4f6cefaa0cae9506a934e825becca46e000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc800000000000000000000000000000000000000000000019e1ae5ee6e872c7e3800000000000000000000000000000000000000000000000000000000387c2a870000000000000000000000000000000000000000000000000000000038c4d48d00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003e00000000000000000000000009a1385dded2c1c00b8ac11e6597e86f3879a3403000000000000000000000000353d2d14bb674892910685520ac040f560ccbc0601000000000000000000000000000000000000000000000000000000000313880000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000006583dc2b4c450c4adba14732a0cf0aa7d765f4b90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000001f721e2e82f6676fce4ea07a5958cf098d339e180000000000000000000000000000000000000000000000000000000000000124c04b8d59000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000def171fe48cf0115b1d80b88dc8eab59176fee5700000000000000000000000000000000000000000000000000000000658d11fb00000000000000000000000000000000000000000000019e1ae5ee6e872c7e3800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000028d77b108d4f6cefaa0cae9506a934e825becca46eff970a61a04b1ca14834a43f5de4533ebddb5cc800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000124000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + value: '0', + }, + params: { + chainId: ARBITRUM_ONE, + }, +} + +// V2 router +const V2_ROUTER_ETH_TOKENS: TestParams = { + transaction: { + chainId: 42161, + from: '0x865c301c46d64de5c9b124ec1a97ef1efc1bcbd1', + to: '0x434b5245f6fe54d0c9f881d55c2ba27fe7132d89', + hash: '0x0e01ef19061c3e16bfa91f48c9120ae408ac8014015f714a80e73b9ae7a61429', + input: + '0x481e17e300000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000001ec9ef3426a30660000000000000000000000000865c301c46d64de5c9b124ec1a97ef1efc1bcbd100000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab10000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f0000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab10000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000033fe26fca00000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006584848d000000000000000000000000000000000000000000000000000000006584848d0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006584846f000000000000000000000000000000000000000000000000000000006584846f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000027600000000000000000000000000000000000000000000000000000000000002760000000000000000000000000000000000000000000000000000000000000082423b14e6f1a36dc93726f5b2878680533e8f946a794d588287ecb8b923ff8ec35f25548c1c04a9320609350c0f49a3529cf3593d9189e5c3c9817f9b98cc58781c2afb0ef760759ceb8c931dbb1f5530ceea061db5c053da826fd7470386eaf85b35dcbee6a27cca94f9d5891317bd625a92221973fd79f06346b21874d6b3a5391b000000000000000000000000000000000000000000000000000000000000', + value: '1000000000000000', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: zeroAddress, // ETH + tokenOut: '0x8616e8ea83f048ab9a5ec513c9412dd2993bce3f', // fxUSD + amountIn: GreaterThanOrEqual(parseEther('0.001')), + amountOut: GreaterThanOrEqual(parseUnits('2.2185', 18)), + recipient: '0x865c301c46d64de5c9b124ec1a97ef1efc1bcbd1', + }, +} + +const V2_ROUTER_TOKENS_ETH: TestParams = { + transaction: { + chainId: 42161, + from: '0x4806032267387c9e6aab509b497a765d0aca7f61', + to: '0x434b5245f6fe54d0c9f881d55c2ba27fe7132d89', + hash: '0x506705b15f164d17d8a14c4a7504643c4d3790ed61d25de39aef759502ebbbe5', + input: + '0xcccc6e0500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000064d7deb5b8f2ecaa000000000000000000000000000000000000000000000000000cb289cf449be90000000000000000000000004806032267387c9e6aab509b497a765d0aca7f6100000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000116172b2482c5dc3e6f445c16ac13367ac3fcd3500000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab10000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000116172b2482c5dc3e6f445c16ac13367ac3fcd3500000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000068e296000000000000000000000000000000000000000000000000000000033b29150ff000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000658496f700000000000000000000000000000000000000000000000000000000658496f7000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000658496d900000000000000000000000000000000000000000000000000000000658496d900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000276000000000000000000000000000000000000000000000000000000000000027600000000000000000000000000000000000000000000000000000000000000827b0b81d2992c30d41e44cb7d3c48cbc682e52f739acc06217340cd7c6dd7435f0298b07426b238ed56bbfe6515b3b4a74e88c49e82411af83c71f8602c8441ad1b7d48ebbc834a4caec4a123f65296ed2899d3714c541ec4b7948692001f31b83804576d033737b13423174d19a75e262095d956ad0c4e32a55f52110481dace381c000000000000000000000000000000000000000000000000000000000000', + value: '0', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: '0x116172b2482c5dc3e6f445c16ac13367ac3fcd35', // fxEUR + tokenOut: zeroAddress, // ETH + amountIn: GreaterThanOrEqual(parseUnits('7.26', 18)), + amountOut: GreaterThanOrEqual(parseEther('0.0035')), + recipient: '0x4806032267387c9e6aab509b497a765d0aca7f61', + }, +} + +// HPSM2 +const HPSM2_WITHDRAW: TestParams = { + transaction: { + chainId: 42161, + from: '0x33e2bd5957c0236e88d750b12bbf32bfb8bb92fb', + to: '0x0f330a53874cea3e5a0dee5d291c49275fdc3260', + hash: '0x0a720d80933a2110a07be10186b4ab55db12bdda4a100b282f455bf07eb580a0', + input: + '0xd9caed120000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc80000000000000000000000000000000000000000000000692dcf0f3ed5fc5825', + value: '0', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: '0x8616e8ea83f048ab9a5ec513c9412dd2993bce3f', // fxUSD + tokenOut: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', // USDC.e + amountIn: GreaterThanOrEqual(parseUnits('1940', 18)), + recipient: '0x33e2bd5957c0236e88d750b12bbf32bfb8bb92fb', + }, +} + +const HPSM2_DEPOSIT: TestParams = { + transaction: { + chainId: 42161, + from: '0x0ffad609d35c4bef104ee245a9c4c891d463aa2a', + to: '0x0f330a53874cea3e5a0dee5d291c49275fdc3260', + hash: '0x8b880dd0805ed4767a9a770149bbe9402d44ae334374f2afef8bb5fd257585a8', + input: + '0x8340f5490000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc8000000000000000000000000000000000000000000000000000000003b9aca00', + value: '0', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', // USDC.e + tokenOut: '0x8616e8ea83f048ab9a5ec513c9412dd2993bce3f', // fxUSD + amountIn: GreaterThanOrEqual(parseUnits('1000', 6)), + recipient: '0x0ffad609d35c4bef104ee245a9c4c891d463aa2a', + }, +} + +// HlpCurveV2 +const HLP_CURVE_V2_ETH_TO_CURVE: TestParams = { + transaction: { + chainId: 42161, + from: '0x29d7e0c5839715a2fe6670a248f471427104b266', + to: '0x559844b1df66e247f83ba58bc39fa488a1af1093', + hash: '0xd3ca60df5b415c5e4382b0fabc05197b1ccf31aa24f778bb81264f0cbfc96790', + input: + '0xfb6174e20000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc800000000000000000000000029d7e0c5839715a2fe6670a248f471427104b26600000000000000000000000000000000000000000000000000000000014b9790000000000000000000000000b17b674d9c5cb2e441f8e196a2f048a81355d031000000000000000000000000ab174ffa530c888649c44c4d21c849bbaabc723f00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab10000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000338cb982e00000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000656d4bc200000000000000000000000000000000000000000000000000000000656d4bc2000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000656d4ba400000000000000000000000000000000000000000000000000000000656d4ba4000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000002760000000000000000000000000000000000000000000000000000000000000276000000000000000000000000000000000000000000000000000000000000008273932a003b1c7c539140675f2cca9827f1262c2e8b1de35846c643d1d4fd16b61fe38bfa71bf9247236095e397ccc048bbd58742dbbb8938900838c2c8625a511b6593f88a28d529a0c8da7054834fa595c083229e3575d10053c6127e3b29b690483a539636906c62f263a91d3ff9860a314eb478b27c3268933a40aff2680ef81b000000000000000000000000000000000000000000000000000000000000', + value: '10000000000000000', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: zeroAddress, // ETH + tokenOut: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', // USDC.e + amountIn: GreaterThanOrEqual(parseEther('0.01')), + amountOut: GreaterThanOrEqual(parseUnits('20', 6)), + recipient: '0x29d7e0c5839715a2fe6670a248f471427104b266', + }, +} + +const HLP_CURVE_V2_PEGGED_TO_CURVE: TestParams = { + transaction: { + chainId: 42161, + from: '0x29d7e0c5839715a2fe6670a248f471427104b266', + to: '0x559844b1df66e247f83ba58bc39fa488a1af1093', + hash: '0x3fa25c7d7a069511570978ab80a33efd07d5f5436b593650cb547709e42ab483', + input: + '0x6b60bba8000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f0000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc800000000000000000000000000000000000000000000000000000000004c4b4000000000000000000000000029d7e0c5839715a2fe6670a248f471427104b26600000000000000000000000000000000000000000000000000000000004b06f5000000000000000000000000b17b674d9c5cb2e441f8e196a2f048a81355d031000000000000000000000000ab174ffa530c888649c44c4d21c849bbaabc723f000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000006567b9010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000006567b8e300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000276000000000000000000000000000000000000000000000000000000000000004133eb56c191a220daa863c652d0ccec844c7211e858682671eb448209d3eabf9d334f75f7faf09134d9802b9ebc30fec937e885a215597dd12e0cec50733cde461b00000000000000000000000000000000000000000000000000000000000000', + value: '0', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', // USDC + tokenOut: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', // USDC.e + amountIn: GreaterThanOrEqual(parseUnits('5', 6)), + amountOut: GreaterThanOrEqual(parseUnits('4.91', 6)), + recipient: '0x29d7e0c5839715a2fe6670a248f471427104b266', + }, +} + +const HLP_CURVE_V2_TOKEN_TO_CURVE: TestParams = { + transaction: { + chainId: 42161, + from: '0x49908e05de9e1d559499b08042d1123a1daae6b4', + to: '0x559844b1df66e247f83ba58bc39fa488a1af1093', + hash: '0x819ec6afd60b26412e830feb80b5abe1dab1229fc6ef6a42224e59fb85385d51', + input: + '0xaa2a4e99000000000000000000000000398b09b68aec6c58e28ade6147dac2ecc67897370000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc8000000000000000000000000000000000000000000000000b7e85e4a813dad6900000000000000000000000049908e05de9e1d559499b08042d1123a1daae6b4000000000000000000000000000000000000000000000000000000000091beb1000000000000000000000000b17b674d9c5cb2e441f8e196a2f048a81355d031000000000000000000000000ab174ffa530c888649c44c4d21c849bbaabc723f00000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000398b09b68aec6c58e28ade6147dac2ecc67897370000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000462c3270000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006576db75000000000000000000000000000000000000000000000000000000006576db750000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006576db57000000000000000000000000000000000000000000000000000000006576db57000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000002760000000000000000000000000000000000000000000000000000000000000276000000000000000000000000000000000000000000000000000000000000008262a5a161621679746f538a2b7e13363697e5e2726eb9a46b7947e92a4b92d532779302cae8526271f6ea42384bc4aa34982060913b3fd20d9fd3d88dbadb84c91b898ee47331d840ba6d8db65df6ff0491c78b4e75b6a5a10fcdfff3a1249786fd2ad393afaf66cd151dd5e452592e72bab63a6bfd1ab4c0f2a0eb29da94db6b321c000000000000000000000000000000000000000000000000000000000000', + value: '0', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: '0x398b09b68aec6c58e28ade6147dac2ecc6789737', // fxCAD + tokenOut: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', // USDC.e + amountIn: GreaterThanOrEqual(parseUnits('13.25', 18)), + amountOut: GreaterThanOrEqual(parseUnits('9.5', 6)), + recipient: '0x49908e05de9e1d559499b08042d1123a1daae6b4', + }, +} + +const HLP_BALANCER_TOKENS_ETH: TestParams = { + transaction: { + chainId: 42161, + from: '0xba216e52be5a93e8ba380a761811f3802fa6ed89', + to: '0x9bdc4094860c97d9e5f1c18c4602a4a907d0a916', + hash: '0xe497a8357fb2e62f26da4d01c8ca9570c2613cd55ff3cb89b37685d10aaa8f8e', + input: + '0x8775b592000000000000000000000000db298285fe4c5410b05390ca80e8fbe9de1f259b0000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f4f14d06cb1661ce1dc2a2f26a10a7cd94393b29c00020000000000000000009700000000000000000000000000000000000000000000003863504448e6bdb6d7000000000000000000000000000000000000000000000000001385b35810f088000000000000000000000000ba216e52be5a93e8ba380a761811f3802fa6ed8900000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab10000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000368e1254c00000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000065757b760000000000000000000000000000000000000000000000000000000065757b7600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000065757b580000000000000000000000000000000000000000000000000000000065757b58000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000002760000000000000000000000000000000000000000000000000000000000000276000000000000000000000000000000000000000000000000000000000000008262d1ee51a53236d3ebdbca285ca729bf4671de987335013f17a866f8d42e940a1f02af4df4669edfe6f85dec65c53e5a083be04f36223aa810fc9cfe59c519721ca6c0001d3c24b12f17da5277b9f6abe142906e059701e8272e8c04379beafa5553635cc7f588fcdc5cc72e4b4a65d044d4de6203e9a1fae08522a6e3000f83ed1c000000000000000000000000000000000000000000000000000000000000', + value: '0', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: '0xdb298285fe4c5410b05390ca80e8fbe9de1f259b', // FOREX + tokenOut: zeroAddress, // ETH + amountIn: GreaterThanOrEqual(parseUnits('1040', 18)), + amountOut: GreaterThanOrEqual(parseEther('0.0054')), + recipient: '0xba216e52be5a93e8ba380a761811f3802fa6ed89', + }, +} + +const HLP_BALANCER_ETH_TOKENS: TestParams = { + transaction: { + chainId: 42161, + from: '0xac91c1a921f352d9fdee51320d7b91001c2b21c7', + to: '0x9bdc4094860c97d9e5f1c18c4602a4a907d0a916', + hash: '0x58afb2cc0908f7049700bc10bbd144dc12df7baa2ac616abd0f4eefb22012b73', + input: + '0x892eb1b10000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f000000000000000000000000db298285fe4c5410b05390ca80e8fbe9de1f259b4f14d06cb1661ce1dc2a2f26a10a7cd94393b29c00020000000000000000009700000000000000000000000000000000000000000000190bf2e1dcbcaa1ed7b6000000000000000000000000ac91c1a921f352d9fdee51320d7b91001c2b21c700000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab10000000000000000000000008616e8ea83f048ab9a5ec513c9412dd2993bce3f000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000032722e46000000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000657fdbf300000000000000000000000000000000000000000000000000000000657fdbf3000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000657fdbd500000000000000000000000000000000000000000000000000000000657fdbd50000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000027600000000000000000000000000000000000000000000000000000000000002760000000000000000000000000000000000000000000000000000000000000082aa27720eac78bf96077ea6dee2eb5f8d94f28b0a8d38c5210716231345a061dc3a2e661879dd39c22d6c507c466679097cefc991e3c62d576dfdd913f09986161bf2b7ff86af8641a694e6e0a57720a09ffa3969271180aa2a24913f5ba7e7e4c16eea7a19ac2f6d69377f0a8b961f606d9f135f2396e226621d9c1fe11c5264a51b000000000000000000000000000000000000000000000000000000000000', + value: '595000000000000000', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: zeroAddress, // ETH + tokenOut: '0xdb298285fe4c5410b05390ca80e8fbe9de1f259b', // FOREX + amountIn: GreaterThanOrEqual(parseEther('0.595')), + amountOut: GreaterThanOrEqual(parseUnits('118270', 18)), + recipient: '0xac91c1a921f352d9fdee51320d7b91001c2b21c7', + }, +} + +const CURVE_FACTORY_V2: TestParams = { + transaction: { + chainId: 42161, + from: '0x86d55a7c9e70ba692b9b9b8460f354034f9ec896', + to: '0xab174ffa530c888649c44c4d21c849bbaabc723f', + hash: '0x77bd0b3159930e1b11aa5fcbcad5bb90f0e8693ea3873219ec6eb8f6d9ee524d', + input: + '0xa6417ed6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000008f64fe4909d7f9a300000000000000000000000000000000000000000000000000000000009a80bd', + value: '0', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: '0x8616e8ea83f048ab9a5ec513c9412dd2993bce3f', // fxUSD + tokenOut: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', // USDC.e + amountIn: GreaterThanOrEqual(parseUnits('10', 18)), + amountOut: GreaterThanOrEqual(parseUnits('10', 6)), + recipient: '0x86d55a7c9e70ba692b9b9b8460f354034f9ec896', + }, +} + +const CURVE_FACTORY_2POOL: TestParams = { + transaction: { + chainId: 42161, + from: '0x0b8b2a4996627f9bf106e7b6d9540f1266841957', + to: '0xd0dd5d76cf0fc06dabc48632735566dca241a35e', + hash: '0xa7f8d500da701b028d037740a5f74a73eaaff7d344526484812e96ef354cae26', + input: + '0xa6417ed6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000008c1c0e211a8d8000000000000000000000000000000000000000000000000000000000000096c973', + value: '0', + }, + params: { + chainId: ARBITRUM_ONE, + tokenIn: '0x8616e8ea83f048ab9a5ec513c9412dd2993bce3f', // fxUSD + tokenOut: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', // USDT + amountIn: GreaterThanOrEqual(parseUnits('10', 18)), + amountOut: GreaterThanOrEqual(parseUnits('9.65', 6)), + recipient: '0x0b8b2a4996627f9bf106e7b6d9540f1266841957', + }, +} + +export const passingTestCases = [ + createTestCase( + PARASWAP_SIMPLESWAP, + 'when routed through paraswap (simpleswap)', + ), + createTestCase( + PARASWAP_MULTISWAP, + 'when routed through paraswap (multiswap)', + ), + createTestCase(PARASWAP_UNI_V3, 'when routed through paraswap (uniV3Swap)'), + createTestCase( + V2_ROUTER_ETH_TOKENS, + 'when routed through V2 router (ETH To Tokens)', + ), + createTestCase( + V2_ROUTER_TOKENS_ETH, + 'when routed through V2 router (Tokens To ETH)', + ), + createTestCase( + HPSM2_WITHDRAW, + 'when routed through HSPM2 contract (withdraw)', + ), + createTestCase(HPSM2_DEPOSIT, 'when routed through HSPM2 contract (deposit)'), + createTestCase( + HLP_CURVE_V2_ETH_TO_CURVE, + 'when routed through HLPcurveV2 contract (ETH to tokens)', + ), + createTestCase( + HLP_CURVE_V2_PEGGED_TO_CURVE, + 'when routed through HLPcurveV2 contract (Pegged to Curve)', + ), + createTestCase( + HLP_CURVE_V2_TOKEN_TO_CURVE, + 'when routed through HLPcurveV2 contract (Token to Curve)', + ), + createTestCase( + HLP_BALANCER_ETH_TOKENS, + 'when routed through HLPBalancer contract (ETH to tokens)', + ), + createTestCase( + HLP_BALANCER_TOKENS_ETH, + 'when routed through HLPBalancer contract (Tokens to ETH)', + ), + createTestCase( + CURVE_FACTORY_V2, + 'when routed through curve factory v2 contract', + ), + createTestCase(CURVE_FACTORY_2POOL, 'when routed through curve 2pool'), + createTestCase( + PARASWAP_SIMPLESWAP, + 'when routed through paraswap (simpleswap) using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + PARASWAP_MULTISWAP, + 'when routed through paraswap (multiswap) using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + PARASWAP_UNI_V3, + 'when routed through paraswap (uniV3Swap) using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + V2_ROUTER_ETH_TOKENS, + 'when routed through V2 router (ETH To Tokens) using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + V2_ROUTER_TOKENS_ETH, + 'when routed through V2 router (Tokens To ETH) using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + HPSM2_WITHDRAW, + 'when routed through HSPM2 contract (withdraw) using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + HPSM2_DEPOSIT, + 'when routed through HSPM2 contract (deposit) using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + HLP_CURVE_V2_ETH_TO_CURVE, + 'when routed through HLPcurveV2 contract (ETH to tokens) using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + HLP_CURVE_V2_PEGGED_TO_CURVE, + 'when routed through HLPcurveV2 contract (Pegged to Curve) using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + HLP_CURVE_V2_TOKEN_TO_CURVE, + 'when routed through HLPcurveV2 contract (Token to Curve) using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + HLP_BALANCER_ETH_TOKENS, + 'when routed through HLPBalancer contract (ETH to tokens) using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + HLP_BALANCER_TOKENS_ETH, + 'when routed through HLPBalancer contract (Tokens to ETH) using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + CURVE_FACTORY_V2, + 'when routed through curve factory v2 contract using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + CURVE_FACTORY_2POOL, + 'when routed through curve 2pool using any token', + { + tokenIn: undefined, + tokenOut: undefined, + amountIn: undefined, + amountOut: undefined, + }, + ), + createTestCase( + PARASWAP_SIMPLESWAP, + 'when routed through paraswap (simpleswap) using checksummed token addresses', + { + tokenIn: getAddress(PARASWAP_SIMPLESWAP.params.tokenIn as Address), + tokenOut: getAddress(PARASWAP_SIMPLESWAP.params.tokenOut as Address), + }, + ), + createTestCase( + PARASWAP_MULTISWAP, + 'when routed through paraswap (multiswap) using checksummed token addresses', + { + tokenIn: getAddress(PARASWAP_MULTISWAP.params.tokenIn as Address), + tokenOut: getAddress(PARASWAP_MULTISWAP.params.tokenOut as Address), + }, + ), + createTestCase( + PARASWAP_UNI_V3, + 'when routed through paraswap (uniV3Swap) using checksummed token addresses', + { + tokenIn: getAddress(PARASWAP_UNI_V3.params.tokenIn as Address), + tokenOut: getAddress(PARASWAP_UNI_V3.params.tokenOut as Address), + }, + ), + createTestCase( + V2_ROUTER_ETH_TOKENS, + 'when routed through V2 router (ETH To Tokens) using checksummed token addresses', + { + tokenIn: getAddress(V2_ROUTER_ETH_TOKENS.params.tokenIn as Address), + tokenOut: getAddress(V2_ROUTER_ETH_TOKENS.params.tokenOut as Address), + }, + ), + createTestCase( + V2_ROUTER_TOKENS_ETH, + 'when routed through V2 router (Tokens To ETH) using checksummed token addresses', + { + tokenIn: getAddress(V2_ROUTER_TOKENS_ETH.params.tokenIn as Address), + tokenOut: getAddress(V2_ROUTER_TOKENS_ETH.params.tokenOut as Address), + }, + ), + createTestCase( + HPSM2_WITHDRAW, + 'when routed through HSPM2 contract (withdraw) using checksummed token addresses', + { + tokenIn: getAddress(HPSM2_WITHDRAW.params.tokenIn as Address), + tokenOut: getAddress(HPSM2_WITHDRAW.params.tokenOut as Address), + }, + ), + createTestCase( + HPSM2_DEPOSIT, + 'when routed through HSPM2 contract (deposit) using checksummed token addresses', + { + tokenIn: getAddress(HPSM2_DEPOSIT.params.tokenIn as Address), + tokenOut: getAddress(HPSM2_DEPOSIT.params.tokenOut as Address), + }, + ), + createTestCase( + HLP_CURVE_V2_ETH_TO_CURVE, + 'when routed through HLPcurveV2 contract (ETH to tokens) using checksummed token addresses', + { + tokenIn: getAddress(HLP_CURVE_V2_ETH_TO_CURVE.params.tokenIn as Address), + tokenOut: getAddress( + HLP_CURVE_V2_ETH_TO_CURVE.params.tokenOut as Address, + ), + }, + ), + createTestCase( + HLP_CURVE_V2_PEGGED_TO_CURVE, + 'when routed through HLPcurveV2 contract (Pegged to Curve) using checksummed token addresses', + { + tokenIn: getAddress( + HLP_CURVE_V2_PEGGED_TO_CURVE.params.tokenIn as Address, + ), + tokenOut: getAddress( + HLP_CURVE_V2_PEGGED_TO_CURVE.params.tokenOut as Address, + ), + }, + ), + createTestCase( + HLP_CURVE_V2_TOKEN_TO_CURVE, + 'when routed through HLPcurveV2 contract (Token to Curve) using checksummed token addresses', + { + tokenIn: getAddress( + HLP_CURVE_V2_TOKEN_TO_CURVE.params.tokenIn as Address, + ), + tokenOut: getAddress( + HLP_CURVE_V2_TOKEN_TO_CURVE.params.tokenOut as Address, + ), + }, + ), + createTestCase( + HLP_BALANCER_ETH_TOKENS, + 'when routed through HLPBalancer contract (ETH to tokens) using checksummed token addresses', + { + tokenIn: getAddress(HLP_BALANCER_ETH_TOKENS.params.tokenIn as Address), + tokenOut: getAddress(HLP_BALANCER_ETH_TOKENS.params.tokenOut as Address), + }, + ), + createTestCase( + HLP_BALANCER_TOKENS_ETH, + 'when routed through HLPBalancer contract (Tokens to ETH) using checksummed token addresses', + { + tokenIn: getAddress(HLP_BALANCER_TOKENS_ETH.params.tokenIn as Address), + tokenOut: getAddress(HLP_BALANCER_TOKENS_ETH.params.tokenOut as Address), + }, + ), + createTestCase( + CURVE_FACTORY_V2, + 'when routed through curve factory v2 contract using checksummed token addresses', + { + tokenIn: getAddress(CURVE_FACTORY_V2.params.tokenIn as Address), + tokenOut: getAddress(CURVE_FACTORY_V2.params.tokenOut as Address), + }, + ), + createTestCase( + CURVE_FACTORY_2POOL, + 'when routed through curve 2pool using checksummed token addresses', + { + tokenIn: getAddress(CURVE_FACTORY_2POOL.params.tokenIn as Address), + tokenOut: getAddress(CURVE_FACTORY_2POOL.params.tokenOut as Address), + }, + ), +] + +export const failingTestCases = [ + createTestCase( + PARASWAP_FAIL, + 'when using paraswap route not referred by handlefi', + ), + createTestCase(V2_ROUTER_TOKENS_ETH, 'when chainId is incorrect', { + chainId: 324, + }), + createTestCase( + PARASWAP_SIMPLESWAP, + 'when routed through paraswap (simpleswap) and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + PARASWAP_MULTISWAP, + 'when routed through paraswap (multiswap)', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + PARASWAP_UNI_V3, + 'when routed through paraswap (uniV3Swap) and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + V2_ROUTER_ETH_TOKENS, + 'when routed through V2 router (ETH To Tokens) and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + V2_ROUTER_TOKENS_ETH, + 'when routed through V2 router (Tokens To ETH) and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HPSM2_WITHDRAW, + 'when routed through HSPM2 contract (withdraw) and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HPSM2_DEPOSIT, + 'when routed through HSPM2 contract (deposit) and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HLP_CURVE_V2_ETH_TO_CURVE, + 'when routed through HLPcurveV2 contract (ETH to tokens) and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HLP_CURVE_V2_PEGGED_TO_CURVE, + 'when routed through HLPcurveV2 contract (Pegged to Curve) and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HLP_CURVE_V2_TOKEN_TO_CURVE, + 'when routed through HLPcurveV2 contract (Token to Curve) and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HLP_BALANCER_ETH_TOKENS, + 'when routed through HLPBalancer contract (ETH to tokens) and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HLP_BALANCER_TOKENS_ETH, + 'when routed through HLPBalancer contract (Tokens to ETH) and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + CURVE_FACTORY_V2, + 'when routed through curve factory v2 contract and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + CURVE_FACTORY_2POOL, + 'when routed through curve 2pool and tokenIn is not correct', + { + tokenIn: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + PARASWAP_SIMPLESWAP, + 'when routed through paraswap (simpleswap) and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + PARASWAP_MULTISWAP, + 'when routed through paraswap (multiswap) and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + PARASWAP_UNI_V3, + 'when routed through paraswap (uniV3Swap) and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + V2_ROUTER_ETH_TOKENS, + 'when routed through V2 router (ETH To Tokens) and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + V2_ROUTER_TOKENS_ETH, + 'when routed through V2 router (Tokens To ETH) and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HPSM2_WITHDRAW, + 'when routed through HSPM2 contract (withdraw) and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HPSM2_DEPOSIT, + 'when routed through HSPM2 contract (deposit) and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HLP_CURVE_V2_ETH_TO_CURVE, + 'when routed through HLPcurveV2 contract (ETH to tokens) and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HLP_CURVE_V2_PEGGED_TO_CURVE, + 'when routed through HLPcurveV2 contract (Pegged to Curve) and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HLP_CURVE_V2_TOKEN_TO_CURVE, + 'when routed through HLPcurveV2 contract (Token to Curve) and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + HLP_BALANCER_ETH_TOKENS, + 'when routed through HLPBalancer contract (ETH to tokens) and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + CURVE_FACTORY_V2, + 'when routed through curve factory v2 contract and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + CURVE_FACTORY_2POOL, + 'when routed through curve 2pool and tokenOut is not correct', + { + tokenOut: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', + }, + ), + createTestCase( + PARASWAP_SIMPLESWAP, + 'when routed through paraswap (simpleswap) and amountOut is not sufficient', + { + amountOut: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + PARASWAP_MULTISWAP, + 'when routed through paraswap (multiswap) and amountOut is not sufficient', + { + amountOut: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + PARASWAP_UNI_V3, + 'when routed through paraswap (uniV3Swap) and amountOut is not sufficient', + { + amountOut: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + V2_ROUTER_ETH_TOKENS, + 'when routed through V2 router (ETH To Tokens) and amountOut is not sufficient', + { + amountOut: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + V2_ROUTER_TOKENS_ETH, + 'when routed through V2 router (Tokens To ETH) and amountOut is not sufficient', + { + amountOut: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + HLP_CURVE_V2_ETH_TO_CURVE, + 'when routed through HLPcurveV2 contract (ETH to tokens) and amountOut is not sufficient', + { + amountOut: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + HLP_CURVE_V2_PEGGED_TO_CURVE, + 'when routed through HLPcurveV2 contract (Pegged to Curve) and amountOut is not sufficient', + { + amountOut: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + HLP_CURVE_V2_TOKEN_TO_CURVE, + 'when routed through HLPcurveV2 contract (Token to Curve) and amountOut is not sufficient', + { + amountOut: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + HLP_BALANCER_ETH_TOKENS, + 'when routed through HLPBalancer contract (ETH to tokens) and amountOut is not sufficient', + { + amountOut: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + CURVE_FACTORY_V2, + 'when routed through curve factory v2 contract and amountOut is not sufficient', + { + amountOut: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + CURVE_FACTORY_2POOL, + 'when routed through curve 2pool and amountOut is not sufficient', + { + amountOut: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + PARASWAP_SIMPLESWAP, + 'when routed through paraswap (simpleswap) and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + PARASWAP_MULTISWAP, + 'when routed through paraswap (multiswap) and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + PARASWAP_UNI_V3, + 'when routed through paraswap (uniV3Swap) and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + V2_ROUTER_ETH_TOKENS, + 'when routed through V2 router (ETH To Tokens) and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + V2_ROUTER_TOKENS_ETH, + 'when routed through V2 router (Tokens To ETH) and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + HPSM2_WITHDRAW, + 'when routed through HSPM2 contract (withdraw) and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + HPSM2_DEPOSIT, + 'when routed through HSPM2 contract (deposit) and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + HLP_CURVE_V2_ETH_TO_CURVE, + 'when routed through HLPcurveV2 contract (ETH to tokens) and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + HLP_CURVE_V2_PEGGED_TO_CURVE, + 'when routed through HLPcurveV2 contract (Pegged to Curve) and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + HLP_CURVE_V2_TOKEN_TO_CURVE, + 'when routed through HLPcurveV2 contract (Token to Curve) and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + HLP_BALANCER_ETH_TOKENS, + 'when routed through HLPBalancer contract (ETH to tokens) and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + CURVE_FACTORY_V2, + 'when routed through curve factory v2 contract and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), + createTestCase( + CURVE_FACTORY_2POOL, + 'when routed through curve 2pool and amountIn is not sufficient', + { + amountIn: GreaterThanOrEqual(parseEther('1000000')), + }, + ), +] diff --git a/packages/handlefi/src/utils.ts b/packages/handlefi/src/utils.ts new file mode 100644 index 000000000..52f389726 --- /dev/null +++ b/packages/handlefi/src/utils.ts @@ -0,0 +1,74 @@ +import type { ActionParams, FilterOperator } from '@rabbitholegg/questdk' +import { getAddress, type Address, type Hash } from 'viem' + +interface Transaction { + chainId: number + from: Address + hash?: Hash + input: string + to: Address + value: string +} + +export interface TestCase { + transaction: Transaction + params: T + description: string +} + +export type TestParams = { + transaction: Transaction + params: T +} + +/** + * Creates a test case object for a given action and transaction. + * + * This function takes a `TestParams` object that includes both a `Transaction` and + * `ActionParams`, a description of the test case, and an optional set of overrides + * for the action parameters. It returns a `TestCase` object that contains the transaction, + * the combined action parameters with any overrides applied, and the description. + * + * @param {TestParams} testParams - An object containing the transaction and action parameters. + * @param {string} description - A brief description of the test case. + * @param {Partial} [overrides] - Optional overrides for the action parameters. + * @returns {TestCase} A test case object with the transaction, params, and description. + */ +export function createTestCase( + testParams: TestParams, + description: string, + overrides: Partial = {}, +): TestCase { + return { + transaction: testParams.transaction, + params: { ...testParams.params, ...overrides }, + description, + } +} + +export const buildV2PathQueryWithCase = ( + addressCase: 'lower' | 'checksum', + tokenIn?: string, + tokenOut?: string, +) => { + // v2 paths are formatted as [, ] + const conditions: FilterOperator[] = [] + + if (tokenIn) { + conditions.push({ + $first: + addressCase === 'lower' ? tokenIn.toLowerCase() : getAddress(tokenIn), + }) + } + + if (tokenOut) { + conditions.push({ + $last: + addressCase === 'lower' ? tokenOut.toLowerCase() : getAddress(tokenOut), + }) + } + + return { + $and: conditions, + } +} diff --git a/packages/handlefi/tsconfig.build.json b/packages/handlefi/tsconfig.build.json new file mode 100644 index 000000000..2e27369d5 --- /dev/null +++ b/packages/handlefi/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "extends": "tsconfig/base.json", + "include": ["src"], + "exclude": [ + "src/**/*.test.ts", + "src/**/*.test-d.ts", + "src/**/*.bench.ts", + "src/_test", + "scripts/**/*" + ], + "compilerOptions": { + "resolveJsonModule": true, + "sourceMap": true, + "rootDir": "./src" + } +} diff --git a/packages/handlefi/tsconfig.json b/packages/handlefi/tsconfig.json new file mode 100644 index 000000000..c76405177 --- /dev/null +++ b/packages/handlefi/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "tsconfig/base.json", + "include": ["src/**/*", "src/chain-data.ts"], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/packages/registry/package.json b/packages/registry/package.json index b5c79a707..458efb75b 100644 --- a/packages/registry/package.json +++ b/packages/registry/package.json @@ -68,6 +68,7 @@ "@rabbitholegg/questdk-plugin-woofi": "workspace:*", "@rabbitholegg/questdk-plugin-sushi": "workspace:*", "@rabbitholegg/questdk-plugin-treasure": "workspace:*", + "@rabbitholegg/questdk-plugin-handlefi": "workspace:*", "viem": "^1.16.6" } } diff --git a/packages/registry/src/index.ts b/packages/registry/src/index.ts index 00dcff266..393fb6e67 100644 --- a/packages/registry/src/index.ts +++ b/packages/registry/src/index.ts @@ -36,6 +36,7 @@ import { Synapse } from '@rabbitholegg/questdk-plugin-synapse' import { WooFi } from '@rabbitholegg/questdk-plugin-woofi' import { Sushi } from '@rabbitholegg/questdk-plugin-sushi' import { Treasure } from '@rabbitholegg/questdk-plugin-treasure' +import { HandleFi } from '@rabbitholegg/questdk-plugin-handlefi' import { ENTRYPOINT } from './contract-addresses' export const plugins: Record = { @@ -63,6 +64,7 @@ export const plugins: Record = { [WooFi.pluginId]: WooFi, [Sushi.pluginId]: Sushi, [Treasure.pluginId]: Treasure, + [HandleFi.pluginId]: HandleFi, } export const getPlugin = (pluginId: string) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 792673a89..fc6f5450c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -204,6 +204,31 @@ importers: typescript: 5.1.6 vitest: 0.33.0 + packages/handlefi: + specifiers: + '@rabbitholegg/questdk': 2.0.0-alpha.28 + '@types/node': ^20.8.7 + '@vitest/coverage-v8': ^0.33.0 + rimraf: ^5.0.5 + rome: ^12.1.3 + ts-node: ^10.9.1 + tsconfig: workspace:* + typescript: ^5.2.2 + viem: ^1.16.6 + vitest: ^0.33.0 + dependencies: + '@rabbitholegg/questdk': 2.0.0-alpha.28_typescript@5.3.3 + viem: 1.21.0_typescript@5.3.3 + devDependencies: + '@types/node': 20.10.5 + '@vitest/coverage-v8': 0.33.0_vitest@0.33.0 + rimraf: 5.0.5 + rome: 12.1.3 + ts-node: 10.9.2_7ln3lwozyybesesf4lghtaucbi + tsconfig: link:../tsconfig + typescript: 5.3.3 + vitest: 0.33.0 + packages/hop: specifiers: '@hop-protocol/core': 0.0.1-beta.182 @@ -401,6 +426,7 @@ importers: '@rabbitholegg/questdk-plugin-camelot': workspace:* '@rabbitholegg/questdk-plugin-connext': workspace:* '@rabbitholegg/questdk-plugin-gmx': workspace:* + '@rabbitholegg/questdk-plugin-handlefi': workspace:* '@rabbitholegg/questdk-plugin-hop': workspace:* '@rabbitholegg/questdk-plugin-hyphen': workspace:* '@rabbitholegg/questdk-plugin-okutrade': workspace:* @@ -436,6 +462,7 @@ importers: '@rabbitholegg/questdk-plugin-camelot': link:../camelot '@rabbitholegg/questdk-plugin-connext': link:../connext '@rabbitholegg/questdk-plugin-gmx': link:../gmx + '@rabbitholegg/questdk-plugin-handlefi': link:../handlefi '@rabbitholegg/questdk-plugin-hop': link:../hop '@rabbitholegg/questdk-plugin-hyphen': link:../hyphen '@rabbitholegg/questdk-plugin-okutrade': link:../okutrade @@ -2383,7 +2410,7 @@ packages: optional: true dependencies: abitype: 0.9.10_typescript@5.1.6 - rimraf: 5.0.1 + rimraf: 5.0.5 typescript: 5.1.6 viem: 1.21.0_typescript@5.1.6 transitivePeerDependencies: @@ -2401,7 +2428,7 @@ packages: optional: true dependencies: abitype: 0.9.10_typescript@5.3.3 - rimraf: 5.0.1 + rimraf: 5.0.5 typescript: 5.3.3 viem: 1.21.0_typescript@5.3.3 transitivePeerDependencies: @@ -2466,7 +2493,7 @@ packages: resolution: {integrity: sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==} dependencies: '@noble/curves': 1.0.0 - '@noble/hashes': 1.3.0 + '@noble/hashes': 1.3.2 '@scure/base': 1.1.5 dev: false @@ -2489,7 +2516,7 @@ packages: /@scure/bip39/1.2.0: resolution: {integrity: sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==} dependencies: - '@noble/hashes': 1.3.0 + '@noble/hashes': 1.3.2 '@scure/base': 1.1.5 dev: false @@ -2761,6 +2788,7 @@ packages: /@types/node/20.4.5: resolution: {integrity: sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==} + dev: true /@types/normalize-package-data/2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -2821,7 +2849,7 @@ packages: /@types/ws/8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: - '@types/node': 20.4.5 + '@types/node': 20.10.5 dev: false /@uniswap/lib/4.0.1-alpha: @@ -8589,6 +8617,7 @@ packages: hasBin: true dependencies: glob: 10.3.10 + dev: true /rimraf/5.0.5: resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} @@ -8596,7 +8625,6 @@ packages: hasBin: true dependencies: glob: 10.3.10 - dev: true /ripemd160/2.0.2: resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} @@ -11105,7 +11133,7 @@ packages: /wide-align/1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: - string-width: 1.0.2 + string-width: 4.2.3 dev: false optional: true