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(symbiosis): add support for symbiosis bridge #85

Merged
merged 36 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7589741
feat(symbiosis): initialize plugin structure from template
mmackz Oct 21, 2023
ce28c39
feat(symbiosis): add ABI needed to decode calldata
mmackz Oct 22, 2023
605d548
feat(symbiosis): add chain id constants
mmackz Oct 22, 2023
49e8daa
chore(symbiosis): add symbiosis sdk and ethers
mmackz Oct 22, 2023
eaf9e39
feat(symbiosis): set up transaction filter structure
mmackz Oct 22, 2023
c5e1f92
feat(symbiosis): use sdk to determine current bridge contract
mmackz Oct 22, 2023
34301c0
refactor(symbiosis): move check for recipient to destination chain
mmackz Oct 22, 2023
bb38d2f
feat(symbiosis): check tokenAddress against approvedTokens
mmackz Oct 22, 2023
797aff8
feat(symbiosis): get bridge contract from sdk if not provided
mmackz Oct 22, 2023
76bfb38
feat(symbiosis): add type assertion for bridgeContract address
mmackz Oct 23, 2023
e2a6945
refactor(symbiosis): remove unused types
mmackz Oct 23, 2023
044d366
feat(symbiosis): implement getSupportedChainIds function
mmackz Oct 23, 2023
4e01bbc
feat(symbiosis): map chainId to tokens
mmackz Oct 23, 2023
bc954a3
refactor(symbiosis): change file name
mmackz Oct 23, 2023
3995a83
feat(symbiosis): structure and add test transactions
mmackz Oct 23, 2023
a1e32f5
feat(symbiosis): implement getSupportedTokenAddresses function
mmackz Oct 23, 2023
62d4362
chore(pnpm): format
mmackz Oct 23, 2023
b162676
test(symbiosis): add tests for verfiy filter functionality
mmackz Oct 24, 2023
7a01921
fix(symbiosis): resolved typescript errors
mmackz Oct 24, 2023
de1031b
test(symbiosis): add additional tests
mmackz Oct 24, 2023
a0734bf
feat(symbiosis): add plugin to registry
mmackz Oct 24, 2023
108b630
chore(pnpm): format
mmackz Oct 24, 2023
d30b44e
test(symbiosis): add tests for fail conditions
mmackz Oct 24, 2023
ca787a6
fix(symbiosis): fix type error
mmackz Oct 24, 2023
473f96d
chore(changeset): generate changes for symbiosis bridge
mmackz Oct 24, 2023
bed7516
chore(pnpm): update lockfile
mmackz Oct 24, 2023
d9e3d88
fix(symbiosis): change token address for final fail tx
mmackz Oct 25, 2023
625f23d
Merge branch 'upstream/main' into symbiosis-bridge
mmackz Oct 25, 2023
9c33334
test(symbiosis): add additional test
mmackz Oct 26, 2023
f6c76fb
Merge branch 'upstream/main' into symbiosis-bridge
mmackz Oct 26, 2023
9ebda59
docs(symbiosis): add README.md
mmackz Oct 26, 2023
1233263
refactor(symbiosis): use abiAbstract method
mmackz Oct 26, 2023
41348ca
chore(symbiosis): remove private field from plugins package.json
mmackz Oct 26, 2023
3ab4f83
docs(symbiosis): update README.md
mmackz Oct 26, 2023
b054a10
refactor(symbiosis): clean up and remove duplicate code
mmackz Oct 26, 2023
d41d31b
test(symbiosis): add additional test
mmackz Oct 26, 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
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.
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) => {
Copy link
Member

Choose a reason for hiding this comment

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

I love this pattern - NICE
Gotta convert the other plugins to use this this is slick

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: {
Copy link
Member

Choose a reason for hiding this comment

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

Right here you should be able to do

otherSideCalldata: {
    $abiAbstract: metaBurnABI,
    _metaBurnTransaction: {
        chainID: destinationChainId,
        chain2address: recipient,
    }
}

And that should work - I wouldn't bother cause this looks like it's working fine and it's more precise but just to see how you'd use that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I like this solution much better, and it allows us to remove the abi for the metaSynthesize function.

Im going to go ahead and implement this.

$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