Skip to content

Commit 84104a4

Browse files
authored
Merge pull request #106 from Defi-Moses/synapse_bridge
Synapse SDK Plugin (Draft)
2 parents a88d22f + cf1ad98 commit 84104a4

16 files changed

+2082
-1
lines changed

packages/registry/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { Paraswap } from '@rabbitholegg/questdk-plugin-paraswap'
2929
import { Rabbithole } from '@rabbitholegg/questdk-plugin-rabbithole'
3030
import { Symbiosis } from '@rabbitholegg/questdk-plugin-symbiosis'
3131
import { OkuTrade } from '@rabbitholegg/questdk-plugin-okutrade'
32+
import { Synapse } from '@rabbitholegg/questdk-plugin-synapse'
3233
import { Balancer } from '@rabbitholegg/questdk-plugin-balancer'
3334
import { TraderJoe } from '@rabbitholegg/questdk-plugin-traderjoe'
3435
import { ENTRYPOINT } from './contract-addresses'
@@ -50,6 +51,7 @@ export const plugins: Record<string, IActionPlugin> = {
5051
[Paraswap.pluginId]: Paraswap,
5152
[Rabbithole.pluginId]: Rabbithole,
5253
[Symbiosis.pluginId]: Symbiosis,
54+
[Synapse.pluginId]: Synapse,
5355
[OkuTrade.pluginId]: OkuTrade,
5456
[Balancer.pluginId]: Balancer,
5557
[TraderJoe.pluginId]: TraderJoe,

packages/synapse/CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# @rabbitholegg/questdk-plugin-project
2+
3+
## 1.0.0-alpha.6
4+
5+
### Minor Changes
6+
7+
- [#71](https://github.com/rabbitholegg/questdk-plugins/pull/71) [`53d2ee5`](https://github.com/rabbitholegg/questdk-plugins/commit/53d2ee51b9479008bd549ebacba29fcfd82c4684) Thanks [@Quazia](https://github.com/Quazia)! - Update questdk instance and add Paraswap Stake action
8+
9+
## 1.0.0-alpha.5
10+
11+
### Patch Changes
12+
13+
- [#79](https://github.com/rabbitholegg/questdk-plugins/pull/79) [`d6843bd`](https://github.com/rabbitholegg/questdk-plugins/commit/d6843bd48d83bc84ca652ea081fd825f37a5f6d7) Thanks [@Quazia](https://github.com/Quazia)! - Remove preinstall hook and update questdk
14+
15+
## 1.0.0-alpha.4
16+
17+
### Minor Changes
18+
19+
- [#29](https://github.com/rabbitholegg/questdk-plugins/pull/29) [`5fa3757`](https://github.com/rabbitholegg/questdk-plugins/commit/5fa375719ade2563143bafb4b690f7a53671fa82) Thanks [@Quazia](https://github.com/Quazia)! - Re-release all pre-existing plugins

packages/synapse/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
When First creating a template for a project we generally want to find at most 4 transactions. Some bridges don't have a sense of a source and target chain, but for bridges that do we need more information. Some bridges might use the same ABI and address for all 4 of these transaction types!
2+
3+
## ETH/BASE TOKEN
4+
When trying to bridge ETH or the base network token (Matic for Polygon for instance) sometimes the pattern is different.
5+
We want to find a transaction from the base chain and the target chain that shows transferring ETH from and to those chains.
6+
In general we refer to moving value from a base chain (mainnet) to a higher order chain a `deposit` and returning back to a base chain a `withdrawal`.
7+
In the case where it's a general purpose bridge this process is simply a `transfer`. In that case you only need to find 1 transaction, not 2.
8+
9+
### Deposit ETH
10+
This handles the case where we have ETH that we want to bridge from Mainnet to L2. A lot of times this is handled differently. Often times this triggers a `payable` function and requires the `bridge` function to use the `value` param.
11+
12+
### Withdraw ETH
13+
This handles the case where we have ETH that we want to bridge from an L2 to Mainnet. Depending on this chain this can also be a payable, but often times will just be a separate contract address.
14+
15+
## ERC20
16+
ERC20 transactions are by far the most common for bridges.
17+
In some cases this might be the only transaction type you need to find, although most of the time ETH is handled different. If the protocol _always_ uses wrapped ETH, this may be the only transaction type.
18+
### Deposit ERC20
19+
Generally this is the same as moving from L2 to L1, but especially for native bridging sometimes there are precompiles for the withdraw process.
20+
### Withdraw ERC20
21+
22+
23+
24+
You can use the following example code to pull down test transactions in the correct format easily:
25+
https://viem.sh/docs/actions/public/getTransaction.html#example

packages/synapse/package.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "@rabbitholegg/questdk-plugin-project",
3+
"private": true,
4+
"version": "1.0.0-alpha.6",
5+
"type": "module",
6+
"exports": {
7+
"require": "./dist/cjs/index.js",
8+
"import": "./dist/esm/index.js",
9+
"types": "./dist/types/index.d.ts"
10+
},
11+
"main": "./dist/cjs/index.js",
12+
"module": "./dist/esm/index.js",
13+
"packageManager": "pnpm@8.3.1",
14+
"description": "",
15+
"scripts": {
16+
"bench": "vitest bench",
17+
"bench:ci": "CI=true vitest bench",
18+
"build": "pnpm run clean && pnpm run build:cjs && pnpm run build:esm && pnpm run build:types",
19+
"build:cjs": "tsc --project tsconfig.build.json --module commonjs --outDir ./dist/cjs --removeComments --verbatimModuleSyntax false && echo > ./dist/cjs/package.json '{\"type\":\"commonjs\"}'",
20+
"build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir ./dist/esm && echo > ./dist/esm/package.json '{\"type\":\"module\",\"sideEffects\":false}'",
21+
"build:types": "tsc --project tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap",
22+
"clean": "rimraf dist",
23+
"format": "rome format . --write",
24+
"lint": "rome check .",
25+
"lint:fix": "pnpm lint --apply",
26+
"test": "vitest dev",
27+
"test:cov": "vitest dev --coverage",
28+
"test:ci": "CI=true vitest --coverage",
29+
"test:ui": "vitest dev --ui"
30+
},
31+
"keywords": [],
32+
"author": "",
33+
"license": "ISC",
34+
"types": "./dist/types/index.d.ts",
35+
"typings": "./dist/types/index.d.ts",
36+
"devDependencies": {
37+
"@types/node": "^20.8.7",
38+
"@vitest/coverage-v8": "^0.33.0",
39+
"rimraf": "^5.0.5",
40+
"rome": "^12.1.3",
41+
"ts-node": "^10.9.1",
42+
"tsconfig": "workspace:*",
43+
"typescript": "^5.2.2",
44+
"vitest": "^0.33.0"
45+
},
46+
"dependencies": {
47+
"@rabbitholegg/questdk": "2.0.0-alpha.25",
48+
"viem": "^1.16.6"
49+
}
50+
}

packages/synapse/src/Synapse.test.ts

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import { GreaterThanOrEqual, apply } from '@rabbitholegg/questdk/filter'
2+
import { describe, expect, test } from 'vitest'
3+
import { bridge } from './Synapse.js'
4+
import {
5+
DEPOSIT_ETH,
6+
WITHDRAW_ERC20,
7+
WITHDRAW_ETH,
8+
DEPOSIT_ERC20,
9+
DEPOSIT_CCTP,
10+
WITHDRAW_CCTP
11+
12+
} from './test-transactions.js'
13+
import {
14+
ARBITRUM_CHAIN_ID,
15+
ETH_CHAIN_ID,
16+
BSC_CHAIN_ID
17+
} from './chain-ids.js'
18+
import { SYNAPSE_BRIDGE_FRAGMENTS } from './abi.js'
19+
import { parseEther } from 'viem'
20+
import { SynapseCCTPContract, getContractAddress, CHAIN_TO_ROUTER } from './contract-addresses'
21+
22+
23+
const ARBITRUM_USDCE_ADDRESS = '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8'
24+
const ARBITRUM_USDC_ADDRESS = '0xaf88d065e77c8cc2239327c5edb3a432268e5831'
25+
const ETHEREUM_USDC_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
26+
27+
// A random Ethereum Address
28+
const TEST_USER = '0xF57D86F6bFcc76AA2C7f62616B2436C60Ad397e2'
29+
30+
31+
describe('When given the Synapse plugin', () => {
32+
describe('When generating the filter', () => {
33+
34+
test('Should return a valid bridge action filter for L2 token tx', async () => {
35+
const filter = await bridge({
36+
sourceChainId: ARBITRUM_CHAIN_ID,
37+
destinationChainId: ETH_CHAIN_ID,
38+
tokenAddress: ARBITRUM_USDCE_ADDRESS,
39+
amount: GreaterThanOrEqual(100000n),
40+
recipient: TEST_USER
41+
})
42+
43+
expect(filter).to.deep.equal({
44+
chainId: ARBITRUM_CHAIN_ID,
45+
to: CHAIN_TO_ROUTER[ARBITRUM_CHAIN_ID],
46+
input: {
47+
$abi: SYNAPSE_BRIDGE_FRAGMENTS,
48+
to: TEST_USER,
49+
amount: {
50+
$gte: '100000'
51+
},
52+
chainId: ETH_CHAIN_ID,
53+
token: ARBITRUM_USDCE_ADDRESS,
54+
},
55+
})
56+
})
57+
test('Should return a valid transaction for a L1 -> L2 transaction (Non CCTP)'), async () => {
58+
const filter = await bridge({
59+
sourceChainId: ETH_CHAIN_ID,
60+
destinationChainId: ARBITRUM_CHAIN_ID,
61+
tokenAddress: ETHEREUM_USDC_ADDRESS,
62+
amount: GreaterThanOrEqual(100000n),
63+
recipient: TEST_USER
64+
})
65+
66+
expect(filter).to.deep.equal({
67+
chainId: ETH_CHAIN_ID,
68+
to: CHAIN_TO_ROUTER[ETH_CHAIN_ID],
69+
input: {
70+
$abi: SYNAPSE_BRIDGE_FRAGMENTS,
71+
to: TEST_USER,
72+
amount: {
73+
$gte: '100000'
74+
},
75+
chainId: ARBITRUM_CHAIN_ID,
76+
token: ETHEREUM_USDC_ADDRESS,
77+
},
78+
})
79+
}
80+
test('Should return a valid transaction for a L2 -> L1 transaction (Non CCTP)'), async () => {
81+
const filter = await bridge({
82+
sourceChainId: ARBITRUM_CHAIN_ID,
83+
destinationChainId: ETH_CHAIN_ID,
84+
tokenAddress: ARBITRUM_USDCE_ADDRESS,
85+
amount: GreaterThanOrEqual(100000n),
86+
recipient: TEST_USER
87+
})
88+
89+
expect(filter).to.deep.equal({
90+
chainId: ARBITRUM_CHAIN_ID,
91+
to: CHAIN_TO_ROUTER[ARBITRUM_CHAIN_ID],
92+
input: {
93+
$abi: SYNAPSE_BRIDGE_FRAGMENTS,
94+
to: TEST_USER,
95+
amount: {
96+
$gte: '100000'
97+
},
98+
chainId: ETH_CHAIN_ID,
99+
token: ARBITRUM_USDCE_ADDRESS,
100+
},
101+
})
102+
}
103+
// CCTP Transactions
104+
test('Should return a valid transaction for a L1 -> L2 transaction (CCTP)'), async () => {
105+
const filter = await bridge({
106+
sourceChainId: ETH_CHAIN_ID,
107+
destinationChainId: ARBITRUM_CHAIN_ID,
108+
tokenAddress: ETHEREUM_USDC_ADDRESS,
109+
amount: GreaterThanOrEqual(100000n),
110+
recipient: TEST_USER,
111+
contractAddress: getContractAddress(ETH_CHAIN_ID),
112+
})
113+
114+
expect(filter).to.deep.equal({
115+
chainId: ETH_CHAIN_ID,
116+
// Update
117+
to: SynapseCCTPContract[ETH_CHAIN_ID],
118+
input: {
119+
$abi: SYNAPSE_BRIDGE_FRAGMENTS,
120+
//Update
121+
sender: TEST_USER,
122+
amount: {
123+
$gte: '100000'
124+
},
125+
chainId: ARBITRUM_CHAIN_ID,
126+
token: ETHEREUM_USDC_ADDRESS,
127+
},
128+
})
129+
}
130+
test('Should return a valid transaction for a L2 -> L1 transaction (CCTP)'), async () => {
131+
const filter = await bridge({
132+
sourceChainId: ARBITRUM_CHAIN_ID,
133+
destinationChainId: ETH_CHAIN_ID,
134+
tokenAddress: ARBITRUM_USDC_ADDRESS,
135+
amount: GreaterThanOrEqual(100000n),
136+
recipient: TEST_USER,
137+
contractAddress: getContractAddress(ETH_CHAIN_ID),
138+
})
139+
140+
expect(filter).to.deep.equal({
141+
chainId: ARBITRUM_CHAIN_ID,
142+
to: SynapseCCTPContract[ARBITRUM_CHAIN_ID],
143+
input: {
144+
$abi: SYNAPSE_BRIDGE_FRAGMENTS,
145+
sender: TEST_USER,
146+
amount: {
147+
$gte: '100000'
148+
},
149+
chainId: ETH_CHAIN_ID,
150+
token: ARBITRUM_USDC_ADDRESS,
151+
},
152+
})
153+
}
154+
155+
describe('When applying the filter', () => {
156+
test('should pass filter with valid L1 ETH tx', async () => {
157+
const transaction = DEPOSIT_ETH
158+
const filter = await bridge({
159+
sourceChainId: ETH_CHAIN_ID,
160+
destinationChainId: ARBITRUM_CHAIN_ID,
161+
tokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
162+
amount: GreaterThanOrEqual(parseEther('.2')),
163+
})
164+
expect(apply(transaction, filter)).to.be.true
165+
})
166+
test('should pass filter with valid L2 ETH tx', async () => {
167+
const transaction = WITHDRAW_ETH
168+
const filter = await bridge({
169+
sourceChainId: ARBITRUM_CHAIN_ID,
170+
destinationChainId: ETH_CHAIN_ID,
171+
tokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
172+
amount: GreaterThanOrEqual(parseEther('.259')),
173+
174+
})
175+
expect(apply(transaction, filter)).to.be.true
176+
})
177+
test('should pass filter with valid L1 Token tx', async () => {
178+
const transaction = DEPOSIT_ERC20
179+
const filter = await bridge({
180+
sourceChainId: ETH_CHAIN_ID,
181+
destinationChainId: BSC_CHAIN_ID,
182+
tokenAddress: ETHEREUM_USDC_ADDRESS,
183+
amount: GreaterThanOrEqual('9'),
184+
})
185+
expect(apply(transaction, filter)).to.be.true
186+
})
187+
test('should pass filter with valid L2 token tx', async () => {
188+
const transaction = WITHDRAW_ERC20
189+
const filter = await bridge({
190+
sourceChainId: ARBITRUM_CHAIN_ID ,
191+
destinationChainId: ETH_CHAIN_ID,
192+
tokenAddress: ARBITRUM_USDCE_ADDRESS ,
193+
amount: GreaterThanOrEqual('4006'),
194+
})
195+
expect(apply(transaction, filter)).to.be.true
196+
})
197+
// These are going to fail ? Im not sure how the contract address thing works.
198+
test('should pass filter with valid CCTP Deposit tx', async () => {
199+
const transaction = DEPOSIT_CCTP
200+
const filter = await bridge({
201+
sourceChainId: ETH_CHAIN_ID ,
202+
destinationChainId: ARBITRUM_CHAIN_ID,
203+
tokenAddress: ETHEREUM_USDC_ADDRESS ,
204+
amount: GreaterThanOrEqual('299'),
205+
contractAddress: '0xd359bc471554504f683fbd4f6e36848612349ddf',
206+
})
207+
expect(apply(transaction, filter)).to.be.true
208+
})
209+
test('should pass filter with valid CCTP Withdraw tx', async () => {
210+
const transaction = WITHDRAW_CCTP
211+
const filter = await bridge({
212+
sourceChainId: ARBITRUM_CHAIN_ID ,
213+
destinationChainId: ETH_CHAIN_ID,
214+
tokenAddress: ARBITRUM_USDC_ADDRESS ,
215+
amount: GreaterThanOrEqual('94'),
216+
contractAddress: '0xd359bc471554504f683fbd4f6e36848612349ddf',
217+
})
218+
expect(apply(transaction, filter)).to.be.true
219+
})
220+
})
221+
})
222+
})

packages/synapse/src/Synapse.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
2+
import { type TransactionFilter, type BridgeActionParams, compressJson } from '@rabbitholegg/questdk'
3+
import { type Address } from 'viem'
4+
import { SYNAPSE_CCTP_ROUTER, CHAIN_TO_ROUTER } from './contract-addresses'
5+
import { SYNAPSE_BRIDGE_FRAGMENTS } from './abi'
6+
import { CHAIN_ID_ARRAY } from './chain-ids'
7+
import { Token } from './Token'
8+
import * as tokens from './tokens'
9+
10+
const allTokens: Token[] = Object.values(tokens)
11+
12+
export const bridge = async (bridge: BridgeActionParams): Promise<TransactionFilter> => {
13+
// This is the information we'll use to compose the Transaction object
14+
const {
15+
sourceChainId,
16+
destinationChainId,
17+
contractAddress = null,
18+
tokenAddress,
19+
amount,
20+
recipient,
21+
} = bridge
22+
23+
// This if statement checks if the transaction is a CCTP transaction.
24+
if (contractAddress == '0xd359bc471554504f683fbd4f6e36848612349ddf') {
25+
return compressJson({
26+
chainId: sourceChainId,
27+
to: SYNAPSE_CCTP_ROUTER[sourceChainId],
28+
input: {
29+
$abi: SYNAPSE_BRIDGE_FRAGMENTS,
30+
sender: recipient,
31+
amount: amount,
32+
chainId: destinationChainId,
33+
token: tokenAddress,
34+
},
35+
})
36+
}
37+
// We always want to return a compressed JSON object which we'll transform into a TransactionFilter (for non cctp)
38+
return compressJson({
39+
chainId: sourceChainId, // The chainId of the source chain
40+
to: CHAIN_TO_ROUTER[sourceChainId],
41+
input: {
42+
$abi: SYNAPSE_BRIDGE_FRAGMENTS, // The ABI of the bridge contract
43+
to: recipient, // The recipient of tokens
44+
amount: amount, // The amount of tokens to send
45+
chainId: destinationChainId, // The chainId of the destination chian
46+
token: tokenAddress, // The address of the token to be recieved
47+
},
48+
})
49+
}
50+
51+
export const getSupportedTokenAddresses = async (chainId: number): Promise<Address[]> => {
52+
const supportedTokens = allTokens.filter(token => token.addresses.hasOwnProperty(chainId));
53+
return supportedTokens.map(token => token.addresses[chainId]) as Address[];
54+
}
55+
56+
57+
export const getSupportedChainIds = async (): Promise<number[]> => {
58+
return CHAIN_ID_ARRAY
59+
}
60+

0 commit comments

Comments
 (0)