diff --git a/README.md b/README.md index d94e2d2..67249e5 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Don't hesitate to reach out or submit pull requests with missing aggregators ada - [Li.Fi](https://li.fi/) `stable` `tested` - [Squid Router](https://www.squidrouter.com/) `stable` `tested` - [Socket](https://socket.tech/) `stable` `tested` +- [Sifi](https://sifi.org/) `untested` ### Liquidity - [1inch](https://1inch.io/) `stable` `tested` @@ -101,6 +102,7 @@ async function getTransactionRequests(): TransactionRequest[] { - [0x](https://github.com/AstrolabFinance/swapper/blob/main/src/ZeroX/index.ts) - [KyberSwap](https://github.com/AstrolabFinance/swapper/blob/main/src/KyberSwap/index.ts) - [ParaSwap](https://github.com/AstrolabFinance/swapper/blob/main/src/ParaSwap/index.ts) +- [Sifi](https://github.com/AstrolabFinance/swapper/blob/main/src/Sifi/index.ts) ### Swapper Contract diff --git a/src/Sifi/index.ts b/src/Sifi/index.ts new file mode 100644 index 0000000..8de1134 --- /dev/null +++ b/src/Sifi/index.ts @@ -0,0 +1,87 @@ +import { ITransactionRequestWithEstimate } from "../types"; +import qs from "qs"; +import { ISwapperParams, validateQuoteParams } from "../types"; + +// Sifi specific types +interface IQuote { + // NOTE: No fields have been added since these are never accessed +} + +interface IQuoteParams { + fromChain: number; + fromToken: string; + toChain?: number; + toToken: string; + fromAmount: string; + disablePermit?: number; +} + +const ROUTER_ADDRESS = "0x65c49E9996A877d062085B71E1460fFBe3C4c5Aa"; +const apiRoot = "https://api.sifi.org/v1/"; + +export const routerByChainId: { [id: number]: string } = { + 1: ROUTER_ADDRESS, + 10: ROUTER_ADDRESS, + 56: ROUTER_ADDRESS, + 137: ROUTER_ADDRESS, + 8453: ROUTER_ADDRESS, + 42161: ROUTER_ADDRESS, + 43114: ROUTER_ADDRESS, +}; + +const convertParams = (o: ISwapperParams): IQuoteParams => ({ + fromChain: o.inputChainId, + fromToken: o.input, + toChain: o.outputChainId, + toToken: o.output, + fromAmount: o.amountWei.toString(), + disablePermit: 1, +}); + +export async function getQuote(o: ISwapperParams): Promise { + if (!routerByChainId[o.inputChainId]) return undefined; + // NOTE: Required for validateQuoteParams + o.payer = o.payer || o.testPayer!; + if (!validateQuoteParams(o)) throw new Error("invalid input"); + const params = convertParams(o); + try { + const url = `${apiRoot}quote?${qs.stringify(params)}`; + const res = await fetch(url); + if (res.status >= 400) + throw new Error(`${res.status}: ${res.statusText} - ${await res.text?.()}`); + return await res.json(); + } catch (e) { + console.error(`getQuote failed: ${e}`); + } +} + +// NOTE: Expects referrer to be an EVM address +export async function getTransactionRequest(o: ISwapperParams): Promise { + const quote = await getQuote(o); + if (!quote) return undefined; + const params = { + quote, + toAddress: o.receiver ?? o.payer ?? o.testPayer, + fromAddress: o.payer ?? o.testPayer, + partner: o.referrer + }; + try { + const res = await fetch( + `${apiRoot}swap`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(params), + }, + ); + if (res.status >= 400) + throw new Error(`${res.status}: ${res.statusText} - ${await res.text?.()}`); + const swap = await res.json(); + return { + ...swap.tx, + estimatedGas: swap.tx.gasLimit, + }; + } catch (e) { + console.error(`getTransactionRequest failed: ${e}`); + } +} diff --git a/src/index.ts b/src/index.ts index a01af80..6b5afd5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import * as KyberSwap from "./KyberSwap"; import * as Squid from "./Squid"; import * as LiFi from "./LiFi"; import * as Socket from "./Socket"; +import * as Sifi from "./Sifi"; import { Aggregator, AggregatorId, ISwapperParams, ITransactionRequestWithEstimate } from "./types"; @@ -19,6 +20,7 @@ export const aggregatorById: { [key: string]: Aggregator } = { [AggregatorId.SQUID]: Squid, [AggregatorId.LIFI]: LiFi, [AggregatorId.SOCKET]: Socket, + [AggregatorId.SIFI]: Sifi, }; /** @@ -125,5 +127,6 @@ export { KyberSwap, Squid, LiFi, - Socket + Socket, + Sifi } diff --git a/src/types.ts b/src/types.ts index 936ba91..86dc27b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,6 +10,7 @@ export enum AggregatorId { ONE_INCH = "ONE_INCH", ZERO_X = "ZERO_X", PARASWAP = "PARASWAP", + SIFI = "SIFI", } export interface ISwapperParams { diff --git a/test/unit/swapper.client.test.ts b/test/unit/swapper.client.test.ts index 998113c..a1fb0dc 100644 --- a/test/unit/swapper.client.test.ts +++ b/test/unit/swapper.client.test.ts @@ -36,6 +36,10 @@ describe("swapper.client.test", function () { for (const tr of (await getTransactionRequestByAggregatorCases(AggregatorId.ZERO_X))) expect(tr?.data).to.be.a("string"); }); + it("Sifi", async function () { + for (const tr of (await getTransactionRequestByAggregatorCases(AggregatorId.SIFI))) + expect(tr?.data).to.be.a("string"); + }); }) }); diff --git a/test/utils/cases.ts b/test/utils/cases.ts index a44ba2e..8d7fbed 100644 --- a/test/utils/cases.ts +++ b/test/utils/cases.ts @@ -74,6 +74,15 @@ export const cases = (): ISwapperParams[] => { testPayer: addresses[250].accounts!.impersonate, payer: "", }, + { + aggregatorId, + inputChainId: 42161, // Arbitrum One + input: addresses[42161].tokens.DAI, + output: addresses[42161].tokens.USDCE, + amountWei: 100 * 1e18, // string to prevent overflow + testPayer: addresses[42161].accounts!.impersonate, + payer: "", + }, ], ); }