Skip to content

Commit 26f95a4

Browse files
committed
add custom kyberswap quote source
1 parent 8bbceb3 commit 26f95a4

File tree

2 files changed

+210
-0
lines changed

2 files changed

+210
-0
lines changed

src/swapService/strategies/balmySDK/customSourceList.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { IFetchService, IProviderService } from "@balmy/sdk"
22
import { LocalSourceList } from "@balmy/sdk/dist/services/quotes/source-lists/local-source-list"
3+
import { CustomKyberswapQuoteSource } from "./sources/kyberswapQuoteSource"
34
import { CustomLiFiQuoteSource } from "./sources/lifiQuoteSource"
45
import { CustomMagpieQuoteSource } from "./sources/magpieQuoteSource"
56
import { CustomNeptuneQuoteSource } from "./sources/neptuneQuoteSource"
@@ -26,6 +27,7 @@ const customSources = {
2627
oogabooga: new CustomOogaboogaQuoteSource(),
2728
uniswap: new CustomUniswapQuoteSource(),
2829
magpie: new CustomMagpieQuoteSource(),
30+
kyberswap: new CustomKyberswapQuoteSource(),
2931
oku_bob_icecreamswap: new CustomOkuQuoteSource(
3032
"icecreamswap",
3133
"IceCreamSwap",
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import {
2+
type Address,
3+
Addresses,
4+
type ChainId,
5+
Chains,
6+
type TimeString,
7+
calculateDeadline,
8+
isSameAddress,
9+
} from "@balmy/sdk"
10+
import { AlwaysValidConfigAndContextSource } from "@balmy/sdk/dist/services/quotes/quote-sources/base/always-valid-source"
11+
import type {
12+
BuildTxParams,
13+
QuoteParams,
14+
QuoteSourceMetadata,
15+
SourceQuoteResponse,
16+
SourceQuoteTransaction,
17+
} from "@balmy/sdk/dist/services/quotes/quote-sources/types"
18+
import {
19+
addQuoteSlippage,
20+
calculateAllowanceTarget,
21+
failed,
22+
} from "@balmy/sdk/dist/services/quotes/quote-sources/utils"
23+
24+
const SUPPORTED_CHAINS: Record<ChainId, string> = {
25+
[Chains.ARBITRUM.chainId]: "arbitrum",
26+
[Chains.AURORA.chainId]: "aurora",
27+
[Chains.AVALANCHE.chainId]: "avalanche",
28+
[Chains.BNB_CHAIN.chainId]: "bsc",
29+
[Chains.BIT_TORRENT.chainId]: "bttc",
30+
[Chains.CRONOS.chainId]: "cronos",
31+
[Chains.ETHEREUM.chainId]: "ethereum",
32+
[Chains.FANTOM.chainId]: "fantom",
33+
[Chains.OASIS_EMERALD.chainId]: "oasis",
34+
[Chains.POLYGON.chainId]: "polygon",
35+
[Chains.VELAS.chainId]: "velas",
36+
[Chains.OPTIMISM.chainId]: "optimism",
37+
[Chains.LINEA.chainId]: "linea",
38+
[Chains.BASE.chainId]: "base",
39+
[Chains.POLYGON_ZKEVM.chainId]: "polygon-zkevm",
40+
[Chains.SCROLL.chainId]: "scroll",
41+
[Chains.BLAST.chainId]: "blast",
42+
[Chains.MANTLE.chainId]: "mantle",
43+
[Chains.SONIC.chainId]: "sonic",
44+
[Chains.ZK_SYNC_ERA.chainId]: "zksync",
45+
}
46+
47+
const KYBERSWAP_METADATA: QuoteSourceMetadata<KyberswapSupport> = {
48+
name: "Kyberswap",
49+
supports: {
50+
chains: Object.keys(SUPPORTED_CHAINS).map(Number),
51+
swapAndTransfer: true,
52+
buyOrders: false,
53+
},
54+
logoURI: "ipfs://QmNcTVyqeVtNoyrT546VgJTD4vsZEkWp6zhDJ4qhgKkhbK",
55+
}
56+
type KyberswapSupport = { buyOrders: false; swapAndTransfer: true }
57+
type KyberswapConfig = object
58+
type KyberswapData = {
59+
routeSummary: RouteSummary
60+
txValidFor: TimeString | undefined
61+
slippagePercentage: number
62+
takeFrom: Address
63+
recipient: Address | undefined
64+
}
65+
export class CustomKyberswapQuoteSource extends AlwaysValidConfigAndContextSource<
66+
KyberswapSupport,
67+
KyberswapConfig,
68+
KyberswapData
69+
> {
70+
getMetadata() {
71+
return KYBERSWAP_METADATA
72+
}
73+
74+
async quote({
75+
components: { fetchService },
76+
request: {
77+
chainId,
78+
sellToken,
79+
buyToken,
80+
order,
81+
accounts: { takeFrom, recipient },
82+
config: { slippagePercentage, timeout, txValidFor },
83+
},
84+
config,
85+
}: QuoteParams<KyberswapSupport>): Promise<
86+
SourceQuoteResponse<KyberswapData>
87+
> {
88+
const chainKey = SUPPORTED_CHAINS[chainId]
89+
const headers = config.referrer?.name
90+
? { "x-client-id": config.referrer?.name }
91+
: undefined
92+
93+
const url = `https://aggregator-api.kyberswap.com/${chainKey}/api/v1/routes?tokenIn=${sellToken}&tokenOut=${buyToken}&amountIn=${order.sellAmount.toString()}&saveGas=0&gasInclude=true&excludedSources=clipper,hashflow-v3,kyberswap-limit-order,kyberswap-limit-order-v2,mx-trading,native-v1,native-v2`
94+
const routeResponse = await fetchService.fetch(url, { timeout, headers })
95+
if (!routeResponse.ok) {
96+
failed(
97+
KYBERSWAP_METADATA,
98+
chainId,
99+
sellToken,
100+
buyToken,
101+
await routeResponse.text(),
102+
)
103+
}
104+
const {
105+
data: { routeSummary, routerAddress },
106+
}: { data: { routeSummary: RouteSummary; routerAddress: Address } } =
107+
await routeResponse.json()
108+
109+
const quote = {
110+
sellAmount: order.sellAmount,
111+
buyAmount: BigInt(routeSummary.amountOut),
112+
estimatedGas: BigInt(routeSummary.gas),
113+
allowanceTarget: calculateAllowanceTarget(sellToken, routerAddress),
114+
customData: {
115+
routeSummary,
116+
slippagePercentage,
117+
txValidFor,
118+
takeFrom,
119+
recipient,
120+
},
121+
}
122+
123+
return addQuoteSlippage(quote, order.type, slippagePercentage)
124+
}
125+
126+
async buildTx({
127+
components: { fetchService },
128+
request: {
129+
chainId,
130+
sellToken,
131+
buyToken,
132+
sellAmount,
133+
config: { timeout },
134+
customData: {
135+
routeSummary,
136+
txValidFor,
137+
slippagePercentage,
138+
takeFrom,
139+
recipient,
140+
},
141+
},
142+
config,
143+
}: BuildTxParams<
144+
KyberswapConfig,
145+
KyberswapData
146+
>): Promise<SourceQuoteTransaction> {
147+
const chainKey = SUPPORTED_CHAINS[chainId]
148+
const headers = config.referrer?.name
149+
? { "x-client-id": config.referrer?.name }
150+
: undefined
151+
152+
const buildResponse = await fetchService.fetch(
153+
`https://aggregator-api.kyberswap.com/${chainKey}/api/v1/route/build`,
154+
{
155+
timeout,
156+
headers,
157+
method: "POST",
158+
body: JSON.stringify({
159+
routeSummary,
160+
slippageTolerance: slippagePercentage * 100,
161+
recipient: recipient ?? takeFrom,
162+
deadline: txValidFor ? calculateDeadline(txValidFor) : undefined,
163+
source: config.referrer?.name,
164+
sender: takeFrom,
165+
skipSimulateTransaction: config.disableValidation,
166+
}),
167+
},
168+
)
169+
if (!buildResponse.ok) {
170+
failed(
171+
KYBERSWAP_METADATA,
172+
chainId,
173+
sellToken,
174+
buyToken,
175+
await buildResponse.text(),
176+
)
177+
}
178+
const {
179+
data: { data, routerAddress },
180+
} = await buildResponse.json()
181+
182+
if (!data) {
183+
failed(
184+
KYBERSWAP_METADATA,
185+
chainId,
186+
sellToken,
187+
buyToken,
188+
"Failed to calculate a quote",
189+
)
190+
}
191+
192+
const value = isSameAddress(sellToken, Addresses.NATIVE_TOKEN)
193+
? sellAmount
194+
: 0n
195+
196+
return {
197+
to: routerAddress,
198+
value,
199+
calldata: data,
200+
}
201+
}
202+
}
203+
204+
type RouteSummary = {
205+
amountOut: `${bigint}`
206+
gas: `${bigint}`
207+
routerAddress: Address
208+
}

0 commit comments

Comments
 (0)