@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
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct Chain Settlement Backend
|
|
3
|
+
*
|
|
4
|
+
* Handles same-chain transfers with privacy via stealth addresses.
|
|
5
|
+
* Only works when fromChain === toChain (no cross-chain).
|
|
6
|
+
*
|
|
7
|
+
* Use cases:
|
|
8
|
+
* - ETH→ETH (same chain private transfer)
|
|
9
|
+
* - SOL→SOL
|
|
10
|
+
* - NEAR→NEAR
|
|
11
|
+
* - Any single-chain transfer with privacy
|
|
12
|
+
*
|
|
13
|
+
* @module settlement/backends/direct-chain
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type {
|
|
17
|
+
ChainId,
|
|
18
|
+
HexString,
|
|
19
|
+
StealthMetaAddress,
|
|
20
|
+
WalletAdapter,
|
|
21
|
+
} from '@sip-protocol/types'
|
|
22
|
+
import { PrivacyLevel } from '@sip-protocol/types'
|
|
23
|
+
import {
|
|
24
|
+
type SettlementBackend,
|
|
25
|
+
type QuoteParams,
|
|
26
|
+
type Quote,
|
|
27
|
+
type SwapParams,
|
|
28
|
+
type SwapResult,
|
|
29
|
+
type SwapStatusResponse,
|
|
30
|
+
type BackendCapabilities,
|
|
31
|
+
SwapStatus,
|
|
32
|
+
} from '../interface'
|
|
33
|
+
import {
|
|
34
|
+
generateStealthAddress,
|
|
35
|
+
generateEd25519StealthAddress,
|
|
36
|
+
decodeStealthMetaAddress,
|
|
37
|
+
publicKeyToEthAddress,
|
|
38
|
+
ed25519PublicKeyToSolanaAddress,
|
|
39
|
+
ed25519PublicKeyToNearAddress,
|
|
40
|
+
isEd25519Chain,
|
|
41
|
+
} from '../../stealth'
|
|
42
|
+
import { ValidationError } from '../../errors'
|
|
43
|
+
import { randomBytes, bytesToHex } from '@noble/hashes/utils'
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Configuration for DirectChainBackend
|
|
47
|
+
*/
|
|
48
|
+
export interface DirectChainBackendConfig {
|
|
49
|
+
/**
|
|
50
|
+
* Wallet adapter for signing and sending transactions
|
|
51
|
+
* If not provided, backend will only generate quotes
|
|
52
|
+
*/
|
|
53
|
+
walletAdapter?: WalletAdapter
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Estimated gas fees per chain (in native token smallest units)
|
|
57
|
+
* Used for quote generation
|
|
58
|
+
*/
|
|
59
|
+
gasFees?: Partial<Record<ChainId, bigint>>
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Protocol fee in basis points (100 = 1%)
|
|
63
|
+
* Default: 0 (no protocol fee for direct transfers)
|
|
64
|
+
*/
|
|
65
|
+
protocolFeeBps?: number
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Swap tracking data
|
|
70
|
+
*/
|
|
71
|
+
interface SwapData {
|
|
72
|
+
quoteId: string
|
|
73
|
+
swapId: string
|
|
74
|
+
fromChain: ChainId
|
|
75
|
+
toChain: ChainId
|
|
76
|
+
fromToken: string
|
|
77
|
+
toToken: string
|
|
78
|
+
amountIn: string
|
|
79
|
+
amountOut: string
|
|
80
|
+
minAmountOut: string
|
|
81
|
+
recipientAddress: string
|
|
82
|
+
depositAddress: string
|
|
83
|
+
refundAddress?: string
|
|
84
|
+
privacyLevel: PrivacyLevel
|
|
85
|
+
stealthAddress?: {
|
|
86
|
+
address: HexString
|
|
87
|
+
ephemeralPublicKey: HexString
|
|
88
|
+
viewTag: number
|
|
89
|
+
}
|
|
90
|
+
status: SwapStatus
|
|
91
|
+
depositTxHash?: string
|
|
92
|
+
settlementTxHash?: string
|
|
93
|
+
errorMessage?: string
|
|
94
|
+
createdAt: number
|
|
95
|
+
updatedAt: number
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Default gas fees for supported chains (in smallest units)
|
|
100
|
+
*/
|
|
101
|
+
const DEFAULT_GAS_FEES: Record<ChainId, bigint> = {
|
|
102
|
+
ethereum: 21000n * 50n * 1000000000n, // 21k gas * 50 gwei = 0.00105 ETH
|
|
103
|
+
solana: 5000n, // 5000 lamports = 0.000005 SOL
|
|
104
|
+
near: 300000000000000000000n, // 0.3 NEAR in yoctoNEAR
|
|
105
|
+
zcash: 10000n, // 0.0001 ZEC in zatoshi
|
|
106
|
+
polygon: 21000n * 30n * 1000000000n, // 21k gas * 30 gwei POL
|
|
107
|
+
arbitrum: 2100000000n, // 21k gas * 0.1 gwei ETH = 2100 gwei = 2.1e9 wei
|
|
108
|
+
optimism: 21000000n, // 21k gas * 0.001 gwei ETH = 21 gwei = 21e6 wei
|
|
109
|
+
base: 2100000000n, // 21k gas * 0.1 gwei ETH = 2100 gwei = 2.1e9 wei
|
|
110
|
+
bitcoin: 10000n, // 10000 sats = 0.0001 BTC (estimate for 1 input, 2 outputs)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Direct Chain Settlement Backend
|
|
115
|
+
*
|
|
116
|
+
* Implements single-chain transfers with privacy via stealth addresses.
|
|
117
|
+
* Only supports same-chain transfers (fromChain === toChain).
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```typescript
|
|
121
|
+
* const backend = new DirectChainBackend({
|
|
122
|
+
* walletAdapter: myEthereumWallet,
|
|
123
|
+
* })
|
|
124
|
+
*
|
|
125
|
+
* // Get quote for ETH→ETH transfer
|
|
126
|
+
* const quote = await backend.getQuote({
|
|
127
|
+
* fromChain: 'ethereum',
|
|
128
|
+
* toChain: 'ethereum',
|
|
129
|
+
* fromToken: 'ETH',
|
|
130
|
+
* toToken: 'ETH',
|
|
131
|
+
* amount: 1000000000000000000n, // 1 ETH
|
|
132
|
+
* privacyLevel: 'shielded',
|
|
133
|
+
* recipientMetaAddress: 'sip:ethereum:0x...:0x...',
|
|
134
|
+
* })
|
|
135
|
+
*
|
|
136
|
+
* // Execute swap
|
|
137
|
+
* const result = await backend.executeSwap({
|
|
138
|
+
* quoteId: quote.quoteId,
|
|
139
|
+
* })
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export class DirectChainBackend implements SettlementBackend {
|
|
143
|
+
readonly name = 'direct-chain' as const
|
|
144
|
+
|
|
145
|
+
readonly capabilities: BackendCapabilities = {
|
|
146
|
+
supportedSourceChains: [
|
|
147
|
+
'ethereum',
|
|
148
|
+
'solana',
|
|
149
|
+
'near',
|
|
150
|
+
'zcash',
|
|
151
|
+
'polygon',
|
|
152
|
+
'arbitrum',
|
|
153
|
+
'optimism',
|
|
154
|
+
'base',
|
|
155
|
+
'bitcoin',
|
|
156
|
+
],
|
|
157
|
+
supportedDestinationChains: [
|
|
158
|
+
'ethereum',
|
|
159
|
+
'solana',
|
|
160
|
+
'near',
|
|
161
|
+
'zcash',
|
|
162
|
+
'polygon',
|
|
163
|
+
'arbitrum',
|
|
164
|
+
'optimism',
|
|
165
|
+
'base',
|
|
166
|
+
'bitcoin',
|
|
167
|
+
],
|
|
168
|
+
supportedPrivacyLevels: [
|
|
169
|
+
PrivacyLevel.TRANSPARENT,
|
|
170
|
+
PrivacyLevel.SHIELDED,
|
|
171
|
+
PrivacyLevel.COMPLIANT,
|
|
172
|
+
],
|
|
173
|
+
supportsCancellation: false,
|
|
174
|
+
supportsRefunds: true,
|
|
175
|
+
averageExecutionTime: 30, // 30 seconds for same-chain transfers
|
|
176
|
+
features: [
|
|
177
|
+
'same-chain-only',
|
|
178
|
+
'stealth-addresses',
|
|
179
|
+
'minimal-fees',
|
|
180
|
+
'instant-settlement',
|
|
181
|
+
],
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private walletAdapter?: WalletAdapter
|
|
185
|
+
private gasFees: Record<ChainId, bigint>
|
|
186
|
+
private protocolFeeBps: number
|
|
187
|
+
private swaps: Map<string, SwapData> = new Map()
|
|
188
|
+
|
|
189
|
+
constructor(config: DirectChainBackendConfig = {}) {
|
|
190
|
+
this.walletAdapter = config.walletAdapter
|
|
191
|
+
this.gasFees = { ...DEFAULT_GAS_FEES, ...config.gasFees }
|
|
192
|
+
this.protocolFeeBps = config.protocolFeeBps ?? 0
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get a quote for a same-chain transfer
|
|
197
|
+
*
|
|
198
|
+
* @throws {ValidationError} If chains don't match or parameters are invalid
|
|
199
|
+
*/
|
|
200
|
+
async getQuote(params: QuoteParams): Promise<Quote> {
|
|
201
|
+
// Validate same-chain requirement
|
|
202
|
+
if (params.fromChain !== params.toChain) {
|
|
203
|
+
throw new ValidationError(
|
|
204
|
+
`DirectChainBackend only supports same-chain transfers. Got fromChain=${params.fromChain}, toChain=${params.toChain}. ` +
|
|
205
|
+
`For cross-chain swaps, use NEARIntentsBackend or another cross-chain settlement backend.`,
|
|
206
|
+
'toChain',
|
|
207
|
+
{
|
|
208
|
+
fromChain: params.fromChain,
|
|
209
|
+
toChain: params.toChain,
|
|
210
|
+
hint: 'Use NEARIntentsBackend for cross-chain swaps',
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Validate chains are supported
|
|
216
|
+
if (!this.capabilities.supportedSourceChains.includes(params.fromChain)) {
|
|
217
|
+
throw new ValidationError(
|
|
218
|
+
`Chain ${params.fromChain} is not supported by DirectChainBackend`,
|
|
219
|
+
'fromChain',
|
|
220
|
+
{ chain: params.fromChain }
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Validate privacy level is supported
|
|
225
|
+
if (!this.capabilities.supportedPrivacyLevels.includes(params.privacyLevel)) {
|
|
226
|
+
throw new ValidationError(
|
|
227
|
+
`Privacy level ${params.privacyLevel} is not supported`,
|
|
228
|
+
'privacyLevel',
|
|
229
|
+
{ privacyLevel: params.privacyLevel }
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// For same-chain transfer, amount out = amount in (minus fees)
|
|
234
|
+
const chain = params.fromChain
|
|
235
|
+
const gasFee = this.gasFees[chain]
|
|
236
|
+
const protocolFee = (params.amount * BigInt(this.protocolFeeBps)) / 10000n
|
|
237
|
+
const totalFees = gasFee + protocolFee
|
|
238
|
+
const amountOut = params.amount - totalFees
|
|
239
|
+
|
|
240
|
+
if (amountOut <= 0n) {
|
|
241
|
+
throw new ValidationError(
|
|
242
|
+
`Amount too small to cover fees. Amount: ${params.amount}, fees: ${totalFees}`,
|
|
243
|
+
'amount',
|
|
244
|
+
{ amount: params.amount.toString(), fees: totalFees.toString() }
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Generate stealth address for privacy modes
|
|
249
|
+
let recipientAddress: string
|
|
250
|
+
let stealthData: SwapData['stealthAddress']
|
|
251
|
+
|
|
252
|
+
if (params.privacyLevel === PrivacyLevel.TRANSPARENT) {
|
|
253
|
+
// Transparent mode: use sender address as recipient
|
|
254
|
+
if (!params.senderAddress) {
|
|
255
|
+
throw new ValidationError(
|
|
256
|
+
'senderAddress is required for transparent mode',
|
|
257
|
+
'senderAddress'
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
recipientAddress = params.senderAddress
|
|
261
|
+
} else {
|
|
262
|
+
// Shielded/compliant mode: generate stealth address
|
|
263
|
+
if (!params.recipientMetaAddress) {
|
|
264
|
+
throw new ValidationError(
|
|
265
|
+
'recipientMetaAddress is required for shielded/compliant modes',
|
|
266
|
+
'recipientMetaAddress'
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const metaAddr = typeof params.recipientMetaAddress === 'string'
|
|
271
|
+
? decodeStealthMetaAddress(params.recipientMetaAddress)
|
|
272
|
+
: params.recipientMetaAddress
|
|
273
|
+
|
|
274
|
+
if (isEd25519Chain(chain)) {
|
|
275
|
+
// Ed25519 chains (Solana, NEAR)
|
|
276
|
+
const { stealthAddress } = generateEd25519StealthAddress(metaAddr)
|
|
277
|
+
stealthData = stealthAddress
|
|
278
|
+
|
|
279
|
+
if (chain === 'solana') {
|
|
280
|
+
recipientAddress = ed25519PublicKeyToSolanaAddress(stealthAddress.address)
|
|
281
|
+
} else if (chain === 'near') {
|
|
282
|
+
recipientAddress = ed25519PublicKeyToNearAddress(stealthAddress.address)
|
|
283
|
+
} else {
|
|
284
|
+
throw new ValidationError(
|
|
285
|
+
`Ed25519 address derivation not implemented for ${chain}`,
|
|
286
|
+
'toChain',
|
|
287
|
+
{ chain }
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
// Secp256k1 chains (EVM)
|
|
292
|
+
const { stealthAddress } = generateStealthAddress(metaAddr)
|
|
293
|
+
stealthData = stealthAddress
|
|
294
|
+
recipientAddress = publicKeyToEthAddress(stealthAddress.address)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Generate unique IDs
|
|
299
|
+
const quoteId = `quote-${bytesToHex(randomBytes(16))}`
|
|
300
|
+
const swapId = `swap-${bytesToHex(randomBytes(16))}`
|
|
301
|
+
const depositAddress = params.senderAddress ?? recipientAddress
|
|
302
|
+
|
|
303
|
+
// Calculate slippage (0 for same-chain, but respect user setting)
|
|
304
|
+
const slippageBps = params.slippageTolerance ?? 0
|
|
305
|
+
const slippageAmount = (amountOut * BigInt(slippageBps)) / 10000n
|
|
306
|
+
const minAmountOut = amountOut - slippageAmount
|
|
307
|
+
|
|
308
|
+
// Calculate expiry
|
|
309
|
+
const expiresAt = params.deadline ?? Math.floor(Date.now() / 1000) + 3600 // 1 hour default
|
|
310
|
+
|
|
311
|
+
// Store swap data
|
|
312
|
+
const swapData: SwapData = {
|
|
313
|
+
quoteId,
|
|
314
|
+
swapId,
|
|
315
|
+
fromChain: params.fromChain,
|
|
316
|
+
toChain: params.toChain,
|
|
317
|
+
fromToken: params.fromToken,
|
|
318
|
+
toToken: params.toToken,
|
|
319
|
+
amountIn: params.amount.toString(),
|
|
320
|
+
amountOut: amountOut.toString(),
|
|
321
|
+
minAmountOut: minAmountOut.toString(),
|
|
322
|
+
recipientAddress,
|
|
323
|
+
depositAddress,
|
|
324
|
+
refundAddress: params.senderAddress,
|
|
325
|
+
privacyLevel: params.privacyLevel,
|
|
326
|
+
stealthAddress: stealthData,
|
|
327
|
+
status: SwapStatus.PENDING_DEPOSIT,
|
|
328
|
+
createdAt: Date.now(),
|
|
329
|
+
updatedAt: Date.now(),
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
this.swaps.set(quoteId, swapData)
|
|
333
|
+
this.swaps.set(swapId, swapData) // Also index by swapId
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
quoteId,
|
|
337
|
+
amountIn: params.amount.toString(),
|
|
338
|
+
amountOut: amountOut.toString(),
|
|
339
|
+
minAmountOut: minAmountOut.toString(),
|
|
340
|
+
priceImpact: 0, // No price impact for same-chain
|
|
341
|
+
fees: {
|
|
342
|
+
networkFee: gasFee.toString(),
|
|
343
|
+
protocolFee: protocolFee.toString(),
|
|
344
|
+
},
|
|
345
|
+
depositAddress,
|
|
346
|
+
recipientAddress,
|
|
347
|
+
refundAddress: params.senderAddress,
|
|
348
|
+
expiresAt,
|
|
349
|
+
estimatedTime: 30, // 30 seconds
|
|
350
|
+
route: {
|
|
351
|
+
steps: [
|
|
352
|
+
{
|
|
353
|
+
protocol: 'direct-chain',
|
|
354
|
+
tokenIn: {
|
|
355
|
+
chain: params.fromChain,
|
|
356
|
+
symbol: params.fromToken,
|
|
357
|
+
address: '0x0000000000000000000000000000000000000000' as HexString, // Native token
|
|
358
|
+
decimals: 18, // Default decimals
|
|
359
|
+
},
|
|
360
|
+
tokenOut: {
|
|
361
|
+
chain: params.toChain,
|
|
362
|
+
symbol: params.toToken,
|
|
363
|
+
address: '0x0000000000000000000000000000000000000000' as HexString, // Native token
|
|
364
|
+
decimals: 18, // Default decimals
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
hops: 1,
|
|
369
|
+
},
|
|
370
|
+
metadata: {
|
|
371
|
+
backend: 'direct-chain',
|
|
372
|
+
swapId,
|
|
373
|
+
privacyLevel: params.privacyLevel,
|
|
374
|
+
stealthAddress: stealthData,
|
|
375
|
+
},
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Execute a same-chain swap
|
|
381
|
+
*
|
|
382
|
+
* @throws {ValidationError} If quote is invalid or wallet adapter not configured
|
|
383
|
+
*/
|
|
384
|
+
async executeSwap(params: SwapParams): Promise<SwapResult> {
|
|
385
|
+
const swapData = this.swaps.get(params.quoteId)
|
|
386
|
+
|
|
387
|
+
if (!swapData) {
|
|
388
|
+
throw new ValidationError(
|
|
389
|
+
`Quote not found: ${params.quoteId}`,
|
|
390
|
+
'quoteId',
|
|
391
|
+
{ quoteId: params.quoteId }
|
|
392
|
+
)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (!this.walletAdapter) {
|
|
396
|
+
throw new ValidationError(
|
|
397
|
+
'Wallet adapter not configured. Cannot execute swap without wallet.',
|
|
398
|
+
'walletAdapter',
|
|
399
|
+
{ hint: 'Provide a wallet adapter in DirectChainBackend constructor' }
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Validate wallet is connected
|
|
404
|
+
if (!this.walletAdapter.isConnected()) {
|
|
405
|
+
throw new ValidationError(
|
|
406
|
+
'Wallet not connected',
|
|
407
|
+
'walletAdapter',
|
|
408
|
+
{ hint: 'Connect wallet before executing swap' }
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Validate wallet chain matches swap chain
|
|
413
|
+
if (this.walletAdapter.chain !== swapData.fromChain) {
|
|
414
|
+
throw new ValidationError(
|
|
415
|
+
`Wallet chain mismatch. Expected ${swapData.fromChain}, got ${this.walletAdapter.chain}`,
|
|
416
|
+
'walletAdapter',
|
|
417
|
+
{
|
|
418
|
+
expectedChain: swapData.fromChain,
|
|
419
|
+
actualChain: this.walletAdapter.chain,
|
|
420
|
+
}
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Update status to in progress
|
|
425
|
+
swapData.status = SwapStatus.IN_PROGRESS
|
|
426
|
+
swapData.updatedAt = Date.now()
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
// Build transaction
|
|
430
|
+
const tx = {
|
|
431
|
+
chain: swapData.fromChain,
|
|
432
|
+
data: {
|
|
433
|
+
to: swapData.recipientAddress,
|
|
434
|
+
value: swapData.amountOut, // Keep as string for serialization
|
|
435
|
+
stealthMetadata: swapData.privacyLevel !== PrivacyLevel.TRANSPARENT
|
|
436
|
+
? swapData.stealthAddress
|
|
437
|
+
: undefined,
|
|
438
|
+
},
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Sign and send transaction
|
|
442
|
+
const receipt = await this.walletAdapter.signAndSendTransaction(tx)
|
|
443
|
+
|
|
444
|
+
// Update swap data
|
|
445
|
+
swapData.status = SwapStatus.SUCCESS
|
|
446
|
+
swapData.depositTxHash = receipt.txHash
|
|
447
|
+
swapData.settlementTxHash = receipt.txHash
|
|
448
|
+
swapData.updatedAt = Date.now()
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
swapId: swapData.swapId,
|
|
452
|
+
status: SwapStatus.SUCCESS,
|
|
453
|
+
quoteId: params.quoteId,
|
|
454
|
+
depositAddress: swapData.depositAddress,
|
|
455
|
+
depositTxHash: receipt.txHash,
|
|
456
|
+
settlementTxHash: receipt.txHash,
|
|
457
|
+
actualAmountOut: swapData.amountOut,
|
|
458
|
+
metadata: {
|
|
459
|
+
stealthAddress: swapData.stealthAddress,
|
|
460
|
+
privacyLevel: swapData.privacyLevel,
|
|
461
|
+
},
|
|
462
|
+
}
|
|
463
|
+
} catch (error) {
|
|
464
|
+
// Update status to failed
|
|
465
|
+
swapData.status = SwapStatus.FAILED
|
|
466
|
+
swapData.errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
467
|
+
swapData.updatedAt = Date.now()
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
swapId: swapData.swapId,
|
|
471
|
+
status: SwapStatus.FAILED,
|
|
472
|
+
quoteId: params.quoteId,
|
|
473
|
+
depositAddress: swapData.depositAddress,
|
|
474
|
+
errorMessage: swapData.errorMessage,
|
|
475
|
+
metadata: {
|
|
476
|
+
stealthAddress: swapData.stealthAddress,
|
|
477
|
+
privacyLevel: swapData.privacyLevel,
|
|
478
|
+
},
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Get current swap status
|
|
485
|
+
*
|
|
486
|
+
* @throws {ValidationError} If swap ID is invalid
|
|
487
|
+
*/
|
|
488
|
+
async getStatus(swapId: string): Promise<SwapStatusResponse> {
|
|
489
|
+
const swapData = this.swaps.get(swapId)
|
|
490
|
+
|
|
491
|
+
if (!swapData) {
|
|
492
|
+
throw new ValidationError(
|
|
493
|
+
`Swap not found: ${swapId}`,
|
|
494
|
+
'swapId',
|
|
495
|
+
{ swapId }
|
|
496
|
+
)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
swapId: swapData.swapId,
|
|
501
|
+
status: swapData.status,
|
|
502
|
+
quoteId: swapData.quoteId,
|
|
503
|
+
depositAddress: swapData.depositAddress,
|
|
504
|
+
amountIn: swapData.amountIn,
|
|
505
|
+
amountOut: swapData.amountOut,
|
|
506
|
+
depositTxHash: swapData.depositTxHash,
|
|
507
|
+
settlementTxHash: swapData.settlementTxHash,
|
|
508
|
+
actualAmountOut: swapData.status === SwapStatus.SUCCESS ? swapData.amountOut : undefined,
|
|
509
|
+
errorMessage: swapData.errorMessage,
|
|
510
|
+
stealthRecipient: swapData.stealthAddress?.address,
|
|
511
|
+
ephemeralPublicKey: swapData.stealthAddress?.ephemeralPublicKey,
|
|
512
|
+
updatedAt: swapData.updatedAt,
|
|
513
|
+
metadata: {
|
|
514
|
+
fromChain: swapData.fromChain,
|
|
515
|
+
toChain: swapData.toChain,
|
|
516
|
+
fromToken: swapData.fromToken,
|
|
517
|
+
toToken: swapData.toToken,
|
|
518
|
+
privacyLevel: swapData.privacyLevel,
|
|
519
|
+
},
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Get dry quote (preview without creating swap)
|
|
525
|
+
*
|
|
526
|
+
* This returns the same as getQuote but doesn't store swap data.
|
|
527
|
+
*/
|
|
528
|
+
async getDryQuote(params: QuoteParams): Promise<Quote> {
|
|
529
|
+
const quote = await this.getQuote(params)
|
|
530
|
+
|
|
531
|
+
// Remove from storage (it was just added by getQuote)
|
|
532
|
+
this.swaps.delete(quote.quoteId)
|
|
533
|
+
if (quote.metadata?.swapId) {
|
|
534
|
+
this.swaps.delete(quote.metadata.swapId as string)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return quote
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Set wallet adapter (for updating wallet connection)
|
|
542
|
+
*/
|
|
543
|
+
setWalletAdapter(adapter: WalletAdapter): void {
|
|
544
|
+
this.walletAdapter = adapter
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Get current wallet adapter
|
|
549
|
+
*/
|
|
550
|
+
getWalletAdapter(): WalletAdapter | undefined {
|
|
551
|
+
return this.walletAdapter
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Clear all swap data (for testing)
|
|
556
|
+
*/
|
|
557
|
+
clearSwaps(): void {
|
|
558
|
+
this.swaps.clear()
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Create a DirectChainBackend instance
|
|
564
|
+
*/
|
|
565
|
+
export function createDirectChainBackend(
|
|
566
|
+
config?: DirectChainBackendConfig
|
|
567
|
+
): DirectChainBackend {
|
|
568
|
+
return new DirectChainBackend(config)
|
|
569
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settlement Backends
|
|
3
|
+
*
|
|
4
|
+
* Collection of settlement backend implementations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
NEARIntentsBackend,
|
|
9
|
+
createNEARIntentsBackend,
|
|
10
|
+
} from './near-intents'
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
ZcashNativeBackend,
|
|
14
|
+
createZcashNativeBackend,
|
|
15
|
+
type ZcashNativeBackendConfig,
|
|
16
|
+
} from './zcash-native'
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
DirectChainBackend,
|
|
20
|
+
createDirectChainBackend,
|
|
21
|
+
type DirectChainBackendConfig,
|
|
22
|
+
} from './direct-chain'
|