@sip-protocol/sdk 0.2.10 → 0.3.1
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 +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1643 -266
- package/dist/browser.mjs +259 -5
- package/dist/chunk-4IFOPYJF.mjs +11950 -0
- package/dist/chunk-4VJHI66K.mjs +12120 -0
- package/dist/chunk-5BAS4D44.mjs +10283 -0
- package/dist/chunk-6WOV2YNG.mjs +10179 -0
- package/dist/chunk-7IMRM7LN.mjs +12149 -0
- package/dist/chunk-DU7LQDD2.mjs +10148 -0
- package/dist/{chunk-AV37IZST.mjs → chunk-JNNXNTSS.mjs} +14 -0
- package/dist/chunk-KXN6IWL5.mjs +10736 -0
- package/dist/chunk-MR7HRCRS.mjs +10165 -0
- package/dist/chunk-NDGUWOOZ.mjs +10157 -0
- package/dist/chunk-O4Y2ZUDL.mjs +12721 -0
- package/dist/chunk-UPTISVCY.mjs +10304 -0
- package/dist/chunk-VITVG25F.mjs +982 -0
- package/dist/chunk-VXSHK7US.mjs +10158 -0
- package/dist/chunk-W3YXIQ7L.mjs +11950 -0
- package/dist/chunk-YZCK337Y.mjs +12155 -0
- package/dist/index-Ba7njCU3.d.ts +6925 -0
- package/dist/index-Co26-vbG.d.mts +6925 -0
- package/dist/index-DAgedMrt.d.ts +6927 -0
- package/dist/index-DW7AQwcU.d.mts +6927 -0
- package/dist/{index-CAhjA4kh.d.mts → index-DqZoHYKI.d.mts} +362 -6
- package/dist/index-dTtK_DTl.d.ts +6762 -0
- package/dist/index-jnkYu-Z4.d.mts +6762 -0
- package/dist/{index-BFOKTz2z.d.ts → index-vB1N1mHd.d.ts} +362 -6
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1340 -199
- package/dist/index.mjs +19 -1
- package/dist/noir-BHQtFvRk.d.mts +467 -0
- package/dist/noir-BHQtFvRk.d.ts +467 -0
- package/package.json +14 -14
- package/src/index.ts +32 -0
- package/src/proofs/worker.ts +240 -4
- 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
- package/src/zcash/bridge.ts +20 -2
- package/src/zcash/swap-service.ts +20 -2
- package/LICENSE +0 -21
package/src/proofs/worker.ts
CHANGED
|
@@ -181,6 +181,211 @@ export function createWorkerBlobURL(): string {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// Generate validity proof
|
|
185
|
+
async function generateValidityProof(id, params) {
|
|
186
|
+
if (!isReady) {
|
|
187
|
+
sendError(id, new Error('Worker not initialized'));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
sendProgress(id, 'witness', 20, 'Preparing validity witness...');
|
|
193
|
+
|
|
194
|
+
// Import noble crypto for hashing
|
|
195
|
+
const { sha256 } = await import('@noble/hashes/sha256');
|
|
196
|
+
|
|
197
|
+
// Convert inputs to field elements
|
|
198
|
+
const intentHashField = hexToField(params.intentHash);
|
|
199
|
+
const senderAddressField = hexToField(params.senderAddress);
|
|
200
|
+
const senderBlindingField = bytesToField(params.senderBlinding);
|
|
201
|
+
const senderSecretField = bytesToField(params.senderSecret);
|
|
202
|
+
const nonceField = bytesToField(params.nonce);
|
|
203
|
+
|
|
204
|
+
// Compute sender commitment
|
|
205
|
+
const addressBytes = hexToBytes(senderAddressField);
|
|
206
|
+
const blindingBytes = hexToBytes(senderBlindingField.padStart(64, '0'));
|
|
207
|
+
const commitmentPreimage = new Uint8Array([...addressBytes, ...blindingBytes]);
|
|
208
|
+
const commitmentHash = sha256(commitmentPreimage);
|
|
209
|
+
const commitmentX = bytesToHex(commitmentHash.slice(0, 16)).padStart(64, '0');
|
|
210
|
+
const commitmentY = bytesToHex(commitmentHash.slice(16, 32)).padStart(64, '0');
|
|
211
|
+
|
|
212
|
+
// Compute nullifier
|
|
213
|
+
const secretBytes = hexToBytes(senderSecretField.padStart(64, '0'));
|
|
214
|
+
const intentBytes = hexToBytes(intentHashField);
|
|
215
|
+
const nonceBytes = hexToBytes(nonceField.padStart(64, '0'));
|
|
216
|
+
const nullifierPreimage = new Uint8Array([...secretBytes, ...intentBytes, ...nonceBytes]);
|
|
217
|
+
const nullifierHash = sha256(nullifierPreimage);
|
|
218
|
+
const nullifier = bytesToHex(nullifierHash);
|
|
219
|
+
|
|
220
|
+
const signature = Array.from(params.authorizationSignature);
|
|
221
|
+
const messageHash = fieldToBytes32(intentHashField);
|
|
222
|
+
|
|
223
|
+
// Get public key coordinates
|
|
224
|
+
let pubKeyX, pubKeyY;
|
|
225
|
+
if (params.senderPublicKey) {
|
|
226
|
+
pubKeyX = Array.from(params.senderPublicKey.x);
|
|
227
|
+
pubKeyY = Array.from(params.senderPublicKey.y);
|
|
228
|
+
} else {
|
|
229
|
+
// Derive from secret
|
|
230
|
+
const { secp256k1 } = await import('@noble/curves/secp256k1');
|
|
231
|
+
const uncompressedPubKey = secp256k1.getPublicKey(params.senderSecret, false);
|
|
232
|
+
pubKeyX = Array.from(uncompressedPubKey.slice(1, 33));
|
|
233
|
+
pubKeyY = Array.from(uncompressedPubKey.slice(33, 65));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const witnessInputs = {
|
|
237
|
+
intent_hash: intentHashField,
|
|
238
|
+
sender_commitment_x: commitmentX,
|
|
239
|
+
sender_commitment_y: commitmentY,
|
|
240
|
+
nullifier: nullifier,
|
|
241
|
+
timestamp: params.timestamp.toString(),
|
|
242
|
+
expiry: params.expiry.toString(),
|
|
243
|
+
sender_address: senderAddressField,
|
|
244
|
+
sender_blinding: senderBlindingField,
|
|
245
|
+
sender_secret: senderSecretField,
|
|
246
|
+
pub_key_x: pubKeyX,
|
|
247
|
+
pub_key_y: pubKeyY,
|
|
248
|
+
signature: signature,
|
|
249
|
+
message_hash: messageHash,
|
|
250
|
+
nonce: nonceField,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
sendProgress(id, 'witness', 40, 'Executing validity circuit...');
|
|
254
|
+
const { witness } = await validityNoir.execute(witnessInputs);
|
|
255
|
+
|
|
256
|
+
sendProgress(id, 'proving', 60, 'Generating validity proof...');
|
|
257
|
+
const proofData = await validityBackend.generateProof(witness);
|
|
258
|
+
|
|
259
|
+
sendProgress(id, 'complete', 100, 'Validity proof generated');
|
|
260
|
+
|
|
261
|
+
const publicInputs = [
|
|
262
|
+
'0x' + intentHashField,
|
|
263
|
+
'0x' + commitmentX,
|
|
264
|
+
'0x' + commitmentY,
|
|
265
|
+
'0x' + nullifier,
|
|
266
|
+
'0x' + params.timestamp.toString(16).padStart(16, '0'),
|
|
267
|
+
'0x' + params.expiry.toString(16).padStart(16, '0'),
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
const proof = {
|
|
271
|
+
type: 'validity',
|
|
272
|
+
proof: '0x' + bytesToHex(proofData.proof),
|
|
273
|
+
publicInputs,
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
sendSuccess(id, { proof, publicInputs });
|
|
277
|
+
} catch (error) {
|
|
278
|
+
sendError(id, error);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Generate fulfillment proof
|
|
283
|
+
async function generateFulfillmentProof(id, params) {
|
|
284
|
+
if (!isReady) {
|
|
285
|
+
sendError(id, new Error('Worker not initialized'));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
sendProgress(id, 'witness', 20, 'Preparing fulfillment witness...');
|
|
291
|
+
|
|
292
|
+
// Import noble crypto for hashing
|
|
293
|
+
const { sha256 } = await import('@noble/hashes/sha256');
|
|
294
|
+
|
|
295
|
+
const intentHashField = hexToField(params.intentHash);
|
|
296
|
+
const recipientStealthField = hexToField(params.recipientStealth);
|
|
297
|
+
|
|
298
|
+
// Compute output commitment
|
|
299
|
+
const amountBytes = bigintToBytes(params.outputAmount, 8);
|
|
300
|
+
const blindingBytes = params.outputBlinding.slice(0, 32);
|
|
301
|
+
const outputPreimage = new Uint8Array([...amountBytes, ...blindingBytes]);
|
|
302
|
+
const outputHash = sha256(outputPreimage);
|
|
303
|
+
const commitmentX = bytesToHex(outputHash.slice(0, 16)).padStart(64, '0');
|
|
304
|
+
const commitmentY = bytesToHex(outputHash.slice(16, 32)).padStart(64, '0');
|
|
305
|
+
|
|
306
|
+
const solverSecretField = bytesToField(params.solverSecret);
|
|
307
|
+
|
|
308
|
+
// Compute solver ID
|
|
309
|
+
const solverSecretBytes = hexToBytes(solverSecretField.padStart(64, '0'));
|
|
310
|
+
const solverIdHash = sha256(solverSecretBytes);
|
|
311
|
+
const solverId = bytesToHex(solverIdHash);
|
|
312
|
+
|
|
313
|
+
const outputBlindingField = bytesToField(params.outputBlinding);
|
|
314
|
+
|
|
315
|
+
const attestation = params.oracleAttestation;
|
|
316
|
+
const attestationRecipientField = hexToField(attestation.recipient);
|
|
317
|
+
const attestationTxHashField = hexToField(attestation.txHash);
|
|
318
|
+
const oracleSignature = Array.from(attestation.signature);
|
|
319
|
+
|
|
320
|
+
// Compute oracle message hash
|
|
321
|
+
const recipientBytes = hexToBytes(attestationRecipientField);
|
|
322
|
+
const attestationAmountBytes = bigintToBytes(attestation.amount, 8);
|
|
323
|
+
const txHashBytes = hexToBytes(attestationTxHashField);
|
|
324
|
+
const blockBytes = bigintToBytes(attestation.blockNumber, 8);
|
|
325
|
+
const oraclePreimage = new Uint8Array([
|
|
326
|
+
...recipientBytes,
|
|
327
|
+
...attestationAmountBytes,
|
|
328
|
+
...txHashBytes,
|
|
329
|
+
...blockBytes,
|
|
330
|
+
]);
|
|
331
|
+
const oracleMessageHash = Array.from(sha256(oraclePreimage));
|
|
332
|
+
|
|
333
|
+
const oraclePubKeyX = config.oraclePublicKey?.x ?? new Array(32).fill(0);
|
|
334
|
+
const oraclePubKeyY = config.oraclePublicKey?.y ?? new Array(32).fill(0);
|
|
335
|
+
|
|
336
|
+
const witnessInputs = {
|
|
337
|
+
intent_hash: intentHashField,
|
|
338
|
+
output_commitment_x: commitmentX,
|
|
339
|
+
output_commitment_y: commitmentY,
|
|
340
|
+
recipient_stealth: recipientStealthField,
|
|
341
|
+
min_output_amount: params.minOutputAmount.toString(),
|
|
342
|
+
solver_id: solverId,
|
|
343
|
+
fulfillment_time: params.fulfillmentTime.toString(),
|
|
344
|
+
expiry: params.expiry.toString(),
|
|
345
|
+
output_amount: params.outputAmount.toString(),
|
|
346
|
+
output_blinding: outputBlindingField,
|
|
347
|
+
solver_secret: solverSecretField,
|
|
348
|
+
attestation_recipient: attestationRecipientField,
|
|
349
|
+
attestation_amount: attestation.amount.toString(),
|
|
350
|
+
attestation_tx_hash: attestationTxHashField,
|
|
351
|
+
attestation_block: attestation.blockNumber.toString(),
|
|
352
|
+
oracle_signature: oracleSignature,
|
|
353
|
+
oracle_message_hash: oracleMessageHash,
|
|
354
|
+
oracle_pub_key_x: oraclePubKeyX,
|
|
355
|
+
oracle_pub_key_y: oraclePubKeyY,
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
sendProgress(id, 'witness', 40, 'Executing fulfillment circuit...');
|
|
359
|
+
const { witness } = await fulfillmentNoir.execute(witnessInputs);
|
|
360
|
+
|
|
361
|
+
sendProgress(id, 'proving', 60, 'Generating fulfillment proof...');
|
|
362
|
+
const proofData = await fulfillmentBackend.generateProof(witness);
|
|
363
|
+
|
|
364
|
+
sendProgress(id, 'complete', 100, 'Fulfillment proof generated');
|
|
365
|
+
|
|
366
|
+
const publicInputs = [
|
|
367
|
+
'0x' + intentHashField,
|
|
368
|
+
'0x' + commitmentX,
|
|
369
|
+
'0x' + commitmentY,
|
|
370
|
+
'0x' + recipientStealthField,
|
|
371
|
+
'0x' + params.minOutputAmount.toString(16).padStart(16, '0'),
|
|
372
|
+
'0x' + solverId,
|
|
373
|
+
'0x' + params.fulfillmentTime.toString(16).padStart(16, '0'),
|
|
374
|
+
'0x' + params.expiry.toString(16).padStart(16, '0'),
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
const proof = {
|
|
378
|
+
type: 'fulfillment',
|
|
379
|
+
proof: '0x' + bytesToHex(proofData.proof),
|
|
380
|
+
publicInputs,
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
sendSuccess(id, { proof, publicInputs });
|
|
384
|
+
} catch (error) {
|
|
385
|
+
sendError(id, error);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
184
389
|
// Helper functions
|
|
185
390
|
function bytesToField(bytes) {
|
|
186
391
|
let result = 0n;
|
|
@@ -208,6 +413,39 @@ export function createWorkerBlobURL(): string {
|
|
|
208
413
|
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
209
414
|
}
|
|
210
415
|
|
|
416
|
+
function hexToBytes(hex) {
|
|
417
|
+
const h = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
418
|
+
const bytes = new Uint8Array(h.length / 2);
|
|
419
|
+
for (let i = 0; i < h.length; i += 2) {
|
|
420
|
+
bytes[i / 2] = parseInt(h.slice(i, i + 2), 16);
|
|
421
|
+
}
|
|
422
|
+
return bytes;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function hexToField(hex) {
|
|
426
|
+
const h = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
427
|
+
return h.padStart(64, '0');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function fieldToBytes32(field) {
|
|
431
|
+
const hex = field.padStart(64, '0');
|
|
432
|
+
const bytes = [];
|
|
433
|
+
for (let i = 0; i < 32; i++) {
|
|
434
|
+
bytes.push(parseInt(hex.slice(i * 2, i * 2 + 2), 16));
|
|
435
|
+
}
|
|
436
|
+
return bytes;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function bigintToBytes(value, length) {
|
|
440
|
+
const bytes = new Uint8Array(length);
|
|
441
|
+
let v = value;
|
|
442
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
443
|
+
bytes[i] = Number(v & 0xffn);
|
|
444
|
+
v = v >> 8n;
|
|
445
|
+
}
|
|
446
|
+
return bytes;
|
|
447
|
+
}
|
|
448
|
+
|
|
211
449
|
// Message handler
|
|
212
450
|
self.onmessage = async function(event) {
|
|
213
451
|
const { id, type, params, config: initConfig } = event.data;
|
|
@@ -220,12 +458,10 @@ export function createWorkerBlobURL(): string {
|
|
|
220
458
|
await generateFundingProof(id, params);
|
|
221
459
|
break;
|
|
222
460
|
case 'generateValidityProof':
|
|
223
|
-
|
|
224
|
-
sendError(id, new Error('Validity proof not yet implemented in worker'));
|
|
461
|
+
await generateValidityProof(id, params);
|
|
225
462
|
break;
|
|
226
463
|
case 'generateFulfillmentProof':
|
|
227
|
-
|
|
228
|
-
sendError(id, new Error('Fulfillment proof not yet implemented in worker'));
|
|
464
|
+
await generateFulfillmentProof(id, params);
|
|
229
465
|
break;
|
|
230
466
|
case 'destroy':
|
|
231
467
|
// Cleanup
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# Settlement Backend Interface
|
|
2
|
+
|
|
3
|
+
This directory contains the settlement abstraction layer for SIP Protocol, enabling pluggable settlement backends for cross-chain swaps.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `SettlementBackend` interface provides a unified API for executing cross-chain swaps through different settlement providers (NEAR Intents, Zcash, THORChain, direct on-chain execution, etc.).
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────────────────────────────────────────────┐
|
|
13
|
+
│ SIP SDK (Application Layer) │
|
|
14
|
+
│ • createIntent() │
|
|
15
|
+
│ • getQuotes() │
|
|
16
|
+
│ • execute() │
|
|
17
|
+
└────────────────────┬────────────────────────────────────┘
|
|
18
|
+
│
|
|
19
|
+
▼
|
|
20
|
+
┌─────────────────────────────────────────────────────────┐
|
|
21
|
+
│ Settlement Backend Interface (Abstraction Layer) │
|
|
22
|
+
│ • getQuote() │
|
|
23
|
+
│ • executeSwap() │
|
|
24
|
+
│ • getStatus() │
|
|
25
|
+
└────────────────────┬────────────────────────────────────┘
|
|
26
|
+
│
|
|
27
|
+
┌───────────┼───────────┬───────────────┐
|
|
28
|
+
▼ ▼ ▼ ▼
|
|
29
|
+
┌─────────────┐ ┌─────────┐ ┌──────────┐ ┌──────────────┐
|
|
30
|
+
│ NEAR │ │ Zcash │ │THORChain │ │ Direct Chain │
|
|
31
|
+
│ Intents │ │ Backend │ │ Backend │ │ Backend │
|
|
32
|
+
└─────────────┘ └─────────┘ └──────────┘ └──────────────┘
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Core Types
|
|
36
|
+
|
|
37
|
+
### SettlementBackend
|
|
38
|
+
|
|
39
|
+
The main interface that all backends must implement:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
interface SettlementBackend {
|
|
43
|
+
readonly name: SettlementBackendName
|
|
44
|
+
readonly capabilities: BackendCapabilities
|
|
45
|
+
|
|
46
|
+
getQuote(params: QuoteParams): Promise<Quote>
|
|
47
|
+
executeSwap(params: SwapParams): Promise<SwapResult>
|
|
48
|
+
getStatus(swapId: string): Promise<SwapStatusResponse>
|
|
49
|
+
|
|
50
|
+
// Optional methods
|
|
51
|
+
cancel?(swapId: string): Promise<void>
|
|
52
|
+
waitForCompletion?(swapId: string, options?): Promise<SwapStatusResponse>
|
|
53
|
+
getDryQuote?(params: QuoteParams): Promise<Quote>
|
|
54
|
+
notifyDeposit?(swapId: string, txHash: string, metadata?): Promise<void>
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### QuoteParams
|
|
59
|
+
|
|
60
|
+
Parameters for requesting a swap quote:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
interface QuoteParams {
|
|
64
|
+
fromChain: ChainId
|
|
65
|
+
toChain: ChainId
|
|
66
|
+
fromToken: string
|
|
67
|
+
toToken: string
|
|
68
|
+
amount: bigint
|
|
69
|
+
privacyLevel: PrivacyLevel
|
|
70
|
+
recipientMetaAddress?: StealthMetaAddress | string
|
|
71
|
+
senderAddress?: string
|
|
72
|
+
slippageTolerance?: number
|
|
73
|
+
deadline?: number
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Quote
|
|
78
|
+
|
|
79
|
+
Quote response with pricing and execution details:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
interface Quote {
|
|
83
|
+
quoteId: string
|
|
84
|
+
amountIn: string
|
|
85
|
+
amountOut: string
|
|
86
|
+
minAmountOut: string
|
|
87
|
+
fees: {
|
|
88
|
+
networkFee: string
|
|
89
|
+
protocolFee: string
|
|
90
|
+
totalFeeUSD?: string
|
|
91
|
+
}
|
|
92
|
+
depositAddress: string
|
|
93
|
+
recipientAddress: string
|
|
94
|
+
expiresAt: number
|
|
95
|
+
// ... optional fields
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### SwapStatus
|
|
100
|
+
|
|
101
|
+
Enum tracking swap lifecycle:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
enum SwapStatus {
|
|
105
|
+
PENDING_DEPOSIT = 'pending_deposit',
|
|
106
|
+
DEPOSIT_CONFIRMED = 'deposit_confirmed',
|
|
107
|
+
IN_PROGRESS = 'in_progress',
|
|
108
|
+
SUCCESS = 'success',
|
|
109
|
+
FAILED = 'failed',
|
|
110
|
+
CANCELLED = 'cancelled',
|
|
111
|
+
REFUNDING = 'refunding',
|
|
112
|
+
REFUNDED = 'refunded',
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Implementing a Backend
|
|
117
|
+
|
|
118
|
+
### 1. Basic Implementation
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import type {
|
|
122
|
+
SettlementBackend,
|
|
123
|
+
QuoteParams,
|
|
124
|
+
Quote,
|
|
125
|
+
SwapParams,
|
|
126
|
+
SwapResult,
|
|
127
|
+
SwapStatusResponse,
|
|
128
|
+
BackendCapabilities,
|
|
129
|
+
} from '@sip-protocol/sdk'
|
|
130
|
+
|
|
131
|
+
export class MySettlementBackend implements SettlementBackend {
|
|
132
|
+
readonly name = 'my-backend'
|
|
133
|
+
readonly capabilities: BackendCapabilities = {
|
|
134
|
+
supportedSourceChains: ['ethereum', 'solana'],
|
|
135
|
+
supportedDestinationChains: ['near', 'polygon'],
|
|
136
|
+
supportedPrivacyLevels: [PrivacyLevel.TRANSPARENT, PrivacyLevel.SHIELDED],
|
|
137
|
+
supportsCancellation: false,
|
|
138
|
+
supportsRefunds: true,
|
|
139
|
+
averageExecutionTime: 120, // seconds
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
constructor(private config: MyBackendConfig) {}
|
|
143
|
+
|
|
144
|
+
async getQuote(params: QuoteParams): Promise<Quote> {
|
|
145
|
+
// 1. Validate parameters
|
|
146
|
+
this.validateQuoteParams(params)
|
|
147
|
+
|
|
148
|
+
// 2. Call backend API for quote
|
|
149
|
+
const backendQuote = await this.fetchQuote(params)
|
|
150
|
+
|
|
151
|
+
// 3. Convert to standard Quote format
|
|
152
|
+
return {
|
|
153
|
+
quoteId: backendQuote.id,
|
|
154
|
+
amountIn: params.amount.toString(),
|
|
155
|
+
amountOut: backendQuote.outputAmount,
|
|
156
|
+
minAmountOut: this.calculateMinOutput(
|
|
157
|
+
backendQuote.outputAmount,
|
|
158
|
+
params.slippageTolerance
|
|
159
|
+
),
|
|
160
|
+
fees: {
|
|
161
|
+
networkFee: backendQuote.networkFee,
|
|
162
|
+
protocolFee: backendQuote.protocolFee,
|
|
163
|
+
},
|
|
164
|
+
depositAddress: backendQuote.depositAddress,
|
|
165
|
+
recipientAddress: backendQuote.recipient,
|
|
166
|
+
expiresAt: Date.now() + 300000, // 5 minutes
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async executeSwap(params: SwapParams): Promise<SwapResult> {
|
|
171
|
+
// 1. Retrieve quote
|
|
172
|
+
const quote = await this.getQuoteById(params.quoteId)
|
|
173
|
+
|
|
174
|
+
// 2. Execute swap (may involve signing, approvals, etc.)
|
|
175
|
+
const execution = await this.executeBackendSwap(params)
|
|
176
|
+
|
|
177
|
+
// 3. Return result
|
|
178
|
+
return {
|
|
179
|
+
swapId: execution.id,
|
|
180
|
+
status: SwapStatus.PENDING_DEPOSIT,
|
|
181
|
+
quoteId: params.quoteId,
|
|
182
|
+
depositAddress: execution.depositAddress,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async getStatus(swapId: string): Promise<SwapStatusResponse> {
|
|
187
|
+
// Query backend for current status
|
|
188
|
+
const backendStatus = await this.fetchStatus(swapId)
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
swapId,
|
|
192
|
+
status: this.mapStatus(backendStatus.status),
|
|
193
|
+
quoteId: backendStatus.quoteId,
|
|
194
|
+
depositAddress: backendStatus.depositAddress,
|
|
195
|
+
amountIn: backendStatus.amountIn,
|
|
196
|
+
amountOut: backendStatus.amountOut,
|
|
197
|
+
depositTxHash: backendStatus.depositTx,
|
|
198
|
+
settlementTxHash: backendStatus.settlementTx,
|
|
199
|
+
updatedAt: backendStatus.lastUpdated,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Helper methods
|
|
204
|
+
private validateQuoteParams(params: QuoteParams): void {
|
|
205
|
+
if (!this.capabilities.supportedSourceChains.includes(params.fromChain)) {
|
|
206
|
+
throw new ValidationError(`Chain ${params.fromChain} not supported`)
|
|
207
|
+
}
|
|
208
|
+
// ... more validation
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private mapStatus(backendStatus: string): SwapStatus {
|
|
212
|
+
// Map backend-specific status to SwapStatus enum
|
|
213
|
+
const statusMap = {
|
|
214
|
+
'waiting': SwapStatus.PENDING_DEPOSIT,
|
|
215
|
+
'processing': SwapStatus.IN_PROGRESS,
|
|
216
|
+
'completed': SwapStatus.SUCCESS,
|
|
217
|
+
'failed': SwapStatus.FAILED,
|
|
218
|
+
}
|
|
219
|
+
return statusMap[backendStatus] ?? SwapStatus.FAILED
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### 2. Optional Methods
|
|
225
|
+
|
|
226
|
+
Implement optional methods for enhanced functionality:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
export class MySettlementBackend implements SettlementBackend {
|
|
230
|
+
// ... required methods ...
|
|
231
|
+
|
|
232
|
+
async cancel(swapId: string): Promise<void> {
|
|
233
|
+
if (!this.capabilities.supportsCancellation) {
|
|
234
|
+
throw new ProofError('Cancellation not supported')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const status = await this.getStatus(swapId)
|
|
238
|
+
if (status.status !== SwapStatus.PENDING_DEPOSIT) {
|
|
239
|
+
throw new ValidationError('Can only cancel pending swaps')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
await this.backendCancelSwap(swapId)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async waitForCompletion(
|
|
246
|
+
swapId: string,
|
|
247
|
+
options?: { interval?: number; timeout?: number; onStatusChange?: (status: SwapStatusResponse) => void }
|
|
248
|
+
): Promise<SwapStatusResponse> {
|
|
249
|
+
const interval = options?.interval ?? 5000
|
|
250
|
+
const timeout = options?.timeout ?? 600000
|
|
251
|
+
const startTime = Date.now()
|
|
252
|
+
|
|
253
|
+
while (Date.now() - startTime < timeout) {
|
|
254
|
+
const status = await this.getStatus(swapId)
|
|
255
|
+
options?.onStatusChange?.(status)
|
|
256
|
+
|
|
257
|
+
if (
|
|
258
|
+
status.status === SwapStatus.SUCCESS ||
|
|
259
|
+
status.status === SwapStatus.FAILED ||
|
|
260
|
+
status.status === SwapStatus.REFUNDED
|
|
261
|
+
) {
|
|
262
|
+
return status
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await new Promise(resolve => setTimeout(resolve, interval))
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
throw new NetworkError('Swap timeout')
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async getDryQuote(params: QuoteParams): Promise<Quote> {
|
|
272
|
+
// Return quote without creating deposit address
|
|
273
|
+
return this.getQuote({ ...params, dry: true })
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async notifyDeposit(
|
|
277
|
+
swapId: string,
|
|
278
|
+
txHash: string,
|
|
279
|
+
metadata?: Record<string, unknown>
|
|
280
|
+
): Promise<void> {
|
|
281
|
+
await this.backendNotifyDeposit(swapId, txHash, metadata)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 3. Factory Function
|
|
287
|
+
|
|
288
|
+
Provide a factory function for easy instantiation:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
export function createMyBackend(config: MyBackendConfig): SettlementBackend {
|
|
292
|
+
return new MySettlementBackend(config)
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### 4. Registry Entry
|
|
297
|
+
|
|
298
|
+
Create a registry entry for discoverability:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
export const myBackendRegistry: SettlementBackendRegistry = {
|
|
302
|
+
name: 'my-backend',
|
|
303
|
+
factory: createMyBackend,
|
|
304
|
+
displayName: 'My Settlement Backend',
|
|
305
|
+
description: 'Fast and reliable cross-chain swaps',
|
|
306
|
+
homepage: 'https://mybackend.com',
|
|
307
|
+
docs: 'https://docs.mybackend.com',
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Privacy Support
|
|
312
|
+
|
|
313
|
+
Backends should handle privacy levels appropriately:
|
|
314
|
+
|
|
315
|
+
### Transparent Mode
|
|
316
|
+
|
|
317
|
+
No privacy features required. Use sender's address directly.
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
if (params.privacyLevel === PrivacyLevel.TRANSPARENT) {
|
|
321
|
+
// Use senderAddress as both sender and recipient
|
|
322
|
+
recipientAddress = params.senderAddress
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Shielded Mode
|
|
327
|
+
|
|
328
|
+
Generate stealth address for recipient:
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
if (params.privacyLevel === PrivacyLevel.SHIELDED) {
|
|
332
|
+
// Generate stealth address from recipientMetaAddress
|
|
333
|
+
const { stealthAddress, ephemeralPublicKey } = generateStealthAddress(
|
|
334
|
+
params.recipientMetaAddress
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
recipientAddress = stealthAddress
|
|
338
|
+
// Store ephemeralPublicKey in metadata for recipient recovery
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Compliant Mode
|
|
343
|
+
|
|
344
|
+
Same as shielded, but include viewing key hash for auditors:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
if (params.privacyLevel === PrivacyLevel.COMPLIANT) {
|
|
348
|
+
// Generate stealth address + viewing key hash
|
|
349
|
+
const { stealthAddress, viewingKeyHash } = generateCompliantAddress(
|
|
350
|
+
params.recipientMetaAddress,
|
|
351
|
+
params.viewingKey
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
recipientAddress = stealthAddress
|
|
355
|
+
// Include viewingKeyHash in metadata for auditors
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Error Handling
|
|
360
|
+
|
|
361
|
+
Use standard SIP errors:
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
import { ValidationError, NetworkError, ProofError } from '@sip-protocol/sdk'
|
|
365
|
+
|
|
366
|
+
// Invalid parameters
|
|
367
|
+
throw new ValidationError('Invalid amount', 'amount', { min: 0, received: -1 })
|
|
368
|
+
|
|
369
|
+
// API/network issues
|
|
370
|
+
throw new NetworkError('Backend API unavailable', { status: 503 })
|
|
371
|
+
|
|
372
|
+
// Unsupported features
|
|
373
|
+
throw new ProofError('Feature not supported by this backend')
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Testing
|
|
377
|
+
|
|
378
|
+
Write comprehensive tests for your backend:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import { describe, it, expect } from 'vitest'
|
|
382
|
+
import { PrivacyLevel } from '@sip-protocol/types'
|
|
383
|
+
import { MySettlementBackend } from './my-backend'
|
|
384
|
+
|
|
385
|
+
describe('MySettlementBackend', () => {
|
|
386
|
+
it('should implement SettlementBackend interface', () => {
|
|
387
|
+
const backend = new MySettlementBackend(config)
|
|
388
|
+
|
|
389
|
+
expect(backend.name).toBe('my-backend')
|
|
390
|
+
expect(backend.capabilities).toBeDefined()
|
|
391
|
+
expect(typeof backend.getQuote).toBe('function')
|
|
392
|
+
expect(typeof backend.executeSwap).toBe('function')
|
|
393
|
+
expect(typeof backend.getStatus).toBe('function')
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
it('should get quote for valid params', async () => {
|
|
397
|
+
const backend = new MySettlementBackend(config)
|
|
398
|
+
|
|
399
|
+
const quote = await backend.getQuote({
|
|
400
|
+
fromChain: 'ethereum',
|
|
401
|
+
toChain: 'near',
|
|
402
|
+
fromToken: 'USDC',
|
|
403
|
+
toToken: 'NEAR',
|
|
404
|
+
amount: 1000000n,
|
|
405
|
+
privacyLevel: PrivacyLevel.TRANSPARENT,
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
expect(quote.quoteId).toBeDefined()
|
|
409
|
+
expect(quote.depositAddress).toBeDefined()
|
|
410
|
+
expect(BigInt(quote.amountOut)).toBeGreaterThan(0n)
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
// ... more tests
|
|
414
|
+
})
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Examples
|
|
418
|
+
|
|
419
|
+
See existing implementations:
|
|
420
|
+
|
|
421
|
+
- `packages/sdk/src/adapters/near-intents.ts` - NEAR Intents adapter (reference implementation)
|
|
422
|
+
- `packages/sdk/src/zcash/swap-service.ts` - Zcash swap service (privacy-focused)
|
|
423
|
+
|
|
424
|
+
## Roadmap
|
|
425
|
+
|
|
426
|
+
Planned backends for M11-M12 milestones:
|
|
427
|
+
|
|
428
|
+
- [x] NEAR Intents (M1-M8)
|
|
429
|
+
- [ ] Zcash Settlement Backend (M9)
|
|
430
|
+
- [ ] THORChain Backend (M10)
|
|
431
|
+
- [ ] Direct Chain Backend (M11)
|
|
432
|
+
- [ ] Mina Protocol Backend (M12)
|
|
433
|
+
|
|
434
|
+
## Resources
|
|
435
|
+
|
|
436
|
+
- [SIP Protocol Documentation](https://docs.sip-protocol.org)
|
|
437
|
+
- [NEAR Intents API](https://1click.chaindefuser.com)
|
|
438
|
+
- [THORChain Docs](https://docs.thorchain.org)
|
|
439
|
+
- [Zcash Shielded Transactions](https://z.cash/technology/zksnarks/)
|