Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(handlefi): add support for HandleFi swap plugin #144

Merged
merged 39 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2f0837a
feat(handlefi): initialize plugin from template
mmackz Dec 19, 2023
9a79531
feat(handlefi): add abi for various functions
mmackz Dec 21, 2023
8e3ea3d
chore(pnpm): install curve api package
mmackz Dec 21, 2023
2074c83
feat(handlefi): clone utils file
mmackz Dec 21, 2023
3c81ab2
feat(handlefi): add contract and token addresses to constants file
mmackz Dec 21, 2023
9ad79b6
feat(handlefi): add arbitrum chain id to constants
mmackz Dec 21, 2023
31e54f3
test(handlefi): setup test cases for paraswap routes
mmackz Dec 21, 2023
6b1249a
feat(handlefi): create input filter for paraswap routes
mmackz Dec 21, 2023
3d618e1
refactor(handlefi): convert buildpathquery function to return differe…
mmackz Dec 22, 2023
ef26763
feat(handlefi): add V2 router filter
mmackz Dec 22, 2023
99011fe
feat(handlefi): add methods for HPSM2 contract
mmackz Dec 22, 2023
8cb286f
feat(handlefi): add support for HLP curve V2 routing
mmackz Dec 22, 2023
c479c9b
feat(handlefi): add support for swaps routed through HlpBalancer cont…
mmackz Dec 22, 2023
b3a66ac
chore(pnpm): format
mmackz Dec 22, 2023
a3421ad
feat(handlefi): add support for curve factory pool
mmackz Dec 22, 2023
fccef74
chore(pnpm): remove curve api package
mmackz Dec 22, 2023
d43d311
chore(pnpm): format
mmackz Dec 22, 2023
b24492a
refactor(handlefi): assert address type on address array
mmackz Dec 22, 2023
b59662b
feat(handlefi): implement helper functions
mmackz Dec 22, 2023
dcda9cd
feat(handlefi): add support for swaps routed through curve 2pool
mmackz Dec 22, 2023
52bf35e
chore(pnpm): format
mmackz Dec 22, 2023
905d134
test(handlefi): test valid action filter is returned
mmackz Dec 22, 2023
ba0cbc9
refactor(handlefi): remove unused exports
mmackz Dec 24, 2023
91bfbdc
test(handlefi): test valid token addresses are returned for each vali…
mmackz Dec 24, 2023
f7b1230
docs(handlefi): comment on curvev2 function
mmackz Dec 24, 2023
e4ceda2
fix(handlefi): use passed in chainId parameter
mmackz Dec 24, 2023
d88bfca
test(handlefi): test checksummed token addresses pass
mmackz Dec 24, 2023
cf563f5
refactor(handlefi): split abi of hpsm2
mmackz Dec 24, 2023
6a906fe
test(handlefi): add split abi to filter test
mmackz Dec 24, 2023
4f8357d
feat(handlefi): unsplit abi and test amountIn on HPSM2 routed trades
mmackz Dec 24, 2023
b049f2e
docs(handlefi): add README.ms
mmackz Dec 24, 2023
0e83a43
chore(pnpm): format
mmackz Dec 24, 2023
379e2aa
chore(pnpm): generate changes for handlefi swap plugin
mmackz Dec 24, 2023
2c1a624
docs(handlefi): typo
mmackz Dec 24, 2023
e83fc3d
docs(handlefi): typo
mmackz Dec 24, 2023
a18fda6
merge branch 'upstream/main' into mmackz-handlefi-swap
mmackz Jan 3, 2024
bd12f25
refactor(handlefi): pass partner as param to function
mmackz Jan 3, 2024
52ee8f0
chore(pnpm): regen lockfile
mmackz Jan 3, 2024
4a116f7
refactor(handlefi): remove unused Chains enum from utils
mmackz Jan 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/gold-socks-dream.md
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.
28 changes: 28 additions & 0 deletions packages/handlefi/README.md
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)
49 changes: 49 additions & 0 deletions packages/handlefi/package.json
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"
}
}
236 changes: 236 additions & 0 deletions packages/handlefi/src/HandleFi.test.ts
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`,
)
})
})
})
})
})
54 changes: 54 additions & 0 deletions packages/handlefi/src/HandleFi.ts
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]
}
Loading
Loading