@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/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@sip-protocol/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Core SDK for Shielded Intents Protocol - Privacy layer for cross-chain transactions",
5
+ "author": "SIP Protocol <hello@sip-protocol.org>",
6
+ "homepage": "https://sip-protocol.org",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/sip-protocol/sip-protocol.git",
10
+ "directory": "packages/sdk"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/sip-protocol/sip-protocol/issues"
14
+ },
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.mjs",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.mjs",
22
+ "require": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "src"
28
+ ],
29
+ "dependencies": {
30
+ "@noble/ciphers": "^2.0.1",
31
+ "@noble/curves": "^1.3.0",
32
+ "@noble/hashes": "^1.3.3",
33
+ "@sip-protocol/types": "0.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "@vitest/coverage-v8": "1.6.1",
37
+ "tsup": "^8.0.0",
38
+ "typescript": "^5.3.0",
39
+ "vitest": "^1.1.0"
40
+ },
41
+ "keywords": [
42
+ "sip",
43
+ "privacy",
44
+ "intents",
45
+ "cross-chain",
46
+ "stealth-addresses",
47
+ "zcash"
48
+ ],
49
+ "license": "MIT",
50
+ "scripts": {
51
+ "build": "tsup src/index.ts --format cjs,esm --dts",
52
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
53
+ "lint": "eslint src/",
54
+ "typecheck": "tsc --noEmit",
55
+ "clean": "rm -rf dist",
56
+ "test": "vitest",
57
+ "test:coverage": "vitest run --coverage",
58
+ "bench": "vitest bench --config vitest.bench.config.ts",
59
+ "bench:json": "vitest bench --config vitest.bench.config.ts --outputJson benchmarks/results.json"
60
+ }
61
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Network Adapters for SIP Protocol
3
+ *
4
+ * Provides integration with external networks and services.
5
+ */
6
+
7
+ // NEAR Intents (1Click API)
8
+ export { OneClickClient } from './oneclick-client'
9
+ export {
10
+ NEARIntentsAdapter,
11
+ createNEARIntentsAdapter,
12
+ } from './near-intents'
13
+
14
+ export type {
15
+ SwapRequest,
16
+ PreparedSwap,
17
+ SwapResult,
18
+ NEARIntentsAdapterConfig,
19
+ } from './near-intents'
@@ -0,0 +1,475 @@
1
+ /**
2
+ * NEAR Intents Adapter for SIP Protocol
3
+ *
4
+ * Bridges SIP SDK with NEAR 1Click API, providing privacy-preserving
5
+ * cross-chain swaps using stealth addresses.
6
+ */
7
+
8
+ import {
9
+ type StealthMetaAddress,
10
+ type OneClickQuoteRequest,
11
+ type OneClickQuoteResponse,
12
+ type OneClickStatusResponse,
13
+ type DefuseAssetId,
14
+ type ChainType,
15
+ type ChainId,
16
+ type HexString,
17
+ type Asset,
18
+ PrivacyLevel,
19
+ OneClickSwapType,
20
+ OneClickSwapStatus,
21
+ } from '@sip-protocol/types'
22
+ import { OneClickClient } from './oneclick-client'
23
+ import { generateStealthAddress, decodeStealthMetaAddress } from '../stealth'
24
+ import { ValidationError } from '../errors'
25
+
26
+ /**
27
+ * Swap request parameters (simplified interface for adapter)
28
+ */
29
+ export interface SwapRequest {
30
+ /** Unique request ID */
31
+ requestId: string
32
+ /** Privacy level for the swap */
33
+ privacyLevel: PrivacyLevel
34
+ /** Input asset */
35
+ inputAsset: Asset
36
+ /** Input amount in smallest units */
37
+ inputAmount: bigint
38
+ /** Output asset */
39
+ outputAsset: Asset
40
+ /** Minimum output amount */
41
+ minOutputAmount?: bigint
42
+ }
43
+
44
+ /**
45
+ * Result of preparing a swap with SIP privacy
46
+ */
47
+ export interface PreparedSwap {
48
+ /** Original swap request */
49
+ request: SwapRequest
50
+ /** 1Click quote request */
51
+ quoteRequest: OneClickQuoteRequest
52
+ /** Generated stealth address (for shielded/compliant modes) */
53
+ stealthAddress?: {
54
+ address: HexString
55
+ ephemeralPublicKey: HexString
56
+ viewTag: number
57
+ }
58
+ /** Shared secret for stealth address derivation (keep private!) */
59
+ sharedSecret?: HexString
60
+ }
61
+
62
+ /**
63
+ * Result of executing a swap
64
+ */
65
+ export interface SwapResult {
66
+ /** Request ID */
67
+ requestId: string
68
+ /** 1Click quote ID */
69
+ quoteId: string
70
+ /** Deposit address for input tokens */
71
+ depositAddress: string
72
+ /** Expected input amount */
73
+ amountIn: string
74
+ /** Expected output amount */
75
+ amountOut: string
76
+ /** Current status */
77
+ status: OneClickSwapStatus
78
+ /** Deposit transaction hash (after deposit) */
79
+ depositTxHash?: string
80
+ /** Settlement transaction hash (after success) */
81
+ settlementTxHash?: string
82
+ /** Stealth address for recipient (if privacy mode) */
83
+ stealthRecipient?: string
84
+ /** Ephemeral public key (for recipient to derive stealth key) */
85
+ ephemeralPublicKey?: string
86
+ }
87
+
88
+ /**
89
+ * Configuration for NEAR Intents adapter
90
+ */
91
+ export interface NEARIntentsAdapterConfig {
92
+ /** OneClickClient instance or config */
93
+ client?: OneClickClient
94
+ /** Base URL for 1Click API */
95
+ baseUrl?: string
96
+ /** JWT token for authentication */
97
+ jwtToken?: string
98
+ /** Default slippage tolerance in basis points (100 = 1%) */
99
+ defaultSlippage?: number
100
+ /** Default deadline offset in seconds */
101
+ defaultDeadlineOffset?: number
102
+ /** Custom asset mappings (merged with defaults) */
103
+ assetMappings?: Record<string, DefuseAssetId>
104
+ }
105
+
106
+ /**
107
+ * Default asset mapping from SIP format to Defuse asset identifier
108
+ */
109
+ const DEFAULT_ASSET_MAPPINGS: Record<string, DefuseAssetId> = {
110
+ // NEAR assets
111
+ 'near:NEAR': 'near:mainnet:native',
112
+ 'near:wNEAR': 'near:mainnet:wrap.near',
113
+
114
+ // Ethereum assets
115
+ 'ethereum:ETH': 'eth:1:native',
116
+ 'ethereum:USDC': 'eth:1:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
117
+ 'ethereum:USDT': 'eth:1:0xdac17f958d2ee523a2206206994597c13d831ec7',
118
+
119
+ // Solana assets
120
+ 'solana:SOL': 'sol:mainnet:native',
121
+
122
+ // Zcash assets
123
+ 'zcash:ZEC': 'zcash:mainnet:native',
124
+
125
+ // Arbitrum assets
126
+ 'arbitrum:ETH': 'arb:42161:native',
127
+
128
+ // Base assets
129
+ 'base:ETH': 'base:8453:native',
130
+
131
+ // Polygon assets
132
+ 'polygon:MATIC': 'polygon:137:native',
133
+ }
134
+
135
+ /**
136
+ * Chain ID to ChainType mapping
137
+ */
138
+ const CHAIN_TYPE_MAP: Record<ChainId, ChainType> = {
139
+ near: 'near',
140
+ ethereum: 'eth',
141
+ solana: 'sol',
142
+ zcash: 'zcash',
143
+ polygon: 'polygon',
144
+ arbitrum: 'arb',
145
+ optimism: 'optimism',
146
+ base: 'base',
147
+ }
148
+
149
+ /**
150
+ * NEAR Intents Adapter
151
+ *
152
+ * Provides privacy-preserving cross-chain swaps via NEAR 1Click API.
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * const adapter = new NEARIntentsAdapter({
157
+ * jwtToken: process.env.NEAR_INTENTS_JWT,
158
+ * })
159
+ *
160
+ * // Or with custom asset mappings (e.g., testnet)
161
+ * const testnetAdapter = new NEARIntentsAdapter({
162
+ * jwtToken: process.env.NEAR_INTENTS_JWT,
163
+ * assetMappings: {
164
+ * 'near:testUSDC': 'near:testnet:usdc.test',
165
+ * },
166
+ * })
167
+ *
168
+ * // Prepare a swap with stealth recipient
169
+ * const prepared = await adapter.prepareSwap(intent, recipientMetaAddress)
170
+ *
171
+ * // Get quote
172
+ * const quote = await adapter.getQuote(prepared)
173
+ *
174
+ * // Execute (after depositing to depositAddress)
175
+ * const result = await adapter.trackSwap(quote.depositAddress)
176
+ * ```
177
+ */
178
+ export class NEARIntentsAdapter {
179
+ private readonly client: OneClickClient
180
+ private readonly defaultSlippage: number
181
+ private readonly defaultDeadlineOffset: number
182
+ private readonly assetMappings: Record<string, DefuseAssetId>
183
+
184
+ constructor(config: NEARIntentsAdapterConfig = {}) {
185
+ this.client = config.client ?? new OneClickClient({
186
+ baseUrl: config.baseUrl,
187
+ jwtToken: config.jwtToken,
188
+ })
189
+ this.defaultSlippage = config.defaultSlippage ?? 100 // 1%
190
+ this.defaultDeadlineOffset = config.defaultDeadlineOffset ?? 3600 // 1 hour
191
+ this.assetMappings = {
192
+ ...DEFAULT_ASSET_MAPPINGS,
193
+ ...config.assetMappings,
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Get the underlying OneClick client
199
+ */
200
+ getClient(): OneClickClient {
201
+ return this.client
202
+ }
203
+
204
+ /**
205
+ * Prepare a swap request
206
+ *
207
+ * For shielded/compliant modes, generates a stealth address for the recipient.
208
+ *
209
+ * @param request - Swap request parameters
210
+ * @param recipientMetaAddress - Recipient's stealth meta-address (for privacy modes)
211
+ * @param senderAddress - Sender's address for refunds
212
+ * @returns Prepared swap with quote request
213
+ */
214
+ async prepareSwap(
215
+ request: SwapRequest,
216
+ recipientMetaAddress?: StealthMetaAddress | string,
217
+ senderAddress?: string,
218
+ ): Promise<PreparedSwap> {
219
+ // Validate request
220
+ this.validateRequest(request)
221
+
222
+ // Determine recipient address
223
+ let recipientAddress: string
224
+ let stealthData: PreparedSwap['stealthAddress']
225
+ let sharedSecret: HexString | undefined
226
+
227
+ if (request.privacyLevel !== PrivacyLevel.TRANSPARENT) {
228
+ // Privacy mode requires stealth address
229
+ if (!recipientMetaAddress) {
230
+ throw new ValidationError(
231
+ 'recipientMetaAddress is required for shielded/compliant privacy modes',
232
+ 'recipientMetaAddress'
233
+ )
234
+ }
235
+
236
+ // Decode if string
237
+ const metaAddr = typeof recipientMetaAddress === 'string'
238
+ ? decodeStealthMetaAddress(recipientMetaAddress)
239
+ : recipientMetaAddress
240
+
241
+ // Generate stealth address
242
+ const { stealthAddress, sharedSecret: secret } = generateStealthAddress(metaAddr)
243
+
244
+ recipientAddress = stealthAddress.address
245
+ stealthData = stealthAddress
246
+ sharedSecret = secret
247
+ } else {
248
+ // Transparent mode uses direct address
249
+ if (!senderAddress) {
250
+ throw new ValidationError(
251
+ 'senderAddress is required for transparent mode (or use stealth address)',
252
+ 'senderAddress'
253
+ )
254
+ }
255
+ recipientAddress = senderAddress
256
+ }
257
+
258
+ // Build quote request
259
+ const quoteRequest = this.buildQuoteRequest(request, recipientAddress, senderAddress)
260
+
261
+ return {
262
+ request,
263
+ quoteRequest,
264
+ stealthAddress: stealthData,
265
+ sharedSecret,
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Get a quote for a prepared swap
271
+ *
272
+ * @param prepared - Prepared swap from prepareSwap()
273
+ * @returns Quote response with deposit address
274
+ */
275
+ async getQuote(prepared: PreparedSwap): Promise<OneClickQuoteResponse> {
276
+ return this.client.quote(prepared.quoteRequest)
277
+ }
278
+
279
+ /**
280
+ * Get a dry quote (preview without deposit address)
281
+ *
282
+ * @param prepared - Prepared swap
283
+ * @returns Quote preview
284
+ */
285
+ async getDryQuote(prepared: PreparedSwap): Promise<OneClickQuoteResponse> {
286
+ return this.client.dryQuote(prepared.quoteRequest)
287
+ }
288
+
289
+ /**
290
+ * Notify 1Click of deposit transaction
291
+ *
292
+ * @param depositAddress - Deposit address from quote
293
+ * @param txHash - Deposit transaction hash
294
+ * @param nearAccount - NEAR account (if depositing from NEAR)
295
+ */
296
+ async notifyDeposit(
297
+ depositAddress: string,
298
+ txHash: string,
299
+ nearAccount?: string,
300
+ ): Promise<void> {
301
+ await this.client.submitDeposit({
302
+ depositAddress,
303
+ txHash,
304
+ nearSenderAccount: nearAccount,
305
+ })
306
+ }
307
+
308
+ /**
309
+ * Get current swap status
310
+ *
311
+ * @param depositAddress - Deposit address from quote
312
+ * @returns Current status
313
+ */
314
+ async getStatus(depositAddress: string): Promise<OneClickStatusResponse> {
315
+ return this.client.getStatus(depositAddress)
316
+ }
317
+
318
+ /**
319
+ * Wait for swap to complete
320
+ *
321
+ * @param depositAddress - Deposit address from quote
322
+ * @param options - Polling options
323
+ * @returns Final status
324
+ */
325
+ async waitForCompletion(
326
+ depositAddress: string,
327
+ options?: {
328
+ interval?: number
329
+ timeout?: number
330
+ onStatus?: (status: OneClickStatusResponse) => void
331
+ },
332
+ ): Promise<OneClickStatusResponse> {
333
+ return this.client.waitForStatus(depositAddress, options)
334
+ }
335
+
336
+ /**
337
+ * Execute a full swap flow
338
+ *
339
+ * This is a convenience method that:
340
+ * 1. Prepares the swap with stealth address
341
+ * 2. Gets a quote
342
+ * 3. Returns all info needed for the user to deposit
343
+ *
344
+ * @param request - Swap request parameters
345
+ * @param recipientMetaAddress - Recipient's stealth meta-address
346
+ * @param senderAddress - Sender's address for refunds
347
+ * @returns Swap result with deposit instructions
348
+ */
349
+ async initiateSwap(
350
+ request: SwapRequest,
351
+ recipientMetaAddress?: StealthMetaAddress | string,
352
+ senderAddress?: string,
353
+ ): Promise<SwapResult> {
354
+ // Prepare swap
355
+ const prepared = await this.prepareSwap(request, recipientMetaAddress, senderAddress)
356
+
357
+ // Get quote
358
+ const quote = await this.getQuote(prepared)
359
+
360
+ return {
361
+ requestId: request.requestId,
362
+ quoteId: quote.quoteId,
363
+ depositAddress: quote.depositAddress,
364
+ amountIn: quote.amountIn,
365
+ amountOut: quote.amountOut,
366
+ status: OneClickSwapStatus.PENDING_DEPOSIT,
367
+ stealthRecipient: prepared.stealthAddress?.address,
368
+ ephemeralPublicKey: prepared.stealthAddress?.ephemeralPublicKey,
369
+ }
370
+ }
371
+
372
+ // ─── Asset Mapping ────────────────────────────────────────────────────────────
373
+
374
+ /**
375
+ * Convert SIP asset to Defuse asset identifier
376
+ */
377
+ mapAsset(chain: ChainId, symbol: string): DefuseAssetId {
378
+ const key = `${chain}:${symbol}`
379
+ const mapped = this.assetMappings[key]
380
+
381
+ if (!mapped) {
382
+ throw new ValidationError(
383
+ `Unknown asset mapping for ${key}. Supported: ${Object.keys(this.assetMappings).join(', ')}`,
384
+ 'asset',
385
+ { chain, symbol }
386
+ )
387
+ }
388
+
389
+ return mapped
390
+ }
391
+
392
+ /**
393
+ * Convert SIP chain ID to 1Click chain type
394
+ */
395
+ mapChainType(chain: ChainId): ChainType {
396
+ const mapped = CHAIN_TYPE_MAP[chain]
397
+
398
+ if (!mapped) {
399
+ throw new ValidationError(
400
+ `Unknown chain mapping for ${chain}`,
401
+ 'chain',
402
+ { chain }
403
+ )
404
+ }
405
+
406
+ return mapped
407
+ }
408
+
409
+ // ─── Private Methods ──────────────────────────────────────────────────────────
410
+
411
+ private validateRequest(request: SwapRequest): void {
412
+ if (!request) {
413
+ throw new ValidationError('request is required', 'request')
414
+ }
415
+ if (!request.requestId) {
416
+ throw new ValidationError('requestId is required', 'request.requestId')
417
+ }
418
+ if (!request.inputAsset) {
419
+ throw new ValidationError('inputAsset is required', 'request.inputAsset')
420
+ }
421
+ if (!request.outputAsset) {
422
+ throw new ValidationError('outputAsset is required', 'request.outputAsset')
423
+ }
424
+ if (request.inputAmount === undefined || request.inputAmount === null) {
425
+ throw new ValidationError('inputAmount is required', 'request.inputAmount')
426
+ }
427
+ }
428
+
429
+ private buildQuoteRequest(
430
+ request: SwapRequest,
431
+ recipient: string,
432
+ refundTo?: string,
433
+ ): OneClickQuoteRequest {
434
+ // Map assets
435
+ const originAsset = this.mapAsset(
436
+ request.inputAsset.chain,
437
+ request.inputAsset.symbol
438
+ )
439
+ const destinationAsset = this.mapAsset(
440
+ request.outputAsset.chain,
441
+ request.outputAsset.symbol
442
+ )
443
+
444
+ // Map chain types
445
+ const depositType = this.mapChainType(request.inputAsset.chain)
446
+ const recipientType = this.mapChainType(request.outputAsset.chain)
447
+ const refundType = depositType // Refund to same chain as deposit
448
+
449
+ // Calculate deadline
450
+ const deadline = new Date(Date.now() + this.defaultDeadlineOffset * 1000).toISOString()
451
+
452
+ return {
453
+ swapType: OneClickSwapType.EXACT_INPUT,
454
+ originAsset,
455
+ destinationAsset,
456
+ amount: request.inputAmount.toString(),
457
+ recipient,
458
+ refundTo: refundTo ?? recipient,
459
+ depositType,
460
+ recipientType,
461
+ refundType,
462
+ slippageTolerance: this.defaultSlippage,
463
+ deadline,
464
+ }
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Create a new NEAR Intents adapter
470
+ */
471
+ export function createNEARIntentsAdapter(
472
+ config?: NEARIntentsAdapterConfig
473
+ ): NEARIntentsAdapter {
474
+ return new NEARIntentsAdapter(config)
475
+ }