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(stargate): Stargate bridge #22

Merged
merged 47 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
79c222e
Feat(stargate): dupe template
Quazia Sep 6, 2023
a97c9e6
Docs(stargate): tx in readme
Quazia Sep 6, 2023
9dfeb1a
Feat(stargate): add chain ids
Quazia Sep 6, 2023
a44f76c
Feat(stargate): contract addresses
Quazia Sep 6, 2023
ee52435
Feat(stargate): test transactions
Quazia Sep 6, 2023
69b5dae
Feat(stargate): add abi
Quazia Sep 7, 2023
b314867
Feat(stargate): getSupportedChainIds
Quazia Sep 7, 2023
5313a7c
Chore(stargate): install viem
Quazia Sep 7, 2023
d439883
Feat(stargate): getTokenFromPool
Quazia Sep 7, 2023
f8fa7d4
Feat(stargate): add source pool decoding
Quazia Sep 7, 2023
197b961
Fix(stargate): chain pool to token typeing
Quazia Sep 8, 2023
511885e
Fix(stargate): add types for chainIDs
Quazia Sep 8, 2023
4e464e7
Fix(stargate): handle token undefined in bridge
Quazia Sep 8, 2023
4758a5f
Test(stargate): filter passing
Quazia Sep 8, 2023
00c7504
Fix(stargate): translate chain ids for layer zero
Quazia Sep 8, 2023
4f6771c
Test(stargate): filter apply
Quazia Sep 8, 2023
d62fed8
Fix(stargate): token lookup in contract addresses
Quazia Sep 8, 2023
5599368
chore: format
Quazia Sep 8, 2023
e7de993
Chore(stargate): const cleanup
Quazia Sep 12, 2023
c457c02
Merge branch 'main' of https://github.com/rabbitholegg/questdk-plugin…
Quazia Sep 12, 2023
86570d4
Chore(stargate): comment cleanup
Quazia Sep 12, 2023
3580797
Merge branch 'stargate_bridge' of https://github.com/rabbitholegg/que…
Quazia Sep 12, 2023
5ab4eea
chore: format
Quazia Sep 12, 2023
3c3a887
Chore(stargate): update quesdk alpha.9
Quazia Sep 13, 2023
cb24e19
Merge branch 'stargate_bridge' of https://github.com/rabbitholegg/que…
Quazia Sep 13, 2023
9cbaafa
Chore(stargate): pin deps
Quazia Sep 13, 2023
1b8969d
Merge branch 'main' of https://github.com/rabbitholegg/questdk-plugin…
Quazia Sep 13, 2023
0e9e939
Merge branch 'main' of https://github.com/rabbitholegg/questdk-plugin…
Quazia Sep 13, 2023
de65909
Chore(pnpm): regen lock
Quazia Sep 13, 2023
518d96f
chore: format
Quazia Sep 13, 2023
dd56137
Fix(stargate): getSupportedChainIds return type
Quazia Sep 13, 2023
4848468
Merge branch 'stargate_bridge' of https://github.com/rabbitholegg/que…
Quazia Sep 13, 2023
2d96fd6
chore: format
Quazia Sep 13, 2023
1346df9
Fix(stargate): type import in helper func
Quazia Sep 13, 2023
301ec76
Merge branch 'stargate_bridge' of https://github.com/rabbitholegg/que…
Quazia Sep 13, 2023
3ac626d
Chore(stargate): cleanup comments
Quazia Sep 13, 2023
e33e886
Fix(stargate): CHAIN_AND_POOL_TO_TOKEN_ADDRESS
Quazia Sep 14, 2023
d601d70
chore: format
Quazia Sep 14, 2023
d3153ee
Test(stargate): update test addresses
Quazia Sep 14, 2023
b4ddb92
Merge branch 'stargate_bridge' of https://github.com/rabbitholegg/que…
Quazia Sep 14, 2023
eb67e35
Chore(changeset): release stargate bridge plugin
Quazia Sep 15, 2023
52d0f99
Feat(registry): add stargate to registry
Quazia Sep 15, 2023
bd86016
Chore(pnpm): regen lock
Quazia Sep 15, 2023
f4fc032
Merge branch 'main' of https://github.com/rabbitholegg/questdk-plugin…
Quazia Sep 15, 2023
1712286
Fix(registry): resolve merge issue
Quazia Sep 15, 2023
586ab58
Fix(registry): fix getPlugin after merge
Quazia Sep 15, 2023
ef6b199
chore: format
Quazia Sep 15, 2023
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
Empty file.
25 changes: 25 additions & 0 deletions packages/stargate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

