@sip-protocol/sdk 0.3.1 → 0.4.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.
Files changed (49) hide show
  1. package/dist/browser.d.mts +2 -2
  2. package/dist/browser.d.ts +2 -2
  3. package/dist/browser.js +1028 -146
  4. package/dist/browser.mjs +49 -1
  5. package/dist/chunk-AOZIY3GU.mjs +12995 -0
  6. package/dist/chunk-BCLIX5T2.mjs +12940 -0
  7. package/dist/chunk-EU4UEWWG.mjs +12164 -0
  8. package/dist/chunk-FKXPHKYD.mjs +12955 -0
  9. package/dist/chunk-OPQ2GQIO.mjs +13013 -0
  10. package/dist/index-BcWNakUD.d.ts +7990 -0
  11. package/dist/index-BsKY3Hr0.d.mts +7990 -0
  12. package/dist/index.d.mts +2 -2
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.js +999 -117
  15. package/dist/index.mjs +49 -1
  16. package/package.json +2 -1
  17. package/src/adapters/near-intents.ts +8 -0
  18. package/src/bitcoin/index.ts +51 -0
  19. package/src/bitcoin/silent-payments.ts +865 -0
  20. package/src/bitcoin/taproot.ts +590 -0
  21. package/src/cosmos/ibc-stealth.ts +825 -0
  22. package/src/cosmos/index.ts +83 -0
  23. package/src/cosmos/stealth.ts +487 -0
  24. package/src/index.ts +51 -0
  25. package/src/move/aptos.ts +369 -0
  26. package/src/move/index.ts +35 -0
  27. package/src/move/sui.ts +367 -0
  28. package/src/oracle/types.ts +8 -0
  29. package/src/settlement/backends/direct-chain.ts +8 -0
  30. package/src/settlement/backends/near-intents.ts +11 -0
  31. package/src/stealth.ts +3 -3
  32. package/src/validation.ts +42 -1
  33. package/src/wallet/aptos/adapter.ts +422 -0
  34. package/src/wallet/aptos/index.ts +10 -0
  35. package/src/wallet/aptos/mock.ts +410 -0
  36. package/src/wallet/aptos/types.ts +278 -0
  37. package/src/wallet/bitcoin/adapter.ts +470 -0
  38. package/src/wallet/bitcoin/index.ts +38 -0
  39. package/src/wallet/bitcoin/mock.ts +516 -0
  40. package/src/wallet/bitcoin/types.ts +274 -0
  41. package/src/wallet/cosmos/adapter.ts +484 -0
  42. package/src/wallet/cosmos/index.ts +63 -0
  43. package/src/wallet/cosmos/mock.ts +596 -0
  44. package/src/wallet/cosmos/types.ts +462 -0
  45. package/src/wallet/index.ts +127 -0
  46. package/src/wallet/sui/adapter.ts +471 -0
  47. package/src/wallet/sui/index.ts +10 -0
  48. package/src/wallet/sui/mock.ts +439 -0
  49. package/src/wallet/sui/types.ts +245 -0
