Skip to content

Commit

Permalink
Merge pull request #118 from rabbitholegg/add_synapse_bridge
Browse files Browse the repository at this point in the history
Feat(synapse): add bridge
  • Loading branch information
Quazia authored Dec 9, 2023
2 parents 79ccc6b + 9b61256 commit 5c5f526
Show file tree
Hide file tree
Showing 18 changed files with 2,282 additions and 1,206 deletions.
6 changes: 6 additions & 0 deletions .changeset/serious-paws-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rabbitholegg/questdk-plugin-registry": minor
"@rabbitholegg/questdk-plugin-synapse": minor
---

Release Synapse Bridge
1 change: 1 addition & 0 deletions packages/registry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@rabbitholegg/questdk-plugin-balancer": "workspace:*",
"@rabbitholegg/questdk-plugin-traderjoe": "workspace:*",
"@rabbitholegg/questdk-plugin-zora": "workspace:*",
"@rabbitholegg/questdk-plugin-synapse": "workspace:*",
"viem": "^1.16.6"
}
}
8 changes: 5 additions & 3 deletions packages/registry/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,21 @@ import { OkuTrade } from '@rabbitholegg/questdk-plugin-okutrade'
import { Zora } from '@rabbitholegg/questdk-plugin-zora'
import { Balancer } from '@rabbitholegg/questdk-plugin-balancer'
import { TraderJoe } from '@rabbitholegg/questdk-plugin-traderjoe'
import { Synapse } from '@rabbitholegg/questdk-plugin-synapse'
import { ENTRYPOINT } from './contract-addresses'

export const plugins: Record<string, IActionPlugin> = {
[Connext.pluginId]: Connext,
[Uniswap.pluginId]: Uniswap,
[Stargate.pluginId]: Stargate,
[Across.pluginId]: Across,
[Polygon.pluginId]: Polygon,
[Optimism.pluginId]: Optimism,
[Hop.pluginId]: Hop,
[Arbitrum.pluginId]: Arbitrum,
[Across.pluginId]: Across,
[Optimism.pluginId]: Optimism,
[GMX.pluginId]: GMX,
[Tally.pluginId]: Tally,
[Camelot.pluginId]: Camelot,
[Tally.pluginId]: Tally,
[BasePaint.pluginId]: BasePaint,
[Hyphen.pluginId]: Hyphen,
[Paraswap.pluginId]: Paraswap,
Expand All @@ -55,6 +56,7 @@ export const plugins: Record<string, IActionPlugin> = {
[Zora.pluginId]: Zora,
[Balancer.pluginId]: Balancer,
[TraderJoe.pluginId]: TraderJoe,
[Synapse.pluginId]: Synapse,
}

export const getPlugin = (pluginId: string) => {
Expand Down
Empty file added packages/synapse/CHANGELOG.md
Empty file.
Empty file added packages/synapse/README.md
Empty file.
50 changes: 50 additions & 0 deletions packages/synapse/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@rabbitholegg/questdk-plugin-synapse",
"private": false,
"version": "1.0.0-alpha.1",
"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"
}
}
276 changes: 276 additions & 0 deletions packages/synapse/src/Synapse.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
import { GreaterThanOrEqual, apply } from '@rabbitholegg/questdk/filter'
import { describe, expect, test } from 'vitest'
import { bridge, getSupportedTokenAddresses } from './Synapse.js'
import {
DEPOSIT_ETH,
WITHDRAW_ERC20,
WITHDRAW_ETH,
DEPOSIT_ERC20,
DEPOSIT_CCTP,
WITHDRAW_CCTP,
} from './test-transactions.js'
import {
ARBITRUM_CHAIN_ID,
ETH_CHAIN_ID,
BSC_CHAIN_ID,
CHAIN_ID_ARRAY,
} from './chain-ids.js'
import { SYNAPSE_BRIDGE_FRAGMENTS } from './abi.js'
import { parseEther, parseUnits, zeroAddress } from 'viem'
import {
SynapseCCTPContract,
getContractAddress,
CHAIN_TO_ROUTER,
SYNAPSE_CCTP_ROUTER,
} from './contract-addresses'

