Skip to content

Commit 525a1a8

Browse files
committed
feat(cross-chain): integrate kybercross api
Add KyberCross quote, build, and scan status API wiring for the cross-chain adapter.
1 parent 5fe58a3 commit 525a1a8

14 files changed

Lines changed: 1503 additions & 188 deletions

File tree

apps/kyberswap-interface/.env.production

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,5 @@ VITE_SOLANA_RPC=https://solana-rpc.kyberswap.com
5757
VITE_SMART_EXIT_API_URL=https://conditional-order.kyberswap.com/api
5858
# VITE_CROSSCHAIN_AGGREGATOR_API=https://crosschain-aggregator.kyberswap.com
5959
VITE_CROSSCHAIN_AGGREGATOR_API=https://pre-crosschain-aggregator.kyberengineering.io
60+
61+
VITE_CROSSCHAIN_KYBERCROSS_API=https://pre-kybercross.kyberengineering.io

apps/kyberswap-interface/src/constants/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const AFFILIATE_SERVICE_URL = required('AFFILIATE_SERVICE')
5757
export const SOLANA_RPC = required('SOLANA_RPC')
5858
export const SMART_EXIT_API_URL = required('SMART_EXIT_API_URL')
5959
export const CROSSCHAIN_AGGREGATOR_API = required('CROSSCHAIN_AGGREGATOR_API')
60+
export const CROSSCHAIN_KYBERCROSS_API = required('CROSSCHAIN_KYBERCROSS_API')
6061