@@ -0,0 +1,471 @@
1
+ /**
2
+ * Sui Wallet Adapter
3
+ *
4
+ * Implementation of WalletAdapter for Sui blockchain.
5
+ * Supports Sui Wallet, Ethos, Suiet, and other Sui wallets.
6
+ */
7
+
8
+ import type {
9
+ HexString,
10
+ Asset,
11
+ Signature,
12
+ UnsignedTransaction,
13
+ SignedTransaction,
14
+ TransactionReceipt,
15
+ } from '@sip-protocol/types'
16
+ import { WalletErrorCode } from '@sip-protocol/types'
17
+ import { BaseWalletAdapter } from '../base-adapter'
18
+ import { WalletError } from '../errors'
19
+ import type {
20
+ SuiWalletProvider,
21
+ SuiAdapterConfig,
22
+ SuiWalletName,
23
+ SuiAccountInfo,
24
+ SuiTransactionBlock,
25
+ SuiSignMessageInput,
26
+ } from './types'
27
+ import {
28
+ getSuiProvider,
29
+ suiPublicKeyToHex,
30
+ getDefaultSuiRpcEndpoint,
31
+ } from './types'
32
+
33
+ /**
34
+ * Sui wallet adapter
35
+ *
36
+ * Provides SIP-compatible wallet interface for Sui.
37
+ * Works with Sui Wallet, Ethos, Suiet, and other Sui wallets.
38
+ *
39
+ * @example Browser usage with Sui Wallet
40
+ * ```typescript
41
+ * const wallet = new SuiWalletAdapter({ wallet: 'sui-wallet' })
42
+ * await wallet.connect()
43
+ *
44
+ * const balance = await wallet.getBalance()
45
+ * console.log(`Balance: ${balance} MIST`)
46
+ *
47
+ * // Sign a message
48
+ * const sig = await wallet.signMessage(new TextEncoder().encode('Hello'))
49
+ * ```
50
+ *
51
+ * @example With custom RPC endpoint
52
+ * ```typescript
53
+ * const wallet = new SuiWalletAdapter({
54
+ * wallet: 'sui-wallet',
55
+ * network: 'testnet',
56
+ * rpcEndpoint: 'https://my-rpc.example.com',
57
+ * })
58
+ * ```
59
+ */
60
+ export class SuiWalletAdapter extends BaseWalletAdapter {
61
+ readonly chain = 'sui' as const
62
+ readonly name: string
63
+
64
+ private provider: SuiWalletProvider | undefined
65
+ private walletName: SuiWalletName
66
+ private network: string
67
+ private rpcEndpoint: string
68
+ private accounts: SuiAccountInfo[] = []
69
+
70
+ // Event handler references for cleanup
71
+ private accountChangeHandler?: (...args: any[]) => void
72
+ private disconnectHandler?: (...args: any[]) => void
73
+
74
+ constructor(config: SuiAdapterConfig = {}) {
75
+ super()
76
+ this.walletName = config.wallet ?? 'sui-wallet'
77
+ this.name = `sui-${this.walletName}`
78
+ this.network = config.network ?? 'mainnet'
79
+ this.rpcEndpoint = config.rpcEndpoint ?? getDefaultSuiRpcEndpoint(this.network)
80
+
81
+ // Allow injecting provider for testing
82
+ if (config.provider) {
83
+ this.provider = config.provider
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Get the current Sui network
89
+ */
90
+ getNetwork(): string {
91
+ return this.network
92
+ }
93
+
94
+ /**
95
+ * Get the RPC endpoint
96
+ */
97
+ getRpcEndpoint(): string {
98
+ return this.rpcEndpoint
99
+ }
100
+
101
+ /**
102
+ * Set the RPC endpoint
103
+ */
104
+ setRpcEndpoint(endpoint: string): void {
105
+ this.rpcEndpoint = endpoint
106
+ }
107
+
108
+ /**
109
+ * Get all connected accounts
110
+ */
111
+ getAccounts(): SuiAccountInfo[] {
112
+ return [...this.accounts]
113
+ }
114
+
115
+ /**
116
+ * Connect to the wallet
117
+ */
118
+ async connect(): Promise<void> {
119
+ this._connectionState = 'connecting'
120
+
121
+ try {
122
+ // Get provider if not already set
123
+ if (!this.provider) {
124
+ this.provider = getSuiProvider(this.walletName)
125
+ }
126
+
127
+ if (!this.provider) {
128
+ this.setError(
129
+ WalletErrorCode.NOT_INSTALLED,
130
+ `${this.walletName} wallet is not installed`
131
+ )
132
+ throw new WalletError(
133
+ `${this.walletName} wallet is not installed`,
134
+ WalletErrorCode.NOT_INSTALLED
135
+ )
136
+ }
137
+
138
+ // Request permissions
139
+ const hasPermissions = await this.provider.hasPermissions()
140
+ if (!hasPermissions) {
141
+ await this.provider.requestPermissions()
142
+ }
143
+
144
+ // Get accounts
145
+ this.accounts = await this.provider.getAccounts()
146
+
147
+ if (!this.accounts || this.accounts.length === 0) {
148
+ throw new WalletError(
149
+ 'No accounts returned from wallet',
150
+ WalletErrorCode.CONNECTION_FAILED
151
+ )
152
+ }
153
+
154
+ // Set up event handlers
155
+ this.setupEventHandlers()
156
+
157
+ // Use first account
158
+ const firstAccount = this.accounts[0]
159
+ const address = firstAccount.address
160
+ const hexPubKey = suiPublicKeyToHex(firstAccount.publicKey)
161
+ this.setConnected(address, hexPubKey)
162
+ } catch (error) {
163
+ const message = error instanceof Error ? error.message : 'Connection failed'
164
+
165
+ // Check if user rejected
166
+ if (message.includes('User rejected') || message.includes('rejected') || message.includes('User disapproved')) {
167
+ this.setError(WalletErrorCode.CONNECTION_REJECTED, message)
168
+ throw new WalletError(message, WalletErrorCode.CONNECTION_REJECTED)
169
+ }
170
+
171
+ this.setError(WalletErrorCode.CONNECTION_FAILED, message)
172
+ throw error instanceof WalletError
173
+ ? error
174
+ : new WalletError(message, WalletErrorCode.CONNECTION_FAILED, { cause: error as Error })
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Disconnect from the wallet
180
+ */
181
+ async disconnect(): Promise<void> {
182
+ this.cleanupEventHandlers()
183
+
184
+ this.setDisconnected('User disconnected')
185
+ this.provider = undefined
186
+ this.accounts = []
187
+ }
188
+
189
+ /**
190
+ * Sign a message
191
+ */
192
+ async signMessage(message: Uint8Array): Promise<Signature> {
193
+ this.requireConnected()
194
+
195
+ if (!this.provider) {
196
+ throw new WalletError('Provider not available', WalletErrorCode.NOT_CONNECTED)
197
+ }
198
+
199
+ try {
200
+ const input: SuiSignMessageInput = {
201
+ message,
202
+ account: this.accounts[0],
203
+ }
204
+
205
+ const response = await this.provider.signMessage(input)
206
+
207
+ // Convert signature to hex if it's base64
208
+ let signatureHex: string
209
+ if (response.signature.startsWith('0x')) {
210
+ signatureHex = response.signature
211
+ } else {
212
+ // Assume base64
213
+ const sigBytes = Buffer.from(response.signature, 'base64')
214
+ signatureHex = `0x${sigBytes.toString('hex')}`
215
+ }
216
+
217
+ return {
218
+ signature: signatureHex as HexString,
219
+ publicKey: this._publicKey as HexString,
220
+ }
221
+ } catch (error) {
222
+ const message = error instanceof Error ? error.message : 'Signing failed'
223
+
224
+ if (message.includes('User rejected') || message.includes('rejected') || message.includes('User disapproved')) {
225
+ throw new WalletError(message, WalletErrorCode.SIGNING_REJECTED)
226
+ }
227
+
228
+ throw new WalletError(message, WalletErrorCode.SIGNING_FAILED, {
229
+ cause: error as Error,
230
+ })
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Sign a transaction
236
+ *
237
+ * The transaction data should be a SuiTransactionBlock
238
+ */
239
+ async signTransaction(tx: UnsignedTransaction): Promise<SignedTransaction> {
240
+ this.requireConnected()
241
+
242
+ if (!this.provider) {
243
+ throw new WalletError('Provider not available', WalletErrorCode.NOT_CONNECTED)
244
+ }
245
+
246
+ try {
247
+ const suiTx = tx.data as SuiTransactionBlock
248
+ const signedTx = await this.provider.signTransactionBlock({
249
+ transactionBlock: suiTx,
250
+ account: this.accounts[0],
251
+ })
252
+
253
+ // Convert signature to hex
254
+ let signatureHex: string
255
+ if (signedTx.signature.startsWith('0x')) {
256
+ signatureHex = signedTx.signature
257
+ } else {
258
+ const sigBytes = Buffer.from(signedTx.signature, 'base64')
259
+ signatureHex = `0x${sigBytes.toString('hex')}`
260
+ }
261
+
262
+ // Convert transaction bytes to hex
263
+ let txBytesHex: string
264
+ if (signedTx.transactionBlockBytes.startsWith('0x')) {
265
+ txBytesHex = signedTx.transactionBlockBytes
266
+ } else {
267
+ const txBytes = Buffer.from(signedTx.transactionBlockBytes, 'base64')
268
+ txBytesHex = `0x${txBytes.toString('hex')}`
269
+ }
270
+
271
+ return {
272
+ unsigned: tx,
273
+ signatures: [
274
+ {
275
+ signature: signatureHex as HexString,
276
+ publicKey: this._publicKey as HexString,
277
+ },
278
+ ],
279
+ serialized: txBytesHex as HexString,
280
+ }
281
+ } catch (error) {
282
+ const message = error instanceof Error ? error.message : 'Signing failed'
283
+
284
+ if (message.includes('User rejected') || message.includes('rejected') || message.includes('User disapproved')) {
285
+ throw new WalletError(message, WalletErrorCode.SIGNING_REJECTED)
286
+ }
287
+
288
+ throw new WalletError(message, WalletErrorCode.SIGNING_FAILED, {
289
+ cause: error as Error,
290
+ })
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Sign and send a transaction
296
+ */
297
+ async signAndSendTransaction(tx: UnsignedTransaction): Promise<TransactionReceipt> {
298
+ this.requireConnected()
299
+
300
+ if (!this.provider) {
301
+ throw new WalletError('Provider not available', WalletErrorCode.NOT_CONNECTED)
302
+ }
303
+
304
+ try {
305
+ const suiTx = tx.data as SuiTransactionBlock
306
+ const response = await this.provider.signAndExecuteTransactionBlock({
307
+ transactionBlock: suiTx,
308
+ account: this.accounts[0],
309
+ })
310
+
311
+ return {
312
+ txHash: (response.digest.startsWith('0x')
313
+ ? response.digest
314
+ : `0x${response.digest}`) as HexString,
315
+ status: 'pending', // Transaction is sent but not confirmed yet
316
+ timestamp: Date.now(),
317
+ }
318
+ } catch (error) {
319
+ const message = error instanceof Error ? error.message : 'Transaction failed'
320
+
321
+ if (message.includes('User rejected') || message.includes('rejected') || message.includes('User disapproved')) {
322
+ throw new WalletError(message, WalletErrorCode.TRANSACTION_REJECTED)
323
+ }
324
+
325
+ if (message.includes('insufficient') || message.includes('Insufficient')) {
326
+ throw new WalletError(message, WalletErrorCode.INSUFFICIENT_FUNDS)
327
+ }
328
+
329
+ throw new WalletError(message, WalletErrorCode.TRANSACTION_FAILED, {
330
+ cause: error as Error,
331
+ })
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Get native SUI balance (in MIST - 1 SUI = 1,000,000,000 MIST)
337
+ */
338
+ async getBalance(): Promise<bigint> {
339
+ this.requireConnected()
340
+
341
+ try {
342
+ const response = await fetch(this.rpcEndpoint, {
343
+ method: 'POST',
344
+ headers: { 'Content-Type': 'application/json' },
345
+ body: JSON.stringify({
346
+ jsonrpc: '2.0',
347
+ id: 1,
348
+ method: 'suix_getBalance',
349
+ params: [this._address],
350
+ }),
351
+ })
352
+
353
+ if (!response.ok) {
354
+ throw new Error(`Failed to fetch balance: ${response.statusText}`)
355
+ }
356
+
357
+ const data = await response.json()
358
+ const balance = data.result?.totalBalance ?? '0'
359
+
360
+ return BigInt(balance)
361
+ } catch (error) {
362
+ throw new WalletError(
363
+ 'Failed to get balance',
364
+ WalletErrorCode.UNKNOWN,
365
+ { cause: error as Error }
366
+ )
367
+ }
368
+ }
369
+
370
+ /**
371
+ * Get token balance for a specific coin type
372
+ */
373
+ async getTokenBalance(asset: Asset): Promise<bigint> {
374
+ this.requireConnected()
375
+
376
+ if (asset.chain !== 'sui') {
377
+ throw new WalletError(
378
+ `Asset chain ${asset.chain} not supported by Sui adapter`,
379
+ WalletErrorCode.UNSUPPORTED_CHAIN
380
+ )
381
+ }
382
+
383
+ // Native SUI
384
+ if (!asset.address || asset.address === '0x2::sui::SUI') {
385
+ return this.getBalance()
386
+ }
387
+
388
+ try {
389
+ const coinType = asset.address
390
+ const response = await fetch(this.rpcEndpoint, {
391
+ method: 'POST',
392
+ headers: { 'Content-Type': 'application/json' },
393
+ body: JSON.stringify({
394
+ jsonrpc: '2.0',
395
+ id: 1,
396
+ method: 'suix_getBalance',
397
+ params: [this._address, coinType],
398
+ }),
399
+ })
400
+
401
+ if (!response.ok) {
402
+ return 0n
403
+ }
404
+
405
+ const data = await response.json()
406
+ const balance = data.result?.totalBalance ?? '0'
407
+
408
+ return BigInt(balance)
409
+ } catch {
410
+ // Token account might not exist
411
+ return 0n
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Set up wallet event handlers
417
+ */
418
+ private setupEventHandlers(): void {
419
+ if (!this.provider?.on) return
420
+
421
+ this.accountChangeHandler = (accounts: SuiAccountInfo[]) => {
422
+ if (!accounts || accounts.length === 0) {
423
+ this.setDisconnected('Account changed')
424
+ return
425
+ }
426
+
427
+ const previousAddress = this._address
428
+ const newAccount = accounts[0]
429
+ const newAddress = newAccount.address
430
+
431
+ if (newAddress && newAddress !== previousAddress) {
432
+ this.accounts = accounts
433
+ this._address = newAddress
434
+ this._publicKey = suiPublicKeyToHex(newAccount.publicKey)
435
+ this.emitAccountChanged(previousAddress, newAddress)
436
+ }
437
+ }
438
+ this.provider.on('accountChanged', this.accountChangeHandler)
439
+
440
+ this.disconnectHandler = () => {
441
+ this.setDisconnected('Wallet disconnected')
442
+ }
443
+ this.provider.on('disconnect', this.disconnectHandler)
444
+ }
445
+
446
+ /**
447
+ * Clean up wallet event handlers
448
+ */
449
+ private cleanupEventHandlers(): void {
450
+ if (!this.provider?.off) return
451
+
452
+ if (this.accountChangeHandler) {
453
+ this.provider.off('accountChanged', this.accountChangeHandler)
454
+ }
455
+ if (this.disconnectHandler) {
456
+ this.provider.off('disconnect', this.disconnectHandler)
457
+ }
458
+
459
+ this.accountChangeHandler = undefined
460
+ this.disconnectHandler = undefined
461
+ }
462
+ }
463
+
464
+ /**
465
+ * Create a Sui wallet adapter with default configuration
466
+ */
467
+ export function createSuiAdapter(
468
+ config: SuiAdapterConfig = {}
469
+ ): SuiWalletAdapter {
470
+ return new SuiWalletAdapter(config)
471
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Sui Wallet Adapter
3
+ *
4
+ * Entry point for Sui wallet integration.
5
+ * Exports adapter, types, and utilities.
6
+ */
7
+
8
+ export * from './types'
9
+ export * from './adapter'
10
+ export * from './mock'