Skip to content

Commit

Permalink
merge branch 'upstream/main' into mmackz-camelot-add-v3
Browse files Browse the repository at this point in the history
  • Loading branch information
mmackz committed Jan 4, 2024
2 parents cdd3013 + edc7aef commit 8a1233a
Show file tree
Hide file tree
Showing 17 changed files with 2,472 additions and 7 deletions.
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 added packages/handlefi/CHANGELOG.md
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

0 comments on commit 8a1233a

Please sign in to comment.