-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
merge branch 'upstream/main' into mmackz-camelot-add-v3
- Loading branch information
Showing
17 changed files
with
2,472 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
"@rabbitholegg/questdk-plugin-handlefi": minor | ||
"@rabbitholegg/questdk-plugin-registry": minor | ||
--- | ||
|
||
add support for handlefi swap plugin to questdk |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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`, | ||
) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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<TransactionFilter> => { | ||
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<Address[]> => { | ||
return _chainId === ARBITRUM_ONE ? TOKEN_ADDRESSES : [] | ||
} | ||
|
||
export const getSupportedChainIds = async (): Promise<number[]> => { | ||
return [ARBITRUM_ONE] | ||
} |
Oops, something went wrong.