-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
386 additions
and
1 deletion.
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,5 @@ | ||
--- | ||
'@protocolink/logics': patch | ||
--- | ||
|
||
support OpenOceanV2 on Metis |
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,2 @@ | ||
CHAIN_ID=1088 | ||
HTTP_RPC_URL=https://andromeda.metis.io/?owner=1088 |
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
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,37 @@ | ||
import * as common from '@protocolink/common'; | ||
|
||
export interface Config { | ||
chainId: number; | ||
exchangeAddress: string; | ||
gasPrice: string; | ||
} | ||
|
||
export const configs: Config[] = [ | ||
{ | ||
chainId: common.ChainId.metis, | ||
exchangeAddress: '0x6352a56caadC4F1E25CD6c75970Fa768A3304e64', | ||
gasPrice: '20', | ||
}, | ||
]; | ||
|
||
export const [supportedChainIds, configMap] = configs.reduce( | ||
(accumulator, config) => { | ||
accumulator[0].push(config.chainId); | ||
accumulator[1][config.chainId] = config; | ||
return accumulator; | ||
}, | ||
[[], {}] as [number[], Record<number, Config>] | ||
); | ||
|
||
export function getExchangeAddress(chainId: number) { | ||
return configMap[chainId].exchangeAddress; | ||
} | ||
|
||
export function getGasPrice(chainId: number) { | ||
return configMap[chainId].gasPrice; | ||
} | ||
|
||
export function getApiUrl(chainId: number) { | ||
const url = 'https://open-api.openocean.finance/v3'; | ||
return url + `/${chainId}`; | ||
} |
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,2 @@ | ||
export * from './configs'; | ||
export * from './logic.swap-token'; |
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,67 @@ | ||
import { LogicTestCaseWithChainId } from 'test/types'; | ||
import { SwapTokenLogic, SwapTokenLogicFields, SwapTokenLogicOptions } from './logic.swap-token'; | ||
import * as common from '@protocolink/common'; | ||
import { constants, utils } from 'ethers'; | ||
import * as core from '@protocolink/core'; | ||
import { expect } from 'chai'; | ||
import { getExchangeAddress } from './configs'; | ||
import { metisTokens } from 'src/logics/openocean-v2/tokens'; | ||
|
||
describe('OpenOceanV2 SwapTokenLogic', function () { | ||
context('Test getTokenList', async function () { | ||
SwapTokenLogic.supportedChainIds.forEach((chainId) => { | ||
it(`network: ${common.toNetworkId(chainId)}`, async function () { | ||
const logic = new SwapTokenLogic(chainId); | ||
const tokenList = await logic.getTokenList(); | ||
expect(tokenList).to.have.lengthOf.above(0); | ||
}); | ||
}); | ||
}); | ||
|
||
context('Test build', function () { | ||
const testCases: LogicTestCaseWithChainId<SwapTokenLogicFields, SwapTokenLogicOptions>[] = [ | ||
{ | ||
chainId: common.ChainId.metis, | ||
fields: { | ||
input: new common.TokenAmount(metisTokens.WETH, '1'), | ||
output: new common.TokenAmount(metisTokens.USDC, '0'), | ||
slippage: 100, | ||
}, | ||
options: { account: '0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa' }, | ||
}, | ||
{ | ||
chainId: common.ChainId.metis, | ||
fields: { | ||
input: new common.TokenAmount(metisTokens.METIS, '1'), | ||
output: new common.TokenAmount(metisTokens.USDC, '0'), | ||
slippage: 100, | ||
}, | ||
options: { account: '0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa' }, | ||
}, | ||
{ | ||
chainId: common.ChainId.metis, | ||
fields: { | ||
input: new common.TokenAmount(metisTokens.USDC, '1'), | ||
output: new common.TokenAmount(metisTokens.METIS, '0'), | ||
slippage: 100, | ||
}, | ||
options: { account: '0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa' }, | ||
}, | ||
]; | ||
|
||
testCases.forEach(({ chainId, fields, options }) => { | ||
it(`${fields.input.token.symbol} to ${fields.output.token.symbol}`, async function () { | ||
const logic = new SwapTokenLogic(chainId); | ||
const routerLogic = await logic.build(fields, options); | ||
const { input } = fields; | ||
|
||
expect(routerLogic.to).to.eq(getExchangeAddress(chainId)); | ||
expect(utils.isBytesLike(routerLogic.data)).to.be.true; | ||
expect(routerLogic.inputs[0].balanceBps).to.eq(core.BPS_NOT_USED); | ||
expect(routerLogic.inputs[0].amountOrOffset).to.eq(input.amountWei); | ||
expect(routerLogic.approveTo).to.eq(constants.AddressZero); | ||
expect(routerLogic.callback).to.eq(constants.AddressZero); | ||
}); | ||
}); | ||
}); | ||
}); |
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,93 @@ | ||
import { axios } from 'src/utils'; | ||
import * as common from '@protocolink/common'; | ||
import * as core from '@protocolink/core'; | ||
import { getApiUrl, getGasPrice, supportedChainIds } from './configs'; | ||
import { slippageToOpenOcean, slippageToProtocolink } from './slippage'; | ||
|
||
export type SwapTokenLogicTokenList = common.Token[]; | ||
|
||
export type SwapTokenLogicParams = core.TokenToTokenExactInParams<{ | ||
slippage?: number; | ||
disabledDexIds?: string; | ||
}>; | ||
|
||
export type SwapTokenLogicFields = core.TokenToTokenExactInFields<{ | ||
slippage?: number; | ||
disabledDexIds?: string; | ||
}>; | ||
|
||
export type SwapTokenLogicOptions = Pick<core.GlobalOptions, 'account'>; | ||
|
||
@core.LogicDefinitionDecorator() | ||
export class SwapTokenLogic | ||
extends core.Logic | ||
implements core.LogicTokenListInterface, core.LogicOracleInterface, core.LogicBuilderInterface | ||
{ | ||
static readonly supportedChainIds = supportedChainIds; | ||
|
||
async getTokenList() { | ||
const url = getApiUrl(this.chainId); | ||
const resp = await axios.get(url + '/tokenList'); | ||
const tokens = resp.data.data; | ||
const tokenList: SwapTokenLogicTokenList = []; | ||
for (const { address, decimals, symbol, name } of tokens) { | ||
tokenList.push(new common.Token(this.chainId, address, decimals, symbol, name)); | ||
} | ||
return tokenList; | ||
} | ||
|
||
// If you wish to exclude quotes from a specific DEX, you can include the corresponding DEX ID | ||
// in the 'disabledDexIds' parameter. You can retrieve the DEX IDs from the following API: | ||
// https://open-api.openocean.finance/v3/{chainId}/dexList | ||
async quote(params: SwapTokenLogicParams) { | ||
const { input, tokenOut, disabledDexIds } = params; | ||
const url = getApiUrl(this.chainId); | ||
const gasPrice = getGasPrice(this.chainId); | ||
let slippage = slippageToOpenOcean(params.slippage ?? 0); | ||
|
||
const resp = await axios.get(url + '/quote', { | ||
params: { | ||
inTokenAddress: input.token.address, | ||
outTokenAddress: tokenOut.address, | ||
amount: input.amount, | ||
gasPrice, | ||
slippage, | ||
disabledDexIds, | ||
}, | ||
}); | ||
|
||
slippage = slippageToProtocolink(slippage); | ||
|
||
const { outAmount } = resp.data.data; | ||
const output = new common.TokenAmount(tokenOut).setWei(outAmount); | ||
return { input, output, slippage, disabledDexIds }; | ||
} | ||
|
||
// Different gas_price will lead to different routes. | ||
// This is due to that OpenOcean calculates the best overall return. | ||
// The best overall return = out_value - tx cost and the tx_cost = gas_used & gas_price | ||
async build(fields: SwapTokenLogicFields, options: SwapTokenLogicOptions) { | ||
const { input, output, disabledDexIds } = fields; | ||
const { account } = options; | ||
const url = getApiUrl(this.chainId); | ||
const gasPrice = getGasPrice(this.chainId); | ||
const agent = await this.calcAgent(account); | ||
const slippage = slippageToOpenOcean(fields.slippage ?? 0); | ||
|
||
const resp = await axios.get(url + '/swap_quote', { | ||
params: { | ||
inTokenAddress: input.token.address, | ||
outTokenAddress: output.token.address, | ||
amount: input.amount, | ||
gasPrice, | ||
slippage, | ||
account: agent, | ||
disabledDexIds, | ||
}, | ||
}); | ||
|
||
const { to, data } = resp.data.data; | ||
const inputs = [core.newLogicInput({ input })]; | ||
return core.newLogic({ to, data, inputs }); | ||
} | ||
} |
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 { expect } from 'chai'; | ||
import { slippageToOpenOcean, slippageToProtocolink } from './slippage'; | ||
|
||
describe('Test slippageToOpenOcean', function () { | ||
const testCases = [ | ||
{ | ||
title: 'in range integer', | ||
slippage: 100, | ||
expected: 1, | ||
}, | ||
{ | ||
title: 'in range floating number', | ||
slippage: 150, | ||
expected: 1.5, | ||
}, | ||
{ | ||
title: 'out of range minimal', | ||
slippage: 0, | ||
expected: 0.05, | ||
}, | ||
{ | ||
title: 'out of range maximal', | ||
slippage: 10000, | ||
expected: 50, | ||
}, | ||
]; | ||
|
||
testCases.forEach(({ title, slippage, expected }) => { | ||
it(title, function () { | ||
expect(slippageToOpenOcean(slippage) === expected).to.be.true; | ||
}); | ||
}); | ||
}); | ||
|
||
describe('Test slippageToProtocolink', function () { | ||
const testCases = [ | ||
{ | ||
title: 'in range integer', | ||
slippage: 1, | ||
expected: 100, | ||
}, | ||
{ | ||
title: 'in range floating number', | ||
slippage: 1.5, | ||
expected: 150, | ||
}, | ||
]; | ||
|
||
testCases.forEach(({ title, slippage, expected }) => { | ||
it(title, function () { | ||
expect(slippageToProtocolink(slippage) === expected).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,7 @@ | ||
export function slippageToOpenOcean(slippage: number) { | ||
return Math.min(Math.max(slippage / 100, 0.05), 50); | ||
} | ||
|
||
export function slippageToProtocolink(slippage: number) { | ||
return slippage * 100; | ||
} |
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,30 @@ | ||
{ | ||
"METIS": { | ||
"chainId": 1088, | ||
"address": "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000", | ||
"decimals": 18, | ||
"symbol": "METIS", | ||
"name": "Metis" | ||
}, | ||
"USDC": { | ||
"chainId": 1088, | ||
"address": "0xEA32A96608495e54156Ae48931A7c20f0dcc1a21", | ||
"decimals": 6, | ||
"symbol": "m.USDC", | ||
"name": "USDC Token" | ||
}, | ||
"WETH": { | ||
"chainId": 1, | ||
"address": "0x420000000000000000000000000000000000000a", | ||
"decimals": 18, | ||
"symbol": "WETH", | ||
"name": "Wrapped Ether" | ||
}, | ||
"DAI": { | ||
"chainId": 1088, | ||
"address": "0x4c078361FC9BbB78DF910800A991C7c3DD2F6ce0", | ||
"decimals": 18, | ||
"symbol": "m.DAI", | ||
"name": "DAI Token" | ||
} | ||
} |
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 @@ | ||
import * as common from '@protocolink/common'; | ||
import metisTokensJSON from './data/metis.json'; | ||
|
||
type MetisTokenSymbols = keyof typeof metisTokensJSON; | ||
|
||
export const metisTokens = common.toTokenMap<MetisTokenSymbols>(metisTokensJSON); |
Oops, something went wrong.