Skip to content

Commit

Permalink
Merge pull request #85 from mmackz/symbiosis-bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
Quazia authored Oct 30, 2023
2 parents eff7087 + d41d31b commit 391468f
Show file tree
Hide file tree
Showing 16 changed files with 833 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .changeset/two-pandas-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rabbitholegg/questdk-plugin-symbiosis": minor
"@rabbitholegg/questdk-plugin-registry": minor
---

implement symbiosis bridge plugin
1 change: 1 addition & 0 deletions packages/registry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@rabbitholegg/questdk-plugin-paraswap": "workspace:*",
"@rabbitholegg/questdk-plugin-polygon": "workspace:*",
"@rabbitholegg/questdk-plugin-stargate": "workspace:*",
"@rabbitholegg/questdk-plugin-symbiosis": "workspace:*",
"@rabbitholegg/questdk-plugin-tally": "workspace:*",
"@rabbitholegg/questdk-plugin-uniswap": "workspace:*",
"viem": "^1.16.6"
Expand Down
2 changes: 2 additions & 0 deletions packages/registry/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Tally } from '@rabbitholegg/questdk-plugin-tally'
import { BasePaint } from '@rabbitholegg/questdk-plugin-basepaint'
import { Hyphen } from '@rabbitholegg/questdk-plugin-hyphen'
import { Paraswap } from '@rabbitholegg/questdk-plugin-paraswap'
import { Symbiosis } from '@rabbitholegg/questdk-plugin-symbiosis'
import { ENTRYPOINT } from './contract-addresses'