## ETH/BASE TOKEN
Other than the token address retrieval this is a pretty trivial to implement plugin.
Unfortunatly in order to get the token address we need to do some direct chain reads but nothing too heavy.


### Deposit ETH
Deposit to Arbitrum Layer Zero
https://etherscan.io/tx/0xe3a54ddf9a09ee9df6abe87af38cc418c516fb198fc635972615418f5254812a

### Withdraw ETH
Withdraw ETH to Optimism
https://arbiscan.io/tx/0xc6fa0deba78d6228d3cb343a908390162955dcb3d6d5a8e952ef24c54f7c453f

## ERC20

### Deposit ERC20
https://etherscan.io/tx/0x28901f729bd89400b5c046eb73abf0656ab82cb02463e47562d7ce24737d263f
### Withdraw ERC20
USDT
https://arbiscan.io/tx/0xb983d41651ccad84a70600564376975a3a3385dae53de35259486f20dd2d8d4d


You can use the following example code to pull down test transactions in the correct format easily:
https://viem.sh/docs/actions/public/getTransaction.html#example
51 changes: 51 additions & 0 deletions packages/stargate/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "@rabbitholegg/questdk-plugin-stargate",
"private": true,
"version": "1.0.0-alpha.3",
"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": "[email protected]",
"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",
"preinstall": "npx only-allow pnpm",
"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.4.5",
"@vitest/coverage-v8": "^0.33.0",
"rimraf": "^5.0.1",
"rome": "^12.1.3",
"ts-node": "^10.9.1",
"tsconfig": "workspace:*",
"typescript": "^5.1.6",
"vitest": "^0.33.0"
},
"dependencies": {
"@rabbitholegg/questdk": "1.0.1-alpha.8",
"viem": "^1.6.7"
}
}
160 changes: 160 additions & 0 deletions packages/stargate/src/Stargate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { GreaterThanOrEqual, apply } from '@rabbitholegg/questdk/filter'
import { describe, expect, test } from 'vitest'
import { bridge } from './Stargate.js'
import {
DEPOSIT_ETH,
DEPOSIT_ERC20,
WITHDRAW_ETH,
WITHDRAW_ERC20,
} from './test-transactions.js'
import {
ARBITRUM_LAYER_ZERO_CHAIN_ID,
POLYGON_LAYER_ZERO_CHAIN_ID,
ETH_LAYER_ZERO_CHAIN_ID,
OPTIMISM_LAYER_ZERO_CHAIN_ID,
LAYER_ZERO_TO_LAYER_ONE_CHAIN_ID,
} from './chain-ids.js'
import { STARGATE_BRIDGE_ABI } from './abi.js'
import { parseEther } from 'viem'
import {
CHAIN_AND_POOL_TO_TOKEN_ADDRESS,
CHAIN_ID_TO_ROUTER_ADDRESS,
CHAIN_ID_TO_ETH_ROUTER_ADDRESS,
} from './contract-addresses.js'