const ARBITRUM_USDCE_ADDRESS = '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8'
const ARBITRUM_USDC_ADDRESS = '0xaf88d065e77c8cc2239327c5edb3a432268e5831'
const ETHEREUM_USDC_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'

// A random Ethereum Address
const TEST_USER = '0xF57D86F6bFcc76AA2C7f62616B2436C60Ad397e2'

describe('When given the Synapse 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_CHAIN_ID,
destinationChainId: ETH_CHAIN_ID,
tokenAddress: ARBITRUM_USDCE_ADDRESS,
amount: GreaterThanOrEqual(100000n),
recipient: TEST_USER,
})
expect(filter).to.deep.equal({
chainId: ARBITRUM_CHAIN_ID,
to: {
$or: [
CHAIN_TO_ROUTER[ARBITRUM_CHAIN_ID].toLowerCase(),
SYNAPSE_CCTP_ROUTER[ARBITRUM_CHAIN_ID].toLowerCase(),
],
},
input: {
$abi: SYNAPSE_BRIDGE_FRAGMENTS,
$and: [
{
amount: {
$gte: '100000',
},
chainId: ETH_CHAIN_ID,
token: ARBITRUM_USDCE_ADDRESS,
},
{ $or: [{ sender: TEST_USER }, { to: TEST_USER }] },
],
},
})
})
test('Should return a valid transaction for a L1 -> L2 transaction (Non CCTP)', async () => {
const filter = await bridge({
sourceChainId: ETH_CHAIN_ID,
destinationChainId: ARBITRUM_CHAIN_ID,
tokenAddress: ETHEREUM_USDC_ADDRESS,
amount: GreaterThanOrEqual(100000n),
recipient: TEST_USER,
})

expect(filter).to.deep.equal({
chainId: ETH_CHAIN_ID,
to: {
$or: [
CHAIN_TO_ROUTER[ETH_CHAIN_ID].toLowerCase(),
SYNAPSE_CCTP_ROUTER[ETH_CHAIN_ID].toLowerCase(),
],
},
input: {
$abi: SYNAPSE_BRIDGE_FRAGMENTS,
$and: [
{
amount: {
$gte: '100000',
},
chainId: ARBITRUM_CHAIN_ID,
token: ETHEREUM_USDC_ADDRESS,
},
{ $or: [{ sender: TEST_USER }, { to: TEST_USER }] },
],
},
})
})
test('Should return a valid transaction for a L2 -> L1 transaction (Non CCTP)', async () => {
const filter = await bridge({
sourceChainId: ARBITRUM_CHAIN_ID,
destinationChainId: ETH_CHAIN_ID,
tokenAddress: ARBITRUM_USDCE_ADDRESS,
amount: GreaterThanOrEqual(100000n),
recipient: TEST_USER,
})

expect(filter).to.deep.equal({
chainId: ARBITRUM_CHAIN_ID,
to: {
$or: [
CHAIN_TO_ROUTER[ARBITRUM_CHAIN_ID].toLowerCase(),
SYNAPSE_CCTP_ROUTER[ARBITRUM_CHAIN_ID].toLowerCase(),
],
},
input: {
$abi: SYNAPSE_BRIDGE_FRAGMENTS,
$and: [
{
amount: {
$gte: '100000',
},
chainId: ETH_CHAIN_ID,
token: ARBITRUM_USDCE_ADDRESS,
},
{ $or: [{ sender: TEST_USER }, { to: TEST_USER }] },
],
},
})
})
// // CCTP Transactions
test('Should return a valid transaction for a L1 -> L2 transaction (CCTP)', async () => {
const filter = await bridge({
sourceChainId: ETH_CHAIN_ID,
destinationChainId: ARBITRUM_CHAIN_ID,
tokenAddress: ETHEREUM_USDC_ADDRESS,
amount: GreaterThanOrEqual(100000n),
recipient: TEST_USER,
contractAddress: getContractAddress(ETH_CHAIN_ID),
})

expect(filter).to.deep.equal({
chainId: ETH_CHAIN_ID,
to: getContractAddress(ETH_CHAIN_ID),
input: {
$abi: SYNAPSE_BRIDGE_FRAGMENTS,
$and: [
{
amount: {
$gte: '100000',
},
chainId: ARBITRUM_CHAIN_ID,
token: ETHEREUM_USDC_ADDRESS,
},
{ $or: [{ sender: TEST_USER }, { to: TEST_USER }] },
],
},
})
})
test('Should return a valid transaction for a L2 -> L1 transaction (CCTP)', async () => {
const filter = await bridge({
sourceChainId: ARBITRUM_CHAIN_ID,
destinationChainId: ETH_CHAIN_ID,
tokenAddress: ARBITRUM_USDC_ADDRESS,
amount: GreaterThanOrEqual(100000n),
recipient: TEST_USER,
contractAddress: getContractAddress(ETH_CHAIN_ID),
})

expect(filter).to.deep.equal({
chainId: ARBITRUM_CHAIN_ID,
to: SynapseCCTPContract[ARBITRUM_CHAIN_ID],
input: {
$abi: SYNAPSE_BRIDGE_FRAGMENTS,
$and: [
{
amount: {
$gte: '100000',
},
chainId: ETH_CHAIN_ID,
token: ARBITRUM_USDC_ADDRESS,
},
{ $or: [{ sender: TEST_USER }, { to: TEST_USER }] },
],
},
})
})
})

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_CHAIN_ID,
destinationChainId: ARBITRUM_CHAIN_ID,
tokenAddress: zeroAddress,
amount: GreaterThanOrEqual(parseEther('.2')),
})
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_CHAIN_ID,
destinationChainId: ETH_CHAIN_ID,
tokenAddress: zeroAddress,
amount: GreaterThanOrEqual(parseEther('.259')),
})
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_CHAIN_ID,
destinationChainId: BSC_CHAIN_ID,
tokenAddress: ETHEREUM_USDC_ADDRESS,
amount: GreaterThanOrEqual(parseUnits('9', 6)),
})
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_CHAIN_ID,
destinationChainId: ETH_CHAIN_ID,
tokenAddress: ARBITRUM_USDCE_ADDRESS,
amount: GreaterThanOrEqual(parseUnits('4006', 6)),
})
expect(apply(transaction, filter)).to.be.true
})
// These are going to fail ? Im not sure how the contract address thing works.
test('should pass filter with valid CCTP Deposit tx', async () => {
const transaction = DEPOSIT_CCTP
const filter = await bridge({
sourceChainId: ETH_CHAIN_ID,
destinationChainId: ARBITRUM_CHAIN_ID,
tokenAddress: ETHEREUM_USDC_ADDRESS,
amount: GreaterThanOrEqual(parseUnits('300', 6)),
contractAddress: '0xd359bc471554504f683fbd4f6e36848612349ddf',
})
expect(apply(transaction, filter)).to.be.true
})
test('should pass filter with valid CCTP Withdraw tx', async () => {
const transaction = WITHDRAW_CCTP
const filter = await bridge({
sourceChainId: ARBITRUM_CHAIN_ID,
destinationChainId: ETH_CHAIN_ID,
tokenAddress: ARBITRUM_USDC_ADDRESS,
amount: GreaterThanOrEqual(parseUnits('95', 6)),
contractAddress: '0xd359bc471554504f683fbd4f6e36848612349ddf',
})
expect(apply(transaction, filter)).to.be.true
})
})

describe('should return a valid list of tokens for each supported chain', () => {
CHAIN_ID_ARRAY.forEach((chainId) => {
// chain 5395 DFK_CHAIN has no tokens at the moment
if (chainId !== 5395) {
test(`for chainId: ${chainId}`, async () => {
const tokens = await getSupportedTokenAddresses(chainId)
const addressRegex = /^0x[a-fA-F0-9]{40}$/
expect(tokens).to.be.an('array')
expect(tokens).to.have.length.greaterThan(1)
expect(tokens).to.have.length.lessThan(100)
tokens.forEach((token) => {
expect(token).to.match(
addressRegex,
`Token address ${token} is not a valid Ethereum address`,
)
})
})
}
})
})
})
Loading

0 comments on commit 5c5f526

Please sign in to comment.