Skip to content

Commit 0c44e27

Browse files
committed
add skip routing
1 parent 77e6851 commit 0c44e27

13 files changed

+785
-221
lines changed

src/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import { Osmosis } from './exchange/Osmosis.js'
1111
import { getRedbankConfig } from './redbank/config/getConfig.js'
1212
import { getConfig as getRoverConfig } from './rover/config/getConfig.js'
1313
import { AstroportCW } from './exchange/Astroport.js'
14-
import { AstroportRouteRequester } from './query/routing/AstroportRouteRequester.js'
1514
import { OsmosisRouteRequester } from './query/routing/OsmosisRouteRequester.js'
15+
import { SkipRouteRequester } from './query/routing/skip/SkipRouteRequester.js'
1616
import { RouteRequester } from './query/routing/RouteRequesterInterface.js'
1717
import { ChainQuery } from './query/chainQuery.js'
1818
import { MetricsService } from './metrics.js'
@@ -67,7 +67,7 @@ export const main = async () => {
6767
chainName === 'osmosis' ? new Osmosis() : new AstroportCW(prefix, config.astroportRouter!)
6868
const routeRequester =
6969
chainName === 'neutron'
70-
? new AstroportRouteRequester(process.env.ASTROPORT_API_URL!)
70+
? new SkipRouteRequester('https://api.skip.build')
7171
: new OsmosisRouteRequester(process.env.SQS_URL!)
7272

7373
// Start metrics server

src/query/routing/AstroportRouteRequester.ts

Lines changed: 0 additions & 60 deletions
This file was deleted.
Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
import Long from 'long'
2-
import { RouteHop } from '../../types/RouteHop'
3-
import { RequestRouteResponse, RouteRequester } from './RouteRequesterInterface'
1+
import { RouteRequester, GenericRoute } from './RouteRequesterInterface'
42
import { RouteResponse } from '../../types/OsmosisRouteResponse'
5-
import { PoolType } from '../../types/Pool'
63
import BigNumber from 'bignumber.js'
74

85
export class OsmosisRouteRequester extends RouteRequester {
9-
async requestRoute(
10-
tokenInDenom: string,
11-
tokenOutDenom: string,
12-
tokenInAmount: string,
13-
): Promise<RequestRouteResponse> {
14-
let url = `${this.apiUrl}/router/quote?tokenIn=${tokenInAmount}${tokenInDenom}&tokenOutDenom=${tokenOutDenom}`
6+
async getRoute(params: {
7+
denomIn: string
8+
denomOut: string
9+
amountIn: string
10+
chainIdIn: string
11+
chainIdOut: string
12+
}): Promise<GenericRoute> {
13+
let url = `${this.apiUrl}/router/quote?tokenIn=${params.amountIn}${params.denomIn}&tokenOutDenom=${params.denomOut}`
1514

1615
const response = await fetch(url)
1716

@@ -21,30 +20,26 @@ export class OsmosisRouteRequester extends RouteRequester {
2120

2221
let routeResponse: RouteResponse = await response.json()
2322

24-
let route = routeResponse.route[0].pools.map((pool) => {
25-
let routeHop: RouteHop = {
26-
poolId: new Long(pool.id),
27-
tokenInDenom: tokenInDenom,
28-
tokenOutDenom: pool.token_out_denom,
29-
pool: {
30-
address: 'notrequired',
31-
id: new Long(pool.id),
32-
poolType: PoolType.XYK,
33-
swapFee: pool.spread_factor,
34-
token0: tokenInDenom,
35-
token1: pool.token_out_denom,
36-
},
37-
}
38-
tokenInDenom = pool.token_out_denom
39-
return routeHop
40-
})
23+
// Convert Osmosis route to GenericRoute format
24+
const steps = routeResponse.route[0].pools.map((pool) => ({
25+
venue: 'osmosis',
26+
denomIn: params.denomIn,
27+
denomOut: pool.token_out_denom,
28+
pool: pool.id.toString(),
29+
}))
4130

4231
// allow for 2.5% slippage from what we estimated
4332
const minOutput = new BigNumber(routeResponse.amount_out).multipliedBy(0.975).toFixed(0)
4433

4534
return {
46-
route,
47-
expectedOutput: minOutput,
35+
amountIn: params.amountIn,
36+
estimatedAmountOut: minOutput,
37+
operations: [
38+
{
39+
chainId: params.chainIdIn,
40+
steps,
41+
},
42+
],
4843
}
4944
}
5045
}
Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
1-
import { RouteHop } from '../../types/RouteHop'
1+
/**
2+
* Generic route interface that can work with any routing service
3+
*/
4+
export interface GenericRoute {
5+
amountIn: string
6+
estimatedAmountOut: string
7+
operations: Array<{
8+
chainId: string
9+
steps: Array<{
10+
venue: string
11+
denomIn: string
12+
denomOut: string
13+
pool?: string
14+
}>
15+
}>
16+
}
17+
218
/**
319
* Route requester requests a route from an endpoint that is often an off chain source,
420
* that provides a route for a given tokenIn and tokenOut. This is often a more reliable
@@ -12,14 +28,14 @@ export abstract class RouteRequester {
1228
this.apiUrl = apiUrl
1329
}
1430

15-
abstract requestRoute(
16-
tokenInDenom: string,
17-
tokenOutDenom: string,
18-
tokenInAmount: string,
19-
): Promise<RequestRouteResponse>
20-
}
21-
22-
export interface RequestRouteResponse {
23-
route: RouteHop[]
24-
expectedOutput: string
31+
/**
32+
* Generic route method that returns a standardized route format
33+
*/
34+
abstract getRoute(params: {
35+
denomIn: string
36+
denomOut: string
37+
amountIn: string
38+
chainIdIn: string
39+
chainIdOut: string
40+
}): Promise<GenericRoute>
2541
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { RouteRequester, GenericRoute } from '../RouteRequesterInterface'
2+
import { getSwapRoute, SwapRoute, Operation, SwapOperation } from './getSwapRoute'
3+
4+
export class SkipRouteRequester extends RouteRequester {
5+
constructor(apiUrl: string = 'https://api.skip.build') {
6+
super(apiUrl)
7+
}
8+
9+
/**
10+
* Generic route method using Skip API
11+
*/
12+
async getRoute(params: {
13+
denomIn: string
14+
denomOut: string
15+
amountIn: string
16+
chainIdIn: string
17+
chainIdOut: string
18+
}): Promise<GenericRoute> {
19+
try {
20+
const swapRoute = await getSwapRoute(
21+
params.denomIn,
22+
params.denomOut,
23+
params.amountIn,
24+
params.chainIdIn,
25+
params.chainIdOut,
26+
)
27+
return this.convertSkipRouteToGenericRoute(swapRoute)
28+
} catch (error) {
29+
if (error instanceof Error && error.message.includes('No swap route found')) {
30+
throw new Error(`No route found for ${params.denomIn} to ${params.denomOut}`)
31+
}
32+
throw error
33+
}
34+
}
35+
36+
/**
37+
* Convert Skip API response to GenericRoute format
38+
*/
39+
private convertSkipRouteToGenericRoute(skipRoute: SwapRoute): GenericRoute {
40+
if (!skipRoute.does_swap) {
41+
throw new Error('No swap route available')
42+
}
43+
44+
console.log('skipRoute', JSON.stringify(skipRoute))
45+
const operations = skipRoute.operations.map((operation: Operation) => ({
46+
chainId: skipRoute.source_asset_chain_id,
47+
steps: operation.swap.swap_in.swap_operations.map((swapOp: SwapOperation) => ({
48+
venue: operation.swap.swap_in.swap_venue.name,
49+
denomIn: swapOp.denom_in,
50+
denomOut: swapOp.denom_out,
51+
pool: swapOp.pool,
52+
})),
53+
}))
54+
55+
return {
56+
amountIn: skipRoute.amount_in,
57+
estimatedAmountOut: skipRoute.amount_out,
58+
operations,
59+
}
60+
}
61+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* Skip API v2 Fungible Route Client
3+
* Fetches swap routes from https://api.skip.build/v2/fungible/route
4+
*/
5+
6+
interface RouteRequest {
7+
source_asset_denom: string
8+
source_asset_chain_id: string
9+
dest_asset_denom: string
10+
dest_asset_chain_id: string
11+
amount_in: string
12+
swap_venues: any[]
13+
}
14+
15+
export interface SwapVenue {
16+
name: string
17+
chain_id: string
18+
logo_uri: string
19+
}
20+
21+
export interface SwapOperation {
22+
pool: string
23+
denom_in: string
24+
denom_out: string
25+
}
26+
27+
interface Swap {
28+
swap_in: {
29+
swap_venue: SwapVenue
30+
swap_operations: SwapOperation[]
31+
swap_amount_in: string
32+
estimated_amount_out: string
33+
}
34+
estimated_affiliate_fee: string
35+
from_chain_id: string
36+
chain_id: string
37+
denom_in: string
38+
denom_out: string
39+
swap_venues: SwapVenue[]
40+
}
41+
42+
export interface Operation {
43+
swap: Swap
44+
tx_index: number
45+
amount_in: string
46+
amount_out: string
47+
}
48+
49+
export interface SwapRoute {
50+
source_asset_denom: string
51+
source_asset_chain_id: string
52+
dest_asset_denom: string
53+
dest_asset_chain_id: string
54+
amount_in: string
55+
amount_out: string
56+
operations: Operation[]
57+
chain_ids: string[]
58+
does_swap: boolean
59+
estimated_amount_out: string
60+
swap_venues: SwapVenue[]
61+
txs_required: number
62+
usd_amount_in: string
63+
usd_amount_out: string
64+
estimated_fees: any[]
65+
required_chain_addresses: string[]
66+
estimated_route_duration_seconds: number
67+
swap_venue: SwapVenue
68+
}
69+
70+
export async function getSwapRoute(
71+
denomIn: string,
72+
denomOut: string,
73+
amountIn: string,
74+
chainIdIn: string,
75+
chainIdOut: string,
76+
): Promise<SwapRoute> {
77+
const url = 'https://api.skip.build/v2/fungible/route'
78+
const body: RouteRequest = {
79+
source_asset_denom: denomIn,
80+
source_asset_chain_id: chainIdIn,
81+
dest_asset_denom: denomOut,
82+
dest_asset_chain_id: chainIdOut,
83+
amount_in: amountIn,
84+
swap_venues: [
85+
{
86+
chain_id: 'neutron-1',
87+
name: 'neutron-duality',
88+
},
89+
{
90+
chain_id: 'neutron-1',
91+
name: 'neutron-astroport',
92+
},
93+
],
94+
}
95+
96+
try {
97+
const res = await fetch(url, {
98+
method: 'POST',
99+
headers: {
100+
'Content-Type': 'application/json',
101+
},
102+
body: JSON.stringify(body),
103+
})
104+
105+
if (!res.ok) {
106+
throw new Error(`HTTP error! Status: ${res.status}`)
107+
}
108+
109+
const data: SwapRoute = await res.json()
110+
return data
111+
} catch (error) {
112+
console.error('Error fetching route:', error)
113+
throw error
114+
}
115+
}

0 commit comments

Comments
 (0)