export const plugins: Record<string, IActionPlugin> = {
Expand All @@ -42,6 +43,7 @@ export const plugins: Record<string, IActionPlugin> = {
[BasePaint.pluginId]: BasePaint,
[Hyphen.pluginId]: Hyphen,
[Paraswap.pluginId]: Paraswap,
[Symbiosis.pluginId]: Symbiosis,
}

export const getPlugin = (pluginId: string) => {
Expand Down
Empty file added packages/symbiosis/CHANGELOG.md
Empty file.
21 changes: 21 additions & 0 deletions packages/symbiosis/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Symbiosis Bridge Plugin

This plugin is designed to filter valid bridge transactions using the Symbiosis cross-chain bridge.

## Overview

The Symbiosis bridge plugin prioritizes the most popular assets on each chain, which include ETH, stablecoins, and certain chain-specific tokens like OP and ARB. By leaving the tokenAddress undefined, the filter should allow any asset to pass, as long as all the other criteria is met. This allows for additional flexibility for quest creators. The plugin utilizes the symbiosis SDK to get the latest contract addresses for their metaRouter contracts. An overview of how the bridge operates can be [found here](https://docs.symbiosis.finance/main-concepts/cross-chain-swaps-with-symbiosis)

## Filtering Criteria

The plugin filters transactions based on certain predefined criteria to identify valid bridge transactions. The criteria can include the following aspects:

- Source chain and Destination chain
- Involved assets (e.g., ETH, USDC, wETH, OP, ARB)
- Amount
- Contract Address
- Recipient

## Sample Transaction
- https://etherscan.io/tx/0xc97deae76c21cb57498d2873babd212fa589eae9e4d2e979f4a10a3428f376ee

52 changes: 52 additions & 0 deletions packages/symbiosis/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "@rabbitholegg/questdk-plugin-symbiosis",
"version": "1.0.0-alpha.4",
"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",
"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": "2.0.0-alpha.22",
"ethers": "^5.2.0",
"symbiosis-js-sdk": "^3.0.9",
"viem": "^1.2.15"
}
}
138 changes: 138 additions & 0 deletions packages/symbiosis/src/Symbiosis.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { GreaterThanOrEqual, apply } from '@rabbitholegg/questdk/filter'
import { describe, expect, test } from 'vitest'
import { bridge } from './Symbiosis'
import {
PASSING_TEST_TRANSACTIONS,
FAILING_TEST_TRANSACTIONS,
controlTransaction,
} from './test-transactions'
import { metaBurnABI, metaRouteABI } from './abi'
import { ETH_CHAIN_ID, OPTIMISM_CHAIN_ID } from './constants'
import { symbiosis } from './symbiosis-sdk'

const TEST_USER = '0xB7e98B3F16CC915B9C7a321c1bd95fa406BDbabe'
const OPTIMISM_USDCe_ADDRESS = '0x7F5c764cBc14f9669B88837ca1490cCa17c31607'

describe('Given the symbiosis plugin', () => {
describe('When handling the bridge', () => {
test('should return a valid action filter', async () => {
const filter = await bridge({
sourceChainId: OPTIMISM_CHAIN_ID,
destinationChainId: ETH_CHAIN_ID,
tokenAddress: OPTIMISM_USDCe_ADDRESS,
amount: GreaterThanOrEqual(100000n),
recipient: TEST_USER,
})

expect(filter).to.deep.equal({
chainId: 10,
to: symbiosis.metaRouter(10).address,
input: {
$abi: metaRouteABI,
_metarouteTransaction: {
approvedTokens: [OPTIMISM_USDCe_ADDRESS],
amount: {
$gte: '100000',
},
otherSideCalldata: {
$abiAbstract: metaBurnABI,
_metaBurnTransaction: {
chainID: 1,
chain2address: TEST_USER,
},
},
},
},
})
})
})
describe('should pass filter with valid transactions', () => {
PASSING_TEST_TRANSACTIONS.forEach((testTransaction) => {
test(testTransaction.description, async () => {
const {
transaction,
destinationChainId,
tokenAddress,
amount,
recipient,
} = testTransaction

const filter = await bridge({
sourceChainId: transaction.chainId,
destinationChainId,
tokenAddress,
amount: GreaterThanOrEqual(amount),
recipient,
})
expect(apply(transaction, filter)).to.be.true
})
})
})
describe('should not pass filter with invalid parameters', () => {
test('when sourceChainId is incorrect', async () => {
const { transaction, tokenAddress, recipient } = controlTransaction

const filter = await bridge({
sourceChainId: 1, // 42161
destinationChainId: 5000,
tokenAddress,
amount: GreaterThanOrEqual(10000n),
recipient,
})
expect(apply(transaction, filter)).to.be.false
})
test('when bridge contract address is incorrect', async () => {
const { transaction, tokenAddress, recipient } = controlTransaction

const filter = await bridge({
sourceChainId: 42161,
destinationChainId: 5000,
contractAddress: '0x1DCfbC3fA01b2a86bC3a3f43479cCe9E8D438Adc',
tokenAddress,
amount: GreaterThanOrEqual(10000n),
recipient,
})
expect(apply(transaction, filter)).to.be.false
})
FAILING_TEST_TRANSACTIONS.forEach((testTransaction) => {
test(testTransaction.description, async () => {
const {
transaction,
destinationChainId,
tokenAddress,
amount,
recipient,
} = testTransaction

const filter = await bridge({
sourceChainId: transaction.chainId,
destinationChainId,
tokenAddress,
amount: GreaterThanOrEqual(amount),
recipient,
})
expect(apply(transaction, filter)).to.be.false
})
})
})
describe('control transaction should pass filter', () => {
test(controlTransaction.description, async () => {
const {
transaction,
destinationChainId,
tokenAddress,
amount,
recipient,
} = controlTransaction

const filter = await bridge({
sourceChainId: transaction.chainId,
destinationChainId,
tokenAddress,
amount: GreaterThanOrEqual(amount),
recipient,
})
expect(apply(transaction, filter)).to.be.true
})
})
})
54 changes: 54 additions & 0 deletions packages/symbiosis/src/Symbiosis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
type TransactionFilter,
type BridgeActionParams,
compressJson,
} from '@rabbitholegg/questdk'
import { type Address } from 'viem'
import { CHAIN_ID_ARRAY, CHAIN_TO_TOKENS } from './constants'
import { metaBurnABI, metaRouteABI } from './abi'
import { symbiosis } from './symbiosis-sdk'

export const bridge = async (
bridge: BridgeActionParams,
): Promise<TransactionFilter> => {
const {
sourceChainId,
destinationChainId,
contractAddress,
tokenAddress,
amount,
recipient,
} = bridge

const bridgeContract: Address =
contractAddress ?? (symbiosis.metaRouter(sourceChainId).address as Address)

return compressJson({
chainId: sourceChainId,
to: bridgeContract,
input: {
$abi: metaRouteABI,
_metarouteTransaction: {
approvedTokens: tokenAddress ? [tokenAddress] : undefined, // if tokenAddress is undefined, any input token will pass filter
amount: amount,
otherSideCalldata: {
$abiAbstract: metaBurnABI,
_metaBurnTransaction: {
chainID: destinationChainId,
chain2address: recipient,
},
},
},
},
})
}

export const getSupportedTokenAddresses = async (
_chainId: number,
): Promise<Address[]> => {
return CHAIN_TO_TOKENS[_chainId] ?? []
}

export const getSupportedChainIds = async (): Promise<number[]> => {
return CHAIN_ID_ARRAY
}
Loading

0 comments on commit 391468f

Please sign in to comment.