6162
type FirebaseConfig = {
6263
apiKey: string
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
import axios, { type AxiosRequestConfig } from 'axios'
2+
import { type Address, type Hash, type Hex } from 'viem'
3+
4+
import { CROSSCHAIN_KYBERCROSS_API } from 'constants/env'
5+
6+
type RequestId = string
7+
type UIntString = string
8+
type TokenReference = string
9+
type JsonObject = Record<string, unknown>
10+
11+
export type ChainName = 'ethereum' | 'arbitrum' | 'base' | 'bsc'
12+
export type BridgeProvider = 'across' | 'relay' | 'mayan' | 'near_intents'
13+
type FlowType = 'bridge_only' | 'swap_then_bridge'
14+
type RouteStatus =
15+
| 'built'
16+
| 'source_pending'
17+
| 'source_confirmed'
18+
| 'bridge_pending'
19+
| 'destination_pending'
20+
| 'completed'
21+
| 'refunded'
22+
| 'failed'
23+
type ActionType = 'wrap_native' | 'unwrap_native' | 'transfer'
24+
25+
export type QuoteRequest = {
26+
from_chain: ChainName
27+
from_token: Address
28+
from_token_decimals: number
29+
from_address: Address
30+
to_chain: ChainName
31+
to_token: Address
32+
to_token_decimals: number
33+
amount: UIntString
34+
to_address?: Address
35+
refund_address?: Address
36+
slippage_bps?: number
37+
client_fee_recipient?: Address
38+
client_fee_bps?: number
39+
include_bridges?: BridgeProvider[]
40+
exclude_bridges?: BridgeProvider[]
41+
}
42+
43+
type RoutePlanRequestSnapshot = {
44+
from_chain: ChainName
45+
from_token: Address
46+
from_token_decimals: number
47+
from_address: Address
48+
to_chain: ChainName
49+
to_token: Address
50+
to_token_decimals: number
51+
to_address: Address
52+
amount: UIntString
53+
slippage_bps: number
54+
refund_address?: Address
55+
client_fee_recipient?: Address
56+
client_fee_bps?: number
57+
}
58+
59+
type FeePlan = {
60+
type: 'client'
61+
chain: ChainName
62+
token: TokenReference
63+
recipient: Address
64+
rate_bps: number
65+
charged_on: 'bridge_input'
66+
expected_amount?: UIntString
67+
min_amount?: UIntString
68+
}
69+
70+
type SwapPlan = {
71+
token_in: TokenReference
72+
token_out: TokenReference
73+
input_amount: UIntString
74+
expected_output_amount: UIntString
75+
min_output_amount: UIntString
76+
metadata: {
77+
route_id: string
78+
route_summary: JsonObject
79+
}
80+
}
81+
82+
type ActionPlan = {
83+
type: ActionType
84+
token_in: TokenReference
85+
token_out: TokenReference
86+
recipient?: Address
87+
}
88+
89+
type AcrossSpokePoolBridgeMetadata = {
90+
settlement?: 'spoke_pool'
91+
spoke_pool: Address
92+
input_amount: UIntString
93+
output_amount: UIntString
94+
destination_chain_id: number
95+
depositor: Address
96+
recipient: Address
97+
exclusive_relayer?: Address
98+
quote_timestamp?: number
99+
fill_deadline?: number
100+
exclusivity_parameter?: number
101+
message?: string | null
102+
quote_expiry_timestamp?: number
103+
}
104+
105+
type AcrossExecutionBridgeMetadata = {
106+
settlement: 'cctp' | 'oft'
107+
input_amount: UIntString
108+
output_amount: UIntString
109+
destination_chain_id: number
110+
execution_target: Address
111+
execution_data: string
112+
quote_expiry_timestamp?: number
113+
execution_value?: UIntString | null
114+
}
115+
116+
type MayanBridgeMetadata = {
117+
mayan_forwarder: Address
118+
mayan_protocol: Address
119+
protocol_data: string
120+
}
121+
122+
export type NearIntentsBridgeMetadata = {
123+
deposit_address: string
124+
}
125+
126+
export type BridgeMetadata =
127+
| AcrossSpokePoolBridgeMetadata
128+
| AcrossExecutionBridgeMetadata
129+
| MayanBridgeMetadata
130+
| NearIntentsBridgeMetadata
131+
132+
type BridgePlan = {
133+
lane_id: string
134+
provider: BridgeProvider
135+
asset_group: string
136+
token_in: TokenReference
137+
token_out: TokenReference
138+
input_amount: UIntString
139+
expected_output_amount: UIntString
140+
min_output_amount: UIntString
141+
metadata: BridgeMetadata
142+
provider_fee?: UIntString
143+
expected_fill_time_sec?: number
144+
}
145+
146+
export type RoutePlan = {
147+
route_id: string
148+
request: RoutePlanRequestSnapshot
149+
flow_type: FlowType
150+
expected_output_amount: UIntString
151+
min_output_amount: UIntString
152+
expires_at: string
153+
bridge: BridgePlan
154+
provider?: string
155+
status?: RouteStatus
156+
updated_at?: string
157+
fees?: FeePlan[]
158+
source_swap?: SwapPlan
159+
pre_bridge?: ActionPlan[]
160+
post_bridge?: ActionPlan[]
161+
}
162+
163+
type SwapDetails = {
164+
token_in: Address
165+
token_out: Address
166+
amount_in: UIntString
167+
amount_out: UIntString
168+
}
169+
170+
type OnChainBridgeDetails = {
171+
tx_hash: Hash
172+
token: Address
173+
amount: UIntString
174+
}
175+
176+
type BridgeDetails = {
177+
source: OnChainBridgeDetails
178+
destination?: OnChainBridgeDetails
179+
}
180+
181+
type RouteExecutionDetails = {
182+
source_swap?: SwapDetails
183+
bridge?: BridgeDetails
184+
dest_swap?: SwapDetails
185+
}
186+
187+
export type TrackingExecution = {
188+
route_id: string
189+
sender: Address
190+
receiver: Address
191+
source_chain: ChainName
192+
dest_chain: ChainName
193+
flow_type: FlowType
194+
source_tx_hash: Hash
195+
route_state: RouteStatus
196+
route_state_details: RouteExecutionDetails
197+
created_at: string
198+
updated_at: string
199+
dest_tx_hash?: Hash | null
200+
route_plan?: RoutePlan
201+
}
202+
203+
export type ExecutionTx = {
204+
to: Address
205+
data: Hex
206+
value: UIntString
207+
gas?: UIntString
208+
}
209+
210+
export type BuildResult = {
211+
tx: ExecutionTx
212+
expires_at?: string
213+
}
214+
215+
type ErrorBody = {
216+
code: string
217+
message: string
218+
details?: JsonObject
219+
}
220+
221+
export type SuccessResponse<TData> = {
222+
request_id: RequestId
223+
success: true
224+
data: TData
225+
}
226+
227+
type ErrorResponse = {
228+
request_id: RequestId
229+
success: false
230+
error: ErrorBody
231+
}
232+
233+
export type ApiResponse<TData> = SuccessResponse<TData> | ErrorResponse
234+
235+
export type QuoteResponse = SuccessResponse<RoutePlan>
236+
export type BuildResponse = SuccessResponse<BuildResult>
237+
export type ScanTxStatusResponse = SuccessResponse<TrackingExecution>
238+
239+
export type ScanTxStatusParams = {
240+
include_route_plan?: boolean
241+
}
242+
243+
const kyberCrossApiClient = axios.create({
244+
baseURL: CROSSCHAIN_KYBERCROSS_API,
245+
headers: {
246+
'Content-Type': 'application/json',
247+
},
248+
})
249+
250+
const call = async <TData>(config: AxiosRequestConfig): Promise<SuccessResponse<TData>> => {
251+
const { data, status } = await kyberCrossApiClient.request<ApiResponse<TData>>({
252+
validateStatus: () => true,
253+
...config,
254+
})
255+
256+
if (status < 200 || status >= 300 || !data.success) {
257+
throw new Error(data.success ? `KyberCross API failed with HTTP ${status}` : data.error.message)
258+
}
259+
260+
return data
261+
}
262+
263+
const getQuote = (data: QuoteRequest): Promise<QuoteResponse> =>
264+
call<RoutePlan>({
265+
method: 'POST',
266+
url: '/api/v1/quotes',
267+
data,
268+
})
269+
270+
const build = (data: RoutePlan): Promise<BuildResponse> =>
271+
call<BuildResult>({
272+
method: 'POST',
273+
url: '/api/v1/builds',
274+
data,
275+
})
276+
277+
const scanTxStatus = (txHash: Hash, params?: ScanTxStatusParams): Promise<ScanTxStatusResponse> =>
278+
call<TrackingExecution>({
279+
method: 'GET',
280+
url: `/api/v1/scan/tx/${txHash}`,
281+
params,
282+
})
283+
284+
export const kyberCrossApi = {
285+
getQuote,
286+
build,
287+
scanTxStatus,
288+
}

0 commit comments

Comments
 (0)