const ARBITRUM_USDC_ADDRESS = '0x892785f33CdeE22A30AEF750F285E18c18040c3e'
const ARBITRUM_USDT_ADDRESS = '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9'
const ETHEREUM_USDC_ADDRESS = '0xdf0770dF86a8034b3EFEf0A1Bb3c889B8332FF56'
const ETHEREUM_SGETH_ADDRESS = '0x101816545F6bd2b1076434B54383a1E633390A2E'
const ARBITRUM_SGETH_ADDRESS = '0x82CbeCF39bEe528B5476FE6d1550af59a9dB6Fc0'
const TEST_USER = '0x7c3bd1a09d7d86920451def20ae503322c8d0412'
// Replace *project* with the name of the project
describe('Given the Across plugin', () => {
describe('When generating the filter', () => {
test('should return a valid bridge action filter for L2 token tx', async () => {
const filter = await bridge({
sourceChainId: ARBITRUM_LAYER_ZERO_CHAIN_ID,
destinationChainId: ETH_LAYER_ZERO_CHAIN_ID,
tokenAddress: ARBITRUM_USDC_ADDRESS,
amount: GreaterThanOrEqual(100000n),
recipient: TEST_USER,
})
const sourcePool =
CHAIN_AND_POOL_TO_TOKEN_ADDRESS[ARBITRUM_LAYER_ZERO_CHAIN_ID][
ARBITRUM_USDC_ADDRESS
]

expect(filter).to.deep.equal({
chainId: LAYER_ZERO_TO_LAYER_ONE_CHAIN_ID[ARBITRUM_LAYER_ZERO_CHAIN_ID],
to: CHAIN_ID_TO_ROUTER_ADDRESS[ARBITRUM_LAYER_ZERO_CHAIN_ID],
input: {
$abi: STARGATE_BRIDGE_ABI,
_srcPoolId: sourcePool,
_amountLD: {
$gte: '100000',
},
_to: TEST_USER,
_dstChainId: ETH_LAYER_ZERO_CHAIN_ID,
},
})
})

test('should return a valid bridge action filter for L1 token tx', async () => {
const filter = await bridge({
sourceChainId: ETH_LAYER_ZERO_CHAIN_ID,
destinationChainId: ARBITRUM_LAYER_ZERO_CHAIN_ID,
tokenAddress: ETHEREUM_USDC_ADDRESS,
amount: GreaterThanOrEqual(100000n),
recipient: TEST_USER,
})
const sourcePool =
CHAIN_AND_POOL_TO_TOKEN_ADDRESS[ETH_LAYER_ZERO_CHAIN_ID][
ETHEREUM_USDC_ADDRESS
]

expect(filter).to.deep.equal({
chainId: LAYER_ZERO_TO_LAYER_ONE_CHAIN_ID[ETH_LAYER_ZERO_CHAIN_ID],
to: CHAIN_ID_TO_ROUTER_ADDRESS[ETH_LAYER_ZERO_CHAIN_ID],
input: {
$abi: STARGATE_BRIDGE_ABI,
_srcPoolId: sourcePool,
_amountLD: {
$gte: '100000',
},
_to: TEST_USER,
_dstChainId: ARBITRUM_LAYER_ZERO_CHAIN_ID,
},
})
})

test('should return a valid bridge action filter for L1 ETH tx', async () => {
const filter = await bridge({
sourceChainId: ETH_LAYER_ZERO_CHAIN_ID,
destinationChainId: ARBITRUM_LAYER_ZERO_CHAIN_ID,
tokenAddress: ETHEREUM_SGETH_ADDRESS,
amount: GreaterThanOrEqual(100000n),
recipient: TEST_USER,
})
expect(filter).to.deep.equal({
chainId: LAYER_ZERO_TO_LAYER_ONE_CHAIN_ID[ETH_LAYER_ZERO_CHAIN_ID],
to: CHAIN_ID_TO_ETH_ROUTER_ADDRESS[ETH_LAYER_ZERO_CHAIN_ID],
input: {
$abi: STARGATE_BRIDGE_ABI,
_amountLD: {
$gte: '100000',
},
_toAddress: TEST_USER,
_dstChainId: ARBITRUM_LAYER_ZERO_CHAIN_ID,
},
})
})
})
describe('When applying the filter', () => {
test('should pass filter with valid L1 ETH tx', async () => {
const transaction = DEPOSIT_ETH
const filter = await bridge({
sourceChainId: ETH_LAYER_ZERO_CHAIN_ID,
destinationChainId: ARBITRUM_LAYER_ZERO_CHAIN_ID,
tokenAddress: ETHEREUM_SGETH_ADDRESS,
amount: GreaterThanOrEqual(parseEther('.04')),
recipient: '0x7c3bd1a09d7d86920451def20ae503322c8d0412',
})
expect(apply(transaction, filter)).to.be.true
})
test('should pass filter with valid L2 ETH tx', async () => {
const transaction = WITHDRAW_ETH
const filter = await bridge({
sourceChainId: ARBITRUM_LAYER_ZERO_CHAIN_ID,
destinationChainId: OPTIMISM_LAYER_ZERO_CHAIN_ID,
tokenAddress: ARBITRUM_SGETH_ADDRESS,
amount: GreaterThanOrEqual(parseEther('.6')),
recipient: '0x08bfa4ef61a457792c45240829529b43b019d941',
})
expect(apply(transaction, filter)).to.be.true
})
test('should pass filter with valid L1 Token tx', async () => {
const transaction = DEPOSIT_ERC20
const filter = await bridge({
sourceChainId: ETH_LAYER_ZERO_CHAIN_ID,
destinationChainId: ARBITRUM_LAYER_ZERO_CHAIN_ID,
tokenAddress: ETHEREUM_USDC_ADDRESS,
amount: GreaterThanOrEqual('48500000'), // $250 USDC,
recipient: '0x0318ccfbfae5e2c06b2f533a35acecea13b9909f',
})
expect(apply(transaction, filter)).to.be.true
})
test('should pass filter with valid L2 token tx', async () => {
const transaction = WITHDRAW_ERC20
const filter = await bridge({
sourceChainId: ARBITRUM_LAYER_ZERO_CHAIN_ID,
destinationChainId: POLYGON_LAYER_ZERO_CHAIN_ID,
tokenAddress: ARBITRUM_USDT_ADDRESS,
amount: GreaterThanOrEqual('1200000000'),
recipient: '0x5ee8496532d3dcb8bc726d66d8cb4c45d979e71d',
})
expect(apply(transaction, filter)).to.be.true
})
})

test('should not pass filter with invalid transactions', () => {})
})
67 changes: 67 additions & 0 deletions packages/stargate/src/Stargate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { type BridgeActionParams, compressJson } from '@rabbitholegg/questdk'
import { type Address } from 'viem'
import { STARGATE_BRIDGE_ABI } from './abi.js'
import {
CHAIN_ID_ARRAY,
LAYER_ZERO_TO_LAYER_ONE_CHAIN_ID,
} from './chain-ids.js'
import {
CHAIN_AND_POOL_TO_TOKEN_ADDRESS,
CHAIN_ID_TO_ROUTER_ADDRESS,
CHAIN_ID_TO_ETH_ROUTER_ADDRESS,
} from './contract-addresses.js'

