@sip-protocol/sdk 0.2.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.d.mts +100 -2
- package/dist/browser.d.ts +100 -2
- package/dist/browser.js +2116 -321
- package/dist/browser.mjs +516 -16
- package/dist/chunk-4IFOPYJF.mjs +11950 -0
- package/dist/chunk-7IMRM7LN.mjs +12149 -0
- package/dist/chunk-JNNXNTSS.mjs +11034 -0
- package/dist/chunk-W3YXIQ7L.mjs +11950 -0
- package/dist/chunk-XLEPIR2P.mjs +884 -0
- package/dist/index-Ba7njCU3.d.ts +6925 -0
- package/dist/index-Co26-vbG.d.mts +6925 -0
- package/dist/index-DqZoHYKI.d.mts +6418 -0
- package/dist/index-dTtK_DTl.d.ts +6762 -0
- package/dist/index-jnkYu-Z4.d.mts +6762 -0
- package/dist/index-vB1N1mHd.d.ts +6418 -0
- package/dist/index.d.mts +2 -5897
- package/dist/index.d.ts +2 -5897
- package/dist/index.js +1334 -199
- package/dist/index.mjs +19 -1
- package/dist/noir-BTyLXLlZ.d.mts +467 -0
- package/dist/noir-BTyLXLlZ.d.ts +467 -0
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/proofs/noir.js +11 -112
- package/dist/proofs/noir.mjs +10 -13
- package/package.json +3 -3
- package/src/browser.ts +23 -0
- package/src/index.ts +32 -0
- package/src/proofs/browser-utils.ts +389 -0
- package/src/proofs/browser.ts +246 -19
- package/src/proofs/circuits/funding_proof.json +1 -1
- package/src/proofs/noir.ts +14 -14
- package/src/proofs/worker.ts +426 -0
- package/src/settlement/README.md +439 -0
- package/src/settlement/backends/direct-chain.ts +569 -0
- package/src/settlement/backends/index.ts +22 -0
- package/src/settlement/backends/near-intents.ts +480 -0
- package/src/settlement/backends/zcash-native.ts +516 -0
- package/src/settlement/index.ts +47 -0
- package/src/settlement/interface.ts +397 -0
- package/src/settlement/registry.ts +269 -0
- package/src/settlement/router.ts +383 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Router for Optimal Route Selection
|
|
3
|
+
*
|
|
4
|
+
* Queries all compatible backends and finds the best route based on preferences.
|
|
5
|
+
*
|
|
6
|
+
* @module settlement/router
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ChainId, PrivacyLevel } from '@sip-protocol/types'
|
|
10
|
+
import type {
|
|
11
|
+
SettlementBackend,
|
|
12
|
+
QuoteParams,
|
|
13
|
+
Quote,
|
|
14
|
+
} from './interface'
|
|
15
|
+
import type { SettlementRegistry } from './registry'
|
|
16
|
+
import { ValidationError, NetworkError } from '../errors'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Route with quote information
|
|
20
|
+
*/
|
|
21
|
+
export interface RouteWithQuote {
|
|
22
|
+
/** Backend name */
|
|
23
|
+
backend: string
|
|
24
|
+
/** Quote from backend */
|
|
25
|
+
quote: Quote
|
|
26
|
+
/** Backend instance */
|
|
27
|
+
backendInstance: SettlementBackend
|
|
28
|
+
/** Score for ranking (higher is better) */
|
|
29
|
+
score: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Quote comparison result
|
|
34
|
+
*/
|
|
35
|
+
export interface QuoteComparison {
|
|
36
|
+
/** All routes with quotes */
|
|
37
|
+
routes: RouteWithQuote[]
|
|
38
|
+
/** Best route by total cost */
|
|
39
|
+
bestByCost: RouteWithQuote | null
|
|
40
|
+
/** Best route by speed */
|
|
41
|
+
bestBySpeed: RouteWithQuote | null
|
|
42
|
+
/** Best route by privacy */
|
|
43
|
+
bestByPrivacy: RouteWithQuote | null
|
|
44
|
+
/** Comparison metadata */
|
|
45
|
+
metadata: {
|
|
46
|
+
/** Total backends queried */
|
|
47
|
+
totalQueried: number
|
|
48
|
+
/** Failed backend queries */
|
|
49
|
+
failures: Array<{ backend: string; error: string }>
|
|
50
|
+
/** Query timestamp */
|
|
51
|
+
queriedAt: number
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Route finding parameters
|
|
57
|
+
*/
|
|
58
|
+
export interface FindBestRouteParams {
|
|
59
|
+
/** Source chain and token */
|
|
60
|
+
from: { chain: ChainId; token: string }
|
|
61
|
+
/** Destination chain and token */
|
|
62
|
+
to: { chain: ChainId; token: string }
|
|
63
|
+
/** Amount to swap (in smallest units) */
|
|
64
|
+
amount: bigint
|
|
65
|
+
/** Privacy level */
|
|
66
|
+
privacyLevel: PrivacyLevel
|
|
67
|
+
/** Prefer speed over cost (default: false) */
|
|
68
|
+
preferSpeed?: boolean
|
|
69
|
+
/** Prefer low fees over speed (default: true) */
|
|
70
|
+
preferLowFees?: boolean
|
|
71
|
+
/** Additional quote parameters */
|
|
72
|
+
recipientMetaAddress?: string
|
|
73
|
+
senderAddress?: string
|
|
74
|
+
slippageTolerance?: number
|
|
75
|
+
deadline?: number
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Smart Router for finding optimal settlement routes
|
|
80
|
+
*
|
|
81
|
+
* Queries all compatible backends in parallel and ranks routes by:
|
|
82
|
+
* - Total cost (network + protocol fees)
|
|
83
|
+
* - Execution speed (estimated time)
|
|
84
|
+
* - Privacy support (shielded vs transparent)
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* const registry = new SettlementRegistry()
|
|
89
|
+
* registry.register(nearIntentsBackend)
|
|
90
|
+
* registry.register(zcashBackend)
|
|
91
|
+
*
|
|
92
|
+
* const router = new SmartRouter(registry)
|
|
93
|
+
* const routes = await router.findBestRoute({
|
|
94
|
+
* from: { chain: 'ethereum', token: 'USDC' },
|
|
95
|
+
* to: { chain: 'solana', token: 'SOL' },
|
|
96
|
+
* amount: 100_000000n,
|
|
97
|
+
* privacyLevel: PrivacyLevel.SHIELDED,
|
|
98
|
+
* preferLowFees: true
|
|
99
|
+
* })
|
|
100
|
+
*
|
|
101
|
+
* // Get best route
|
|
102
|
+
* const best = routes[0]
|
|
103
|
+
* console.log(`Best backend: ${best.backend}`)
|
|
104
|
+
* console.log(`Cost: ${best.quote.fees.totalFeeUSD} USD`)
|
|
105
|
+
* console.log(`Time: ${best.quote.estimatedTime}s`)
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export class SmartRouter {
|
|
109
|
+
constructor(private registry: SettlementRegistry) {}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Find best routes for a swap
|
|
113
|
+
*
|
|
114
|
+
* Queries all compatible backends in parallel and returns sorted routes.
|
|
115
|
+
*
|
|
116
|
+
* @param params - Route finding parameters
|
|
117
|
+
* @returns Sorted routes (best first)
|
|
118
|
+
* @throws {ValidationError} If no backends support the route
|
|
119
|
+
*/
|
|
120
|
+
async findBestRoute(params: FindBestRouteParams): Promise<RouteWithQuote[]> {
|
|
121
|
+
const {
|
|
122
|
+
from,
|
|
123
|
+
to,
|
|
124
|
+
amount,
|
|
125
|
+
privacyLevel,
|
|
126
|
+
preferSpeed = false,
|
|
127
|
+
preferLowFees = true,
|
|
128
|
+
recipientMetaAddress,
|
|
129
|
+
senderAddress,
|
|
130
|
+
slippageTolerance,
|
|
131
|
+
deadline,
|
|
132
|
+
} = params
|
|
133
|
+
|
|
134
|
+
// Validate amount
|
|
135
|
+
if (amount <= 0n) {
|
|
136
|
+
throw new ValidationError('Amount must be greater than zero')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Get all registered backends
|
|
140
|
+
const allBackends = this.registry
|
|
141
|
+
.list()
|
|
142
|
+
.map((name) => this.registry.get(name))
|
|
143
|
+
|
|
144
|
+
// Filter backends that support this route and privacy level
|
|
145
|
+
const compatibleBackends = allBackends.filter((backend) => {
|
|
146
|
+
const { supportedSourceChains, supportedDestinationChains, supportedPrivacyLevels } =
|
|
147
|
+
backend.capabilities
|
|
148
|
+
|
|
149
|
+
const supportsRoute =
|
|
150
|
+
supportedSourceChains.includes(from.chain) &&
|
|
151
|
+
supportedDestinationChains.includes(to.chain)
|
|
152
|
+
|
|
153
|
+
const supportsPrivacy = supportedPrivacyLevels.includes(privacyLevel)
|
|
154
|
+
|
|
155
|
+
return supportsRoute && supportsPrivacy
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
if (compatibleBackends.length === 0) {
|
|
159
|
+
throw new ValidationError(
|
|
160
|
+
`No backend supports route from ${from.chain} to ${to.chain} with privacy level ${privacyLevel}`
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Build quote params
|
|
165
|
+
const quoteParams: QuoteParams = {
|
|
166
|
+
fromChain: from.chain,
|
|
167
|
+
toChain: to.chain,
|
|
168
|
+
fromToken: from.token,
|
|
169
|
+
toToken: to.token,
|
|
170
|
+
amount,
|
|
171
|
+
privacyLevel,
|
|
172
|
+
recipientMetaAddress,
|
|
173
|
+
senderAddress,
|
|
174
|
+
slippageTolerance,
|
|
175
|
+
deadline,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Query all compatible backends in parallel
|
|
179
|
+
const quotePromises = compatibleBackends.map(async (backend) => {
|
|
180
|
+
try {
|
|
181
|
+
const quote = await backend.getQuote(quoteParams)
|
|
182
|
+
return {
|
|
183
|
+
backend: backend.name,
|
|
184
|
+
quote,
|
|
185
|
+
backendInstance: backend,
|
|
186
|
+
success: true,
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
return {
|
|
190
|
+
backend: backend.name,
|
|
191
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
192
|
+
success: false,
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
const results = await Promise.all(quotePromises)
|
|
198
|
+
|
|
199
|
+
// Filter successful quotes
|
|
200
|
+
const successfulRoutes = results
|
|
201
|
+
.filter((r): r is { backend: string; quote: Quote; backendInstance: SettlementBackend; success: true } => r.success)
|
|
202
|
+
.map((r) => ({
|
|
203
|
+
backend: r.backend,
|
|
204
|
+
quote: r.quote,
|
|
205
|
+
backendInstance: r.backendInstance,
|
|
206
|
+
score: 0, // Will be calculated below
|
|
207
|
+
}))
|
|
208
|
+
|
|
209
|
+
if (successfulRoutes.length === 0) {
|
|
210
|
+
const errors = results
|
|
211
|
+
.filter((r): r is { backend: string; error: string; success: false } => !r.success)
|
|
212
|
+
.map((r) => `${r.backend}: ${r.error}`)
|
|
213
|
+
.join(', ')
|
|
214
|
+
|
|
215
|
+
throw new NetworkError(
|
|
216
|
+
`All backends failed to provide quotes: ${errors}`
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Calculate scores and rank
|
|
221
|
+
this.rankRoutes(successfulRoutes, { preferSpeed, preferLowFees })
|
|
222
|
+
|
|
223
|
+
// Sort by score (highest first)
|
|
224
|
+
successfulRoutes.sort((a, b) => b.score - a.score)
|
|
225
|
+
|
|
226
|
+
return successfulRoutes
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Compare quotes from multiple routes side-by-side
|
|
231
|
+
*
|
|
232
|
+
* @param routes - Routes to compare (from findBestRoute)
|
|
233
|
+
* @returns Comparison with best routes by different criteria
|
|
234
|
+
*/
|
|
235
|
+
compareQuotes(routes: RouteWithQuote[]): QuoteComparison {
|
|
236
|
+
if (routes.length === 0) {
|
|
237
|
+
return {
|
|
238
|
+
routes: [],
|
|
239
|
+
bestByCost: null,
|
|
240
|
+
bestBySpeed: null,
|
|
241
|
+
bestByPrivacy: null,
|
|
242
|
+
metadata: {
|
|
243
|
+
totalQueried: 0,
|
|
244
|
+
failures: [],
|
|
245
|
+
queriedAt: Date.now(),
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Find best by cost (lowest total fee)
|
|
251
|
+
const bestByCost = [...routes].sort((a, b) => {
|
|
252
|
+
const costA = this.calculateTotalCost(a.quote)
|
|
253
|
+
const costB = this.calculateTotalCost(b.quote)
|
|
254
|
+
return costA - costB
|
|
255
|
+
})[0]
|
|
256
|
+
|
|
257
|
+
// Find best by speed (lowest estimated time)
|
|
258
|
+
const bestBySpeed = [...routes].sort((a, b) => {
|
|
259
|
+
const timeA = a.quote.estimatedTime ?? Infinity
|
|
260
|
+
const timeB = b.quote.estimatedTime ?? Infinity
|
|
261
|
+
return timeA - timeB
|
|
262
|
+
})[0]
|
|
263
|
+
|
|
264
|
+
// Find best by privacy (full shielded support)
|
|
265
|
+
const bestByPrivacy = [...routes].find((route) => {
|
|
266
|
+
const { supportedPrivacyLevels } = route.backendInstance.capabilities
|
|
267
|
+
return (
|
|
268
|
+
supportedPrivacyLevels.includes('shielded' as PrivacyLevel) ||
|
|
269
|
+
supportedPrivacyLevels.includes('compliant' as PrivacyLevel)
|
|
270
|
+
)
|
|
271
|
+
}) || routes[0]
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
routes,
|
|
275
|
+
bestByCost,
|
|
276
|
+
bestBySpeed,
|
|
277
|
+
bestByPrivacy,
|
|
278
|
+
metadata: {
|
|
279
|
+
totalQueried: routes.length,
|
|
280
|
+
failures: [], // Could track from findBestRoute
|
|
281
|
+
queriedAt: Date.now(),
|
|
282
|
+
},
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Rank routes by score
|
|
288
|
+
*
|
|
289
|
+
* Scoring algorithm:
|
|
290
|
+
* - Base score: 100
|
|
291
|
+
* - Cost: Lower fees = higher score (up to +50)
|
|
292
|
+
* - Speed: Faster execution = higher score (up to +30)
|
|
293
|
+
* - Privacy: Better privacy support = higher score (up to +20)
|
|
294
|
+
*
|
|
295
|
+
* @private
|
|
296
|
+
*/
|
|
297
|
+
private rankRoutes(
|
|
298
|
+
routes: RouteWithQuote[],
|
|
299
|
+
preferences: { preferSpeed: boolean; preferLowFees: boolean }
|
|
300
|
+
): void {
|
|
301
|
+
const { preferSpeed, preferLowFees } = preferences
|
|
302
|
+
|
|
303
|
+
// Calculate min/max for normalization
|
|
304
|
+
const costs = routes.map((r) => this.calculateTotalCost(r.quote))
|
|
305
|
+
const times = routes.map((r) => r.quote.estimatedTime ?? Infinity)
|
|
306
|
+
|
|
307
|
+
const minCost = Math.min(...costs)
|
|
308
|
+
const maxCost = Math.max(...costs)
|
|
309
|
+
const minTime = Math.min(...times.filter(t => t !== Infinity))
|
|
310
|
+
const maxTime = Math.max(...times.filter(t => t !== Infinity))
|
|
311
|
+
|
|
312
|
+
// Assign scores
|
|
313
|
+
routes.forEach((route, index) => {
|
|
314
|
+
let score = 100 // Base score
|
|
315
|
+
|
|
316
|
+
// Cost scoring (0-50 points)
|
|
317
|
+
if (maxCost > minCost) {
|
|
318
|
+
const costNormalized = 1 - (costs[index] - minCost) / (maxCost - minCost)
|
|
319
|
+
const costWeight = preferLowFees ? 50 : 30
|
|
320
|
+
score += costNormalized * costWeight
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Speed scoring (0-30 points)
|
|
324
|
+
const time = times[index]
|
|
325
|
+
if (time !== Infinity && maxTime > minTime) {
|
|
326
|
+
const speedNormalized = 1 - (time - minTime) / (maxTime - minTime)
|
|
327
|
+
const speedWeight = preferSpeed ? 50 : 30
|
|
328
|
+
score += speedNormalized * speedWeight
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Privacy scoring (0-20 points)
|
|
332
|
+
const { supportedPrivacyLevels } = route.backendInstance.capabilities
|
|
333
|
+
if (supportedPrivacyLevels.includes('shielded' as PrivacyLevel)) {
|
|
334
|
+
score += 20
|
|
335
|
+
} else if (supportedPrivacyLevels.includes('compliant' as PrivacyLevel)) {
|
|
336
|
+
score += 10
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
route.score = score
|
|
340
|
+
})
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Calculate total cost from quote
|
|
345
|
+
*
|
|
346
|
+
* Returns total fee in USD if available, otherwise estimates from fees
|
|
347
|
+
*
|
|
348
|
+
* @private
|
|
349
|
+
*/
|
|
350
|
+
private calculateTotalCost(quote: Quote): number {
|
|
351
|
+
// Use totalFeeUSD if available
|
|
352
|
+
if (quote.fees.totalFeeUSD) {
|
|
353
|
+
return parseFloat(quote.fees.totalFeeUSD)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Otherwise estimate from network + protocol fees
|
|
357
|
+
// This is a rough estimate - real implementation would need price feeds
|
|
358
|
+
const networkFee = parseFloat(quote.fees.networkFee) || 0
|
|
359
|
+
const protocolFee = parseFloat(quote.fees.protocolFee) || 0
|
|
360
|
+
|
|
361
|
+
return networkFee + protocolFee
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Create a new SmartRouter instance
|
|
367
|
+
*
|
|
368
|
+
* @param registry - Settlement registry with registered backends
|
|
369
|
+
* @returns SmartRouter instance
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```typescript
|
|
373
|
+
* const registry = new SettlementRegistry()
|
|
374
|
+
* registry.register(nearIntentsBackend)
|
|
375
|
+
* registry.register(zcashBackend)
|
|
376
|
+
*
|
|
377
|
+
* const router = createSmartRouter(registry)
|
|
378
|
+
* const routes = await router.findBestRoute({ ... })
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
export function createSmartRouter(registry: SettlementRegistry): SmartRouter {
|
|
382
|
+
return new SmartRouter(registry)
|
|
383
|
+
}
|