-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #85 from mmackz/symbiosis-bridge
- Loading branch information
Showing
16 changed files
with
833 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.