// If you're implementing swap or mint, simply duplicate this function and change the name
export const bridge = async (bridge: BridgeActionParams) => {
// This is the information we'll use to compose the Transaction object
const {
sourceChainId,
destinationChainId,
contractAddress,
tokenAddress,
amount,
recipient,
} = bridge
const layerOneChainId = LAYER_ZERO_TO_LAYER_ONE_CHAIN_ID[sourceChainId]
const sourcePool = tokenAddress
? CHAIN_AND_POOL_TO_TOKEN_ADDRESS[sourceChainId][tokenAddress]
: 0

if (sourcePool === 13) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this const ETH_POOL = 13 accurate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I'll create a const and compare against that vs a magic number for increased clarity.

const targetContractAddress = CHAIN_ID_TO_ETH_ROUTER_ADDRESS[sourceChainId]
return compressJson({
chainId: layerOneChainId, // The chainId of the source chain
to: contractAddress || targetContractAddress, // The contract address of the bridge
input: {
$abi: STARGATE_BRIDGE_ABI, // The ABI of the bridge contract
_amountLD: amount, // The amount of tokens to send
_toAddress: recipient, // The recipient of the tokens
_dstChainId: destinationChainId, // The chainId of the destination chain
}, // The input object is where we'll put the ABI and the parameters
})
}
const targetContractAddress = CHAIN_ID_TO_ROUTER_ADDRESS[sourceChainId]

// We always want to return a compressed JSON object which we'll transform into a TransactionFilter
return compressJson({
chainId: layerOneChainId, // The chainId of the source chain
to: contractAddress || targetContractAddress, // The contract address of the bridge
input: {
$abi: STARGATE_BRIDGE_ABI, // The ABI of the bridge contract
_srcPoolId: sourcePool, // The source poolId
_amountLD: amount, // The amount of tokens to send
_to: recipient, // The recipient of the tokens
_dstChainId: destinationChainId, // The chainId of the destination chain
}, // The input object is where we'll put the ABI and the parameters
})
}

export const getSupportedTokenAddresses = async (
_chainId: number,
): Promise<Address[]> => {
// Given a specific chain we would expect this function to return a list of supported token addresses
}

