@sip-protocol/sdk 0.1.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/LICENSE +21 -0
- package/dist/index.d.mts +3640 -0
- package/dist/index.d.ts +3640 -0
- package/dist/index.js +5725 -0
- package/dist/index.mjs +5606 -0
- package/package.json +61 -0
- package/src/adapters/index.ts +19 -0
- package/src/adapters/near-intents.ts +475 -0
- package/src/adapters/oneclick-client.ts +367 -0
- package/src/commitment.ts +470 -0
- package/src/crypto.ts +93 -0
- package/src/errors.ts +471 -0
- package/src/index.ts +369 -0
- package/src/intent.ts +488 -0
- package/src/privacy.ts +382 -0
- package/src/proofs/index.ts +52 -0
- package/src/proofs/interface.ts +228 -0
- package/src/proofs/mock.ts +258 -0
- package/src/proofs/noir.ts +233 -0
- package/src/sip.ts +299 -0
- package/src/solver/index.ts +25 -0
- package/src/solver/mock-solver.ts +278 -0
- package/src/stealth.ts +414 -0
- package/src/validation.ts +401 -0
- package/src/wallet/base-adapter.ts +407 -0
- package/src/wallet/errors.ts +106 -0
- package/src/wallet/ethereum/adapter.ts +655 -0
- package/src/wallet/ethereum/index.ts +48 -0
- package/src/wallet/ethereum/mock.ts +505 -0
- package/src/wallet/ethereum/types.ts +364 -0
- package/src/wallet/index.ts +116 -0
- package/src/wallet/registry.ts +207 -0
- package/src/wallet/solana/adapter.ts +533 -0
- package/src/wallet/solana/index.ts +40 -0
- package/src/wallet/solana/mock.ts +522 -0
- package/src/wallet/solana/types.ts +253 -0
- package/src/zcash/index.ts +53 -0
- package/src/zcash/rpc-client.ts +623 -0
- package/src/zcash/shielded-service.ts +641 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NEAR 1Click API HTTP Client
|
|
3
|
+
*
|
|
4
|
+
* Provides typed access to the NEAR Intents 1Click API for cross-chain swaps.
|
|
5
|
+
*
|
|
6
|
+
* @see https://docs.near-intents.org/near-intents/integration/distribution-channels/1click-api
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
type OneClickConfig,
|
|
11
|
+
type OneClickToken,
|
|
12
|
+
type OneClickQuoteRequest,
|
|
13
|
+
type OneClickQuoteResponse,
|
|
14
|
+
type OneClickDepositSubmit,
|
|
15
|
+
type OneClickStatusResponse,
|
|
16
|
+
type OneClickWithdrawal,
|
|
17
|
+
type OneClickError,
|
|
18
|
+
OneClickSwapStatus,
|
|
19
|
+
OneClickErrorCode,
|
|
20
|
+
} from '@sip-protocol/types'
|
|
21
|
+
import { NetworkError, ErrorCode, ValidationError } from '../errors'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default configuration values
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULTS = {
|
|
27
|
+
baseUrl: 'https://1click.chaindefuser.com',
|
|
28
|
+
timeout: 30000,
|
|
29
|
+
} as const
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* HTTP client for NEAR 1Click API
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const client = new OneClickClient({
|
|
37
|
+
* jwtToken: process.env.NEAR_INTENTS_JWT,
|
|
38
|
+
* })
|
|
39
|
+
*
|
|
40
|
+
* // Get available tokens
|
|
41
|
+
* const tokens = await client.getTokens()
|
|
42
|
+
*
|
|
43
|
+
* // Request a quote
|
|
44
|
+
* const quote = await client.quote({
|
|
45
|
+
* swapType: OneClickSwapType.EXACT_INPUT,
|
|
46
|
+
* originAsset: 'near:mainnet:wrap.near',
|
|
47
|
+
* destinationAsset: 'eth:1:native',
|
|
48
|
+
* amount: '1000000000000000000000000',
|
|
49
|
+
* refundTo: 'user.near',
|
|
50
|
+
* recipient: '0x742d35Cc...',
|
|
51
|
+
* depositType: 'near',
|
|
52
|
+
* refundType: 'near',
|
|
53
|
+
* recipientType: 'eth',
|
|
54
|
+
* })
|
|
55
|
+
*
|
|
56
|
+
* // Check status
|
|
57
|
+
* const status = await client.getStatus(quote.depositAddress)
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export class OneClickClient {
|
|
61
|
+
private readonly baseUrl: string
|
|
62
|
+
private readonly jwtToken?: string
|
|
63
|
+
private readonly timeout: number
|
|
64
|
+
private readonly fetchFn: typeof fetch
|
|
65
|
+
|
|
66
|
+
constructor(config: OneClickConfig = {}) {
|
|
67
|
+
this.baseUrl = config.baseUrl ?? DEFAULTS.baseUrl
|
|
68
|
+
this.jwtToken = config.jwtToken
|
|
69
|
+
this.timeout = config.timeout ?? DEFAULTS.timeout
|
|
70
|
+
this.fetchFn = config.fetch ?? globalThis.fetch
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get all supported tokens
|
|
75
|
+
*
|
|
76
|
+
* @returns Array of supported tokens with metadata
|
|
77
|
+
*/
|
|
78
|
+
async getTokens(): Promise<OneClickToken[]> {
|
|
79
|
+
return this.get<OneClickToken[]>('/v0/tokens')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Request a swap quote
|
|
84
|
+
*
|
|
85
|
+
* @param request - Quote request parameters
|
|
86
|
+
* @returns Quote response with deposit address and amounts
|
|
87
|
+
* @throws {NetworkError} On API errors
|
|
88
|
+
* @throws {ValidationError} On invalid parameters
|
|
89
|
+
*/
|
|
90
|
+
async quote(request: OneClickQuoteRequest): Promise<OneClickQuoteResponse> {
|
|
91
|
+
this.validateQuoteRequest(request)
|
|
92
|
+
return this.post<OneClickQuoteResponse>('/v0/quote', request)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Request a dry quote (preview without deposit address)
|
|
97
|
+
*
|
|
98
|
+
* Useful for UI price estimates without committing to a swap.
|
|
99
|
+
*
|
|
100
|
+
* @param request - Quote request parameters (dry flag set automatically)
|
|
101
|
+
* @returns Quote preview without deposit address
|
|
102
|
+
*/
|
|
103
|
+
async dryQuote(request: Omit<OneClickQuoteRequest, 'dry'>): Promise<OneClickQuoteResponse> {
|
|
104
|
+
return this.quote({ ...request, dry: true })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Submit deposit transaction notification
|
|
109
|
+
*
|
|
110
|
+
* Call this after depositing to the depositAddress to speed up detection.
|
|
111
|
+
*
|
|
112
|
+
* @param deposit - Deposit submission details
|
|
113
|
+
* @returns Updated quote response
|
|
114
|
+
*/
|
|
115
|
+
async submitDeposit(deposit: OneClickDepositSubmit): Promise<OneClickQuoteResponse> {
|
|
116
|
+
if (!deposit.txHash) {
|
|
117
|
+
throw new ValidationError('txHash is required', 'deposit.txHash')
|
|
118
|
+
}
|
|
119
|
+
if (!deposit.depositAddress) {
|
|
120
|
+
throw new ValidationError('depositAddress is required', 'deposit.depositAddress')
|
|
121
|
+
}
|
|
122
|
+
return this.post<OneClickQuoteResponse>('/v0/deposit/submit', deposit)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get swap status
|
|
127
|
+
*
|
|
128
|
+
* @param depositAddress - Deposit address from quote
|
|
129
|
+
* @param depositMemo - Optional memo for memo-based deposits
|
|
130
|
+
* @returns Current swap status
|
|
131
|
+
*/
|
|
132
|
+
async getStatus(depositAddress: string, depositMemo?: string): Promise<OneClickStatusResponse> {
|
|
133
|
+
if (!depositAddress) {
|
|
134
|
+
throw new ValidationError('depositAddress is required', 'depositAddress')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const params = new URLSearchParams({ depositAddress })
|
|
138
|
+
if (depositMemo) {
|
|
139
|
+
params.set('depositMemo', depositMemo)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return this.get<OneClickStatusResponse>(`/v0/status?${params.toString()}`)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Poll status until terminal state or timeout
|
|
147
|
+
*
|
|
148
|
+
* @param depositAddress - Deposit address from quote
|
|
149
|
+
* @param options - Polling options
|
|
150
|
+
* @returns Final status when terminal state reached
|
|
151
|
+
*/
|
|
152
|
+
async waitForStatus(
|
|
153
|
+
depositAddress: string,
|
|
154
|
+
options: {
|
|
155
|
+
/** Polling interval in ms (default: 3000) */
|
|
156
|
+
interval?: number
|
|
157
|
+
/** Maximum wait time in ms (default: 300000 = 5 minutes) */
|
|
158
|
+
timeout?: number
|
|
159
|
+
/** Callback on each status check */
|
|
160
|
+
onStatus?: (status: OneClickStatusResponse) => void
|
|
161
|
+
} = {}
|
|
162
|
+
): Promise<OneClickStatusResponse> {
|
|
163
|
+
const interval = options.interval ?? 3000
|
|
164
|
+
const timeout = options.timeout ?? 300000
|
|
165
|
+
const startTime = Date.now()
|
|
166
|
+
|
|
167
|
+
const terminalStates = new Set([
|
|
168
|
+
OneClickSwapStatus.SUCCESS,
|
|
169
|
+
OneClickSwapStatus.FAILED,
|
|
170
|
+
OneClickSwapStatus.REFUNDED,
|
|
171
|
+
])
|
|
172
|
+
|
|
173
|
+
while (Date.now() - startTime < timeout) {
|
|
174
|
+
const status = await this.getStatus(depositAddress)
|
|
175
|
+
|
|
176
|
+
if (options.onStatus) {
|
|
177
|
+
options.onStatus(status)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (terminalStates.has(status.status)) {
|
|
181
|
+
return status
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
await this.delay(interval)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
throw new NetworkError(
|
|
188
|
+
`Status polling timed out after ${timeout}ms`,
|
|
189
|
+
ErrorCode.NETWORK_TIMEOUT,
|
|
190
|
+
{ endpoint: '/v0/status', context: { depositAddress, timeout } }
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get withdrawals for ANY_INPUT deposits
|
|
196
|
+
*
|
|
197
|
+
* @param depositAddress - Deposit address
|
|
198
|
+
* @param depositMemo - Optional deposit memo
|
|
199
|
+
* @param options - Pagination options
|
|
200
|
+
* @returns Array of withdrawals
|
|
201
|
+
*/
|
|
202
|
+
async getWithdrawals(
|
|
203
|
+
depositAddress: string,
|
|
204
|
+
depositMemo?: string,
|
|
205
|
+
options: {
|
|
206
|
+
timestampFrom?: string
|
|
207
|
+
page?: number
|
|
208
|
+
limit?: number
|
|
209
|
+
sortOrder?: 'asc' | 'desc'
|
|
210
|
+
} = {}
|
|
211
|
+
): Promise<OneClickWithdrawal[]> {
|
|
212
|
+
const params = new URLSearchParams({ depositAddress })
|
|
213
|
+
if (depositMemo) params.set('depositMemo', depositMemo)
|
|
214
|
+
if (options.timestampFrom) params.set('timestampFrom', options.timestampFrom)
|
|
215
|
+
if (options.page) params.set('page', options.page.toString())
|
|
216
|
+
if (options.limit) params.set('limit', options.limit.toString())
|
|
217
|
+
if (options.sortOrder) params.set('sortOrder', options.sortOrder)
|
|
218
|
+
|
|
219
|
+
return this.get<OneClickWithdrawal[]>(`/v0/any-input/withdrawals?${params.toString()}`)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ─── Private Methods ──────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
private validateQuoteRequest(request: OneClickQuoteRequest): void {
|
|
225
|
+
if (!request.swapType) {
|
|
226
|
+
throw new ValidationError('swapType is required', 'request.swapType')
|
|
227
|
+
}
|
|
228
|
+
if (!request.originAsset) {
|
|
229
|
+
throw new ValidationError('originAsset is required', 'request.originAsset')
|
|
230
|
+
}
|
|
231
|
+
if (!request.destinationAsset) {
|
|
232
|
+
throw new ValidationError('destinationAsset is required', 'request.destinationAsset')
|
|
233
|
+
}
|
|
234
|
+
if (!request.amount) {
|
|
235
|
+
throw new ValidationError('amount is required', 'request.amount')
|
|
236
|
+
}
|
|
237
|
+
if (!request.refundTo) {
|
|
238
|
+
throw new ValidationError('refundTo is required', 'request.refundTo')
|
|
239
|
+
}
|
|
240
|
+
if (!request.recipient) {
|
|
241
|
+
throw new ValidationError('recipient is required', 'request.recipient')
|
|
242
|
+
}
|
|
243
|
+
if (!request.depositType) {
|
|
244
|
+
throw new ValidationError('depositType is required', 'request.depositType')
|
|
245
|
+
}
|
|
246
|
+
if (!request.refundType) {
|
|
247
|
+
throw new ValidationError('refundType is required', 'request.refundType')
|
|
248
|
+
}
|
|
249
|
+
if (!request.recipientType) {
|
|
250
|
+
throw new ValidationError('recipientType is required', 'request.recipientType')
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private async get<T>(path: string): Promise<T> {
|
|
255
|
+
return this.request<T>('GET', path)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private async post<T>(path: string, body: unknown): Promise<T> {
|
|
259
|
+
return this.request<T>('POST', path, body)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
263
|
+
const url = `${this.baseUrl}${path}`
|
|
264
|
+
const headers: Record<string, string> = {
|
|
265
|
+
'Content-Type': 'application/json',
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (this.jwtToken) {
|
|
269
|
+
headers['Authorization'] = `Bearer ${this.jwtToken}`
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const controller = new AbortController()
|
|
273
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout)
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const response = await this.fetchFn(url, {
|
|
277
|
+
method,
|
|
278
|
+
headers,
|
|
279
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
280
|
+
signal: controller.signal,
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
clearTimeout(timeoutId)
|
|
284
|
+
|
|
285
|
+
if (!response.ok) {
|
|
286
|
+
const error = await this.parseError(response)
|
|
287
|
+
throw new NetworkError(
|
|
288
|
+
error.message || `API request failed with status ${response.status}`,
|
|
289
|
+
this.mapErrorCode(error.code, response.status),
|
|
290
|
+
{
|
|
291
|
+
endpoint: url,
|
|
292
|
+
statusCode: response.status,
|
|
293
|
+
context: { error },
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return response.json() as Promise<T>
|
|
299
|
+
} catch (error) {
|
|
300
|
+
clearTimeout(timeoutId)
|
|
301
|
+
|
|
302
|
+
if (error instanceof NetworkError) {
|
|
303
|
+
throw error
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
307
|
+
throw new NetworkError(
|
|
308
|
+
`Request timed out after ${this.timeout}ms`,
|
|
309
|
+
ErrorCode.NETWORK_TIMEOUT,
|
|
310
|
+
{ endpoint: url }
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
throw new NetworkError(
|
|
315
|
+
`Network request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
316
|
+
ErrorCode.NETWORK_FAILED,
|
|
317
|
+
{ endpoint: url, cause: error instanceof Error ? error : undefined }
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private async parseError(response: Response): Promise<OneClickError> {
|
|
323
|
+
try {
|
|
324
|
+
return await response.json()
|
|
325
|
+
} catch {
|
|
326
|
+
return {
|
|
327
|
+
code: 'UNKNOWN_ERROR',
|
|
328
|
+
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private mapErrorCode(apiCode: string | undefined, statusCode: number): ErrorCode {
|
|
334
|
+
if (apiCode) {
|
|
335
|
+
switch (apiCode) {
|
|
336
|
+
case OneClickErrorCode.INSUFFICIENT_LIQUIDITY:
|
|
337
|
+
case OneClickErrorCode.UNSUPPORTED_PAIR:
|
|
338
|
+
case OneClickErrorCode.AMOUNT_TOO_LOW:
|
|
339
|
+
case OneClickErrorCode.DEADLINE_TOO_SHORT:
|
|
340
|
+
case OneClickErrorCode.INVALID_PARAMS:
|
|
341
|
+
return ErrorCode.VALIDATION_FAILED
|
|
342
|
+
|
|
343
|
+
case OneClickErrorCode.RATE_LIMITED:
|
|
344
|
+
return ErrorCode.RATE_LIMITED
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
switch (statusCode) {
|
|
349
|
+
case 400:
|
|
350
|
+
return ErrorCode.VALIDATION_FAILED
|
|
351
|
+
case 401:
|
|
352
|
+
return ErrorCode.API_ERROR
|
|
353
|
+
case 429:
|
|
354
|
+
return ErrorCode.RATE_LIMITED
|
|
355
|
+
case 500:
|
|
356
|
+
case 502:
|
|
357
|
+
case 503:
|
|
358
|
+
return ErrorCode.NETWORK_UNAVAILABLE
|
|
359
|
+
default:
|
|
360
|
+
return ErrorCode.API_ERROR
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private delay(ms: number): Promise<void> {
|
|
365
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
366
|
+
}
|
|
367
|
+
}
|