export const getSupportedChainIds = async () => {
return CHAIN_ID_ARRAY
}
49 changes: 49 additions & 0 deletions packages/stargate/src/abi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export const STARGATE_BRIDGE_ABI = [
{
inputs: [
{ internalType: 'uint16', name: '_dstChainId', type: 'uint16' },
{
internalType: 'address payable',
name: '_refundAddress',
type: 'address',
},
{ internalType: 'bytes', name: '_toAddress', type: 'bytes' },
{ internalType: 'uint256', name: '_amountLD', type: 'uint256' },
{ internalType: 'uint256', name: '_minAmountLD', type: 'uint256' },
],
name: 'swapETH',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{ internalType: 'uint16', name: '_dstChainId', type: 'uint16' },
{ internalType: 'uint256', name: '_srcPoolId', type: 'uint256' },
{ internalType: 'uint256', name: '_dstPoolId', type: 'uint256' },
{
internalType: 'address payable',
name: '_refundAddress',
type: 'address',
},
{ internalType: 'uint256', name: '_amountLD', type: 'uint256' },
{ internalType: 'uint256', name: '_minAmountLD', type: 'uint256' },
{
components: [
{ internalType: 'uint256', name: 'dstGasForCall', type: 'uint256' },
{ internalType: 'uint256', name: 'dstNativeAmount', type: 'uint256' },
{ internalType: 'bytes', name: 'dstNativeAddr', type: 'bytes' },
],
internalType: 'struct IStargateRouter.lzTxObj',
name: '_lzTxParams',
type: 'tuple',
},
{ internalType: 'bytes', name: '_to', type: 'bytes' },
{ internalType: 'bytes', name: '_payload', type: 'bytes' },
],
name: 'swap',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
]
39 changes: 39 additions & 0 deletions packages/stargate/src/chain-ids.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export const ETH_LAYER_ZERO_CHAIN_ID: number = 101
export const BNB_LAYER_ZERO_CHAIN_ID: number = 102
export const AVALANCHE_LAYER_ZERO_CHAIN_ID: number = 106
export const POLYGON_LAYER_ZERO_CHAIN_ID: number = 109
export const ARBITRUM_LAYER_ZERO_CHAIN_ID: number = 110
export const OPTIMISM_LAYER_ZERO_CHAIN_ID: number = 111
export const FANTOM_LAYER_ZERO_CHAIN_ID: number = 112
export const METIS_LAYER_ZERO_CHAIN_ID: number = 151
export const KAVA_LAYER_ZERO_CHAIN_ID: number = 177
export const LINEA_LAYER_ZERO_CHAIN_ID: number = 183
export const BASE_LAYER_ZERO_CHAIN_ID: number = 184

export const LAYER_ZERO_TO_LAYER_ONE_CHAIN_ID: Record<number, number> = {
[ETH_LAYER_ZERO_CHAIN_ID]: 1,
[BNB_LAYER_ZERO_CHAIN_ID]: 56,
[AVALANCHE_LAYER_ZERO_CHAIN_ID]: 43114,
[POLYGON_LAYER_ZERO_CHAIN_ID]: 137,
[ARBITRUM_LAYER_ZERO_CHAIN_ID]: 42161,
[OPTIMISM_LAYER_ZERO_CHAIN_ID]: 10,
[FANTOM_LAYER_ZERO_CHAIN_ID]: 250,
[METIS_LAYER_ZERO_CHAIN_ID]: 1088,
[KAVA_LAYER_ZERO_CHAIN_ID]: 2222,
[LINEA_LAYER_ZERO_CHAIN_ID]: 59144,
[BASE_LAYER_ZERO_CHAIN_ID]: 8453,
}

export const CHAIN_ID_ARRAY = [
ETH_LAYER_ZERO_CHAIN_ID,
BNB_LAYER_ZERO_CHAIN_ID,
AVALANCHE_LAYER_ZERO_CHAIN_ID,
POLYGON_LAYER_ZERO_CHAIN_ID,
ARBITRUM_LAYER_ZERO_CHAIN_ID,
OPTIMISM_LAYER_ZERO_CHAIN_ID,
FANTOM_LAYER_ZERO_CHAIN_ID,
METIS_LAYER_ZERO_CHAIN_ID,
KAVA_LAYER_ZERO_CHAIN_ID,
LINEA_LAYER_ZERO_CHAIN_ID,
BASE_LAYER_ZERO_CHAIN_ID,
] as const
Loading