@sip-protocol/sdk 0.6.0 → 0.6.2
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/README.md +58 -0
- package/dist/browser.d.mts +4 -4
- package/dist/browser.d.ts +4 -4
- package/dist/browser.js +2752 -448
- package/dist/browser.mjs +31 -1
- package/dist/chunk-7QZPORY5.mjs +15604 -0
- package/dist/chunk-C2NPCUAJ.mjs +17010 -0
- package/dist/chunk-FCVLFUIC.mjs +16699 -0
- package/dist/chunk-G5UHXECN.mjs +16340 -0
- package/dist/chunk-GEDEIZHJ.mjs +16798 -0
- package/dist/chunk-GOOEOAMV.mjs +17026 -0
- package/dist/chunk-MTNYSNR7.mjs +16269 -0
- package/dist/chunk-O5PIB2EA.mjs +16698 -0
- package/dist/chunk-PCFM7FQO.mjs +17010 -0
- package/dist/chunk-QK464ARC.mjs +16946 -0
- package/dist/chunk-VNBMNGC3.mjs +16698 -0
- package/dist/chunk-W5TUELDQ.mjs +16947 -0
- package/dist/index-CD_zShu-.d.ts +10870 -0
- package/dist/index-CQBYdLYy.d.mts +10976 -0
- package/dist/index-Cg9TYEPv.d.mts +11321 -0
- package/dist/index-CqZJOO8C.d.mts +11323 -0
- package/dist/index-CywN9Bnp.d.ts +11321 -0
- package/dist/index-DHy5ZjCD.d.ts +10976 -0
- package/dist/index-DfsVsmxu.d.ts +11323 -0
- package/dist/index-ObjwyVDX.d.mts +10870 -0
- package/dist/index-m0xbSfmT.d.mts +11318 -0
- package/dist/index-rWLEgvhN.d.ts +11318 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2737 -427
- package/dist/index.mjs +31 -1
- package/dist/noir-DKfEzWy9.d.mts +482 -0
- package/dist/noir-DKfEzWy9.d.ts +482 -0
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/proofs/noir.js +12 -3
- package/dist/proofs/noir.mjs +12 -3
- package/package.json +16 -14
- package/src/adapters/near-intents.ts +13 -3
- package/src/auction/index.ts +20 -0
- package/src/auction/sealed-bid.ts +1037 -0
- package/src/compliance/derivation.ts +13 -3
- package/src/compliance/reports.ts +5 -4
- package/src/cosmos/ibc-stealth.ts +2 -2
- package/src/cosmos/stealth.ts +2 -2
- package/src/governance/index.ts +19 -0
- package/src/governance/private-vote.ts +1116 -0
- package/src/index.ts +50 -2
- package/src/intent.ts +145 -8
- package/src/nft/index.ts +27 -0
- package/src/nft/private-nft.ts +811 -0
- package/src/proofs/browser-utils.ts +1 -7
- package/src/proofs/noir.ts +34 -7
- package/src/settlement/backends/direct-chain.ts +14 -3
- package/src/stealth.ts +31 -13
- package/src/types/browser.d.ts +67 -0
- package/src/validation.ts +4 -2
- package/src/wallet/bitcoin/adapter.ts +159 -15
- package/src/wallet/bitcoin/types.ts +340 -15
- package/src/wallet/cosmos/mock.ts +16 -12
- package/src/wallet/hardware/ledger.ts +82 -12
- package/src/wallet/hardware/types.ts +2 -0
- package/LICENSE +0 -21
|
@@ -241,10 +241,8 @@ export function getMobileDeviceInfo(): MobileDeviceInfo {
|
|
|
241
241
|
isTablet: isTablet(),
|
|
242
242
|
supportsTouch: supportsTouch(),
|
|
243
243
|
deviceMemoryGB:
|
|
244
|
-
// @ts-expect-error - deviceMemory is non-standard
|
|
245
244
|
typeof navigator !== 'undefined' && navigator.deviceMemory
|
|
246
|
-
?
|
|
247
|
-
navigator.deviceMemory
|
|
245
|
+
? navigator.deviceMemory
|
|
248
246
|
: null,
|
|
249
247
|
hardwareConcurrency:
|
|
250
248
|
typeof navigator !== 'undefined' && navigator.hardwareConcurrency
|
|
@@ -487,10 +485,8 @@ export async function estimateAvailableMemory(): Promise<number | null> {
|
|
|
487
485
|
if (!isBrowser()) return null
|
|
488
486
|
|
|
489
487
|
// Use Performance API if available (Chrome)
|
|
490
|
-
// @ts-expect-error - Performance.measureUserAgentSpecificMemory is Chrome-specific
|
|
491
488
|
if (typeof performance !== 'undefined' && performance.measureUserAgentSpecificMemory) {
|
|
492
489
|
try {
|
|
493
|
-
// @ts-expect-error - Chrome-specific API
|
|
494
490
|
const result = await performance.measureUserAgentSpecificMemory()
|
|
495
491
|
return result.bytes
|
|
496
492
|
} catch {
|
|
@@ -499,10 +495,8 @@ export async function estimateAvailableMemory(): Promise<number | null> {
|
|
|
499
495
|
}
|
|
500
496
|
|
|
501
497
|
// Use navigator.deviceMemory if available (Chrome, Opera)
|
|
502
|
-
// @ts-expect-error - deviceMemory is non-standard
|
|
503
498
|
if (typeof navigator !== 'undefined' && navigator.deviceMemory) {
|
|
504
499
|
// Returns approximate device memory in GB
|
|
505
|
-
// @ts-expect-error - deviceMemory is non-standard
|
|
506
500
|
return navigator.deviceMemory * 1024 * 1024 * 1024
|
|
507
501
|
}
|
|
508
502
|
|
package/src/proofs/noir.ts
CHANGED
|
@@ -71,9 +71,25 @@ export interface NoirProviderConfig {
|
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* Oracle public key for verifying attestations in fulfillment proofs
|
|
74
|
-
* Required for production use. If not provided
|
|
74
|
+
* Required for production use. If not provided and strictMode is true,
|
|
75
|
+
* fulfillment proof generation will throw an error.
|
|
75
76
|
*/
|
|
76
77
|
oraclePublicKey?: PublicKeyCoordinates
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Enable strict mode for production use
|
|
81
|
+
*
|
|
82
|
+
* When true:
|
|
83
|
+
* - Fulfillment proofs require configured oraclePublicKey
|
|
84
|
+
* - Missing configuration throws errors instead of warnings
|
|
85
|
+
*
|
|
86
|
+
* When false (default):
|
|
87
|
+
* - Placeholder keys are used when oraclePublicKey not configured
|
|
88
|
+
* - Warnings are logged for missing configuration
|
|
89
|
+
*
|
|
90
|
+
* @default false
|
|
91
|
+
*/
|
|
92
|
+
strictMode?: boolean
|
|
77
93
|
}
|
|
78
94
|
|
|
79
95
|
/**
|
|
@@ -114,6 +130,7 @@ export class NoirProofProvider implements ProofProvider {
|
|
|
114
130
|
this.config = {
|
|
115
131
|
backend: 'barretenberg',
|
|
116
132
|
verbose: false,
|
|
133
|
+
strictMode: false,
|
|
117
134
|
...config,
|
|
118
135
|
}
|
|
119
136
|
}
|
|
@@ -588,15 +605,25 @@ export class NoirProofProvider implements ProofProvider {
|
|
|
588
605
|
attestation.blockNumber
|
|
589
606
|
)
|
|
590
607
|
|
|
591
|
-
//
|
|
592
|
-
|
|
608
|
+
// Validate oracle public key configuration
|
|
609
|
+
if (!this.config.oraclePublicKey) {
|
|
610
|
+
if (this.config.strictMode) {
|
|
611
|
+
throw new ProofGenerationError(
|
|
612
|
+
'fulfillment',
|
|
613
|
+
'Oracle public key is required in strict mode. Configure oraclePublicKey in NoirProviderConfig for production use.'
|
|
614
|
+
)
|
|
615
|
+
}
|
|
616
|
+
// Always warn when using placeholder keys, not just in verbose mode
|
|
617
|
+
console.warn(
|
|
618
|
+
'[NoirProofProvider] WARNING: No oracle public key configured. Using placeholder keys. ' +
|
|
619
|
+
'Proofs will NOT be valid for production. Set strictMode: true to enforce configuration.'
|
|
620
|
+
)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Use configured oracle public key, or placeholder if not in strict mode
|
|
593
624
|
const oraclePubKeyX = this.config.oraclePublicKey?.x ?? new Array(32).fill(0)
|
|
594
625
|
const oraclePubKeyY = this.config.oraclePublicKey?.y ?? new Array(32).fill(0)
|
|
595
626
|
|
|
596
|
-
if (!this.config.oraclePublicKey && this.config.verbose) {
|
|
597
|
-
console.warn('[NoirProofProvider] Warning: No oracle public key configured. Using placeholder keys.')
|
|
598
|
-
}
|
|
599
|
-
|
|
600
627
|
// Prepare witness inputs for the circuit
|
|
601
628
|
const witnessInputs = {
|
|
602
629
|
// Public inputs
|
|
@@ -39,6 +39,8 @@ import {
|
|
|
39
39
|
ed25519PublicKeyToNearAddress,
|
|
40
40
|
isEd25519Chain,
|
|
41
41
|
} from '../../stealth'
|
|
42
|
+
import { ed25519PublicKeyToAptosAddress } from '../../move/aptos'
|
|
43
|
+
import { ed25519PublicKeyToSuiAddress } from '../../move/sui'
|
|
42
44
|
import { ValidationError } from '../../errors'
|
|
43
45
|
import { randomBytes, bytesToHex } from '@noble/hashes/utils'
|
|
44
46
|
|
|
@@ -161,6 +163,8 @@ export class DirectChainBackend implements SettlementBackend {
|
|
|
161
163
|
'optimism',
|
|
162
164
|
'base',
|
|
163
165
|
'bitcoin',
|
|
166
|
+
'aptos',
|
|
167
|
+
'sui',
|
|
164
168
|
],
|
|
165
169
|
supportedDestinationChains: [
|
|
166
170
|
'ethereum',
|
|
@@ -172,6 +176,8 @@ export class DirectChainBackend implements SettlementBackend {
|
|
|
172
176
|
'optimism',
|
|
173
177
|
'base',
|
|
174
178
|
'bitcoin',
|
|
179
|
+
'aptos',
|
|
180
|
+
'sui',
|
|
175
181
|
],
|
|
176
182
|
supportedPrivacyLevels: [
|
|
177
183
|
PrivacyLevel.TRANSPARENT,
|
|
@@ -280,7 +286,7 @@ export class DirectChainBackend implements SettlementBackend {
|
|
|
280
286
|
: params.recipientMetaAddress
|
|
281
287
|
|
|
282
288
|
if (isEd25519Chain(chain)) {
|
|
283
|
-
// Ed25519 chains (Solana, NEAR)
|
|
289
|
+
// Ed25519 chains (Solana, NEAR, Aptos, Sui)
|
|
284
290
|
const { stealthAddress } = generateEd25519StealthAddress(metaAddr)
|
|
285
291
|
stealthData = stealthAddress
|
|
286
292
|
|
|
@@ -288,11 +294,16 @@ export class DirectChainBackend implements SettlementBackend {
|
|
|
288
294
|
recipientAddress = ed25519PublicKeyToSolanaAddress(stealthAddress.address)
|
|
289
295
|
} else if (chain === 'near') {
|
|
290
296
|
recipientAddress = ed25519PublicKeyToNearAddress(stealthAddress.address)
|
|
297
|
+
} else if (chain === 'aptos') {
|
|
298
|
+
recipientAddress = ed25519PublicKeyToAptosAddress(stealthAddress.address)
|
|
299
|
+
} else if (chain === 'sui') {
|
|
300
|
+
recipientAddress = ed25519PublicKeyToSuiAddress(stealthAddress.address)
|
|
291
301
|
} else {
|
|
302
|
+
// This should not happen if ED25519_CHAINS is kept in sync
|
|
292
303
|
throw new ValidationError(
|
|
293
|
-
`Ed25519 address derivation not implemented for ${chain}
|
|
304
|
+
`Ed25519 address derivation not implemented for ${chain}. Please add support in direct-chain.ts.`,
|
|
294
305
|
'toChain',
|
|
295
|
-
{ chain }
|
|
306
|
+
{ chain, hint: 'Add address derivation function for this chain' }
|
|
296
307
|
)
|
|
297
308
|
}
|
|
298
309
|
} else {
|
package/src/stealth.ts
CHANGED
|
@@ -144,6 +144,7 @@ export function generateStealthMetaAddress(
|
|
|
144
144
|
|
|
145
145
|
/**
|
|
146
146
|
* Validate a StealthMetaAddress object
|
|
147
|
+
* Supports both secp256k1 (EVM chains) and ed25519 (Solana, NEAR, etc.) key formats
|
|
147
148
|
*/
|
|
148
149
|
function validateStealthMetaAddress(
|
|
149
150
|
metaAddress: StealthMetaAddress,
|
|
@@ -161,20 +162,37 @@ function validateStealthMetaAddress(
|
|
|
161
162
|
)
|
|
162
163
|
}
|
|
163
164
|
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
throw new ValidationError(
|
|
167
|
-
'spendingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)',
|
|
168
|
-
`${field}.spendingKey`
|
|
169
|
-
)
|
|
170
|
-
}
|
|
165
|
+
// Determine key type based on chain (ed25519 vs secp256k1)
|
|
166
|
+
const isEd25519 = isEd25519Chain(metaAddress.chain)
|
|
171
167
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
168
|
+
if (isEd25519) {
|
|
169
|
+
// Ed25519 chains (Solana, NEAR, Aptos, Sui) use 32-byte public keys
|
|
170
|
+
if (!isValidEd25519PublicKey(metaAddress.spendingKey)) {
|
|
171
|
+
throw new ValidationError(
|
|
172
|
+
'spendingKey must be a valid ed25519 public key (32 bytes)',
|
|
173
|
+
`${field}.spendingKey`
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
if (!isValidEd25519PublicKey(metaAddress.viewingKey)) {
|
|
177
|
+
throw new ValidationError(
|
|
178
|
+
'viewingKey must be a valid ed25519 public key (32 bytes)',
|
|
179
|
+
`${field}.viewingKey`
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
// Secp256k1 chains (Ethereum, etc.) use 33-byte compressed public keys
|
|
184
|
+
if (!isValidCompressedPublicKey(metaAddress.spendingKey)) {
|
|
185
|
+
throw new ValidationError(
|
|
186
|
+
'spendingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)',
|
|
187
|
+
`${field}.spendingKey`
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
if (!isValidCompressedPublicKey(metaAddress.viewingKey)) {
|
|
191
|
+
throw new ValidationError(
|
|
192
|
+
'viewingKey must be a valid compressed secp256k1 public key (33 bytes, starting with 02 or 03)',
|
|
193
|
+
`${field}.viewingKey`
|
|
194
|
+
)
|
|
195
|
+
}
|
|
178
196
|
}
|
|
179
197
|
}
|
|
180
198
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for non-standard browser APIs
|
|
3
|
+
*
|
|
4
|
+
* These declarations extend the standard DOM types to include
|
|
5
|
+
* browser-specific APIs used for device capability detection.
|
|
6
|
+
*
|
|
7
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory
|
|
8
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/measureUserAgentSpecificMemory
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extend Navigator interface with non-standard properties
|
|
13
|
+
*/
|
|
14
|
+
interface Navigator {
|
|
15
|
+
/**
|
|
16
|
+
* Device memory in gigabytes (rounded to preserve privacy)
|
|
17
|
+
*
|
|
18
|
+
* Only available in Chrome, Edge, Opera (Chromium-based browsers)
|
|
19
|
+
* Returns values like: 0.25, 0.5, 1, 2, 4, 8
|
|
20
|
+
*
|
|
21
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory
|
|
22
|
+
*/
|
|
23
|
+
readonly deviceMemory?: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Memory measurement result from Chrome-specific API
|
|
28
|
+
*/
|
|
29
|
+
interface MemoryMeasurement {
|
|
30
|
+
/** Total bytes used */
|
|
31
|
+
bytes: number
|
|
32
|
+
/** Breakdown of memory usage by type */
|
|
33
|
+
breakdown: Array<{
|
|
34
|
+
bytes: number
|
|
35
|
+
types: string[]
|
|
36
|
+
attribution?: Array<{
|
|
37
|
+
url: string
|
|
38
|
+
scope: string
|
|
39
|
+
}>
|
|
40
|
+
}>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extend Performance interface with Chrome-specific APIs
|
|
45
|
+
*/
|
|
46
|
+
interface Performance {
|
|
47
|
+
/**
|
|
48
|
+
* Measures memory usage with breakdown by type
|
|
49
|
+
*
|
|
50
|
+
* Chrome-specific API for detailed memory profiling
|
|
51
|
+
* Requires cross-origin isolation headers in some contexts
|
|
52
|
+
*
|
|
53
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/measureUserAgentSpecificMemory
|
|
54
|
+
*/
|
|
55
|
+
measureUserAgentSpecificMemory?(): Promise<MemoryMeasurement>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extend Window interface with vendor-prefixed APIs
|
|
60
|
+
*/
|
|
61
|
+
interface Window {
|
|
62
|
+
/**
|
|
63
|
+
* Safari's webkit-prefixed AudioContext
|
|
64
|
+
* Used for audio fingerprinting fallback
|
|
65
|
+
*/
|
|
66
|
+
webkitAudioContext?: typeof AudioContext
|
|
67
|
+
}
|
package/src/validation.ts
CHANGED
|
@@ -118,9 +118,11 @@ export function isValidSlippage(value: number): boolean {
|
|
|
118
118
|
/**
|
|
119
119
|
* SIP stealth meta-address format:
|
|
120
120
|
* sip:<chain>:<spendingKey>:<viewingKey>
|
|
121
|
-
*
|
|
121
|
+
* Keys can be:
|
|
122
|
+
* - secp256k1: 33 bytes compressed (66 hex chars with 0x prefix)
|
|
123
|
+
* - ed25519: 32 bytes (64 hex chars with 0x prefix)
|
|
122
124
|
*/
|
|
123
|
-
const STEALTH_META_ADDRESS_REGEX = /^sip:[a-z]+:0x[0-9a-fA-F]{66}:0x[0-9a-fA-F]{66}$/
|
|
125
|
+
const STEALTH_META_ADDRESS_REGEX = /^sip:[a-z]+:0x[0-9a-fA-F]{64,66}:0x[0-9a-fA-F]{64,66}$/
|
|
124
126
|
|
|
125
127
|
/**
|
|
126
128
|
* Check if a string is a valid stealth meta-address
|
|
@@ -271,18 +271,33 @@ export class BitcoinWalletAdapter extends BaseWalletAdapter {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
try {
|
|
274
|
-
//
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
// Extract the signed PSBT hex (without 0x prefix)
|
|
278
|
-
const signedPsbt = signed.serialized.slice(2)
|
|
274
|
+
// Extract PSBT from transaction data
|
|
275
|
+
const psbtHex = tx.data as string
|
|
276
|
+
const options = tx.metadata?.signPsbtOptions as SignPsbtOptions | undefined
|
|
279
277
|
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
// Sign with autoFinalized: true to get a finalized PSBT ready for broadcast
|
|
279
|
+
const signedPsbt = await this.provider.signPsbt(psbtHex, {
|
|
280
|
+
...options,
|
|
281
|
+
autoFinalized: true, // Request wallet to finalize the PSBT
|
|
282
|
+
})
|
|
283
283
|
|
|
284
|
-
//
|
|
285
|
-
|
|
284
|
+
// Try to broadcast the finalized transaction
|
|
285
|
+
let txid: string
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
// First, try to push the signed PSBT directly
|
|
289
|
+
// Some wallets return a raw transaction after finalization
|
|
290
|
+
txid = await this.provider.pushTx(signedPsbt)
|
|
291
|
+
} catch (pushError) {
|
|
292
|
+
// If pushTx fails (e.g., Xverse/Leather), try extracting raw tx
|
|
293
|
+
// The signed PSBT may need extraction of the final transaction
|
|
294
|
+
const rawTx = this.extractRawTransaction(signedPsbt)
|
|
295
|
+
if (rawTx) {
|
|
296
|
+
txid = await this.provider.pushTx(rawTx)
|
|
297
|
+
} else {
|
|
298
|
+
throw pushError
|
|
299
|
+
}
|
|
300
|
+
}
|
|
286
301
|
|
|
287
302
|
return {
|
|
288
303
|
txHash: ('0x' + txid) as HexString,
|
|
@@ -306,6 +321,44 @@ export class BitcoinWalletAdapter extends BaseWalletAdapter {
|
|
|
306
321
|
}
|
|
307
322
|
}
|
|
308
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Extract raw transaction from a finalized PSBT
|
|
326
|
+
*
|
|
327
|
+
* A finalized PSBT has all inputs signed and can be converted to a raw transaction.
|
|
328
|
+
* This is a simplified extraction - full implementation would use bitcoinjs-lib.
|
|
329
|
+
*
|
|
330
|
+
* @param psbtHex - Hex-encoded finalized PSBT
|
|
331
|
+
* @returns Raw transaction hex or undefined if extraction fails
|
|
332
|
+
*/
|
|
333
|
+
private extractRawTransaction(psbtHex: string): string | undefined {
|
|
334
|
+
try {
|
|
335
|
+
// PSBT format: magic bytes (4) + separator (1) + key-value pairs
|
|
336
|
+
// After finalization, the PSBT contains the final scriptSig/witness for each input
|
|
337
|
+
// Full extraction requires parsing the PSBT structure
|
|
338
|
+
|
|
339
|
+
// For browser environments, we rely on the wallet to finalize properly
|
|
340
|
+
// This is a fallback that checks if the hex looks like a raw transaction
|
|
341
|
+
// Raw transactions start with version (4 bytes), typically 01000000 or 02000000
|
|
342
|
+
|
|
343
|
+
const cleanHex = psbtHex.startsWith('0x') ? psbtHex.slice(2) : psbtHex
|
|
344
|
+
|
|
345
|
+
// Check if it's already a raw transaction (not a PSBT)
|
|
346
|
+
// PSBT magic: 70736274ff (psbt + 0xff)
|
|
347
|
+
if (!cleanHex.startsWith('70736274ff')) {
|
|
348
|
+
// Might already be a raw transaction
|
|
349
|
+
if (cleanHex.startsWith('01000000') || cleanHex.startsWith('02000000')) {
|
|
350
|
+
return cleanHex
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Cannot extract without proper PSBT parsing library
|
|
355
|
+
// Return undefined to let the caller handle the error
|
|
356
|
+
return undefined
|
|
357
|
+
} catch {
|
|
358
|
+
return undefined
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
309
362
|
/**
|
|
310
363
|
* Get native BTC balance
|
|
311
364
|
*/
|
|
@@ -331,8 +384,10 @@ export class BitcoinWalletAdapter extends BaseWalletAdapter {
|
|
|
331
384
|
/**
|
|
332
385
|
* Get token balance
|
|
333
386
|
*
|
|
334
|
-
* For Bitcoin, this
|
|
335
|
-
*
|
|
387
|
+
* For Bitcoin, this returns BRC-20 token balances.
|
|
388
|
+
* Uses Unisat Open API for balance queries.
|
|
389
|
+
*
|
|
390
|
+
* @param asset - Asset with token symbol (e.g., 'ordi', 'sats')
|
|
336
391
|
*/
|
|
337
392
|
async getTokenBalance(asset: Asset): Promise<bigint> {
|
|
338
393
|
this.requireConnected()
|
|
@@ -344,9 +399,98 @@ export class BitcoinWalletAdapter extends BaseWalletAdapter {
|
|
|
344
399
|
)
|
|
345
400
|
}
|
|
346
401
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
402
|
+
if (!this._address) {
|
|
403
|
+
throw new WalletError('No address connected', WalletErrorCode.NOT_CONNECTED)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
// Query BRC-20 balance from indexer API
|
|
408
|
+
const balance = await this.queryBrc20Balance(this._address, asset.symbol)
|
|
409
|
+
return balance
|
|
410
|
+
} catch (error) {
|
|
411
|
+
// Return 0 if query fails (token might not exist or API unavailable)
|
|
412
|
+
console.warn(`Failed to query BRC-20 balance for ${asset.symbol}:`, error)
|
|
413
|
+
return 0n
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Query BRC-20 token balance from indexer API
|
|
419
|
+
*
|
|
420
|
+
* Uses Unisat Open API as the default indexer.
|
|
421
|
+
* Can be overridden by setting brc20ApiUrl in config.
|
|
422
|
+
*
|
|
423
|
+
* @param address - Bitcoin address
|
|
424
|
+
* @param ticker - BRC-20 token ticker (e.g., 'ordi', 'sats')
|
|
425
|
+
*/
|
|
426
|
+
private async queryBrc20Balance(address: string, ticker: string): Promise<bigint> {
|
|
427
|
+
// Unisat Open API endpoint for BRC-20 balances
|
|
428
|
+
// API docs: https://open-api.unisat.io/swagger.html
|
|
429
|
+
const apiUrl = `https://open-api.unisat.io/v1/indexer/address/${address}/brc20/${ticker.toLowerCase()}/info`
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
const response = await fetch(apiUrl, {
|
|
433
|
+
method: 'GET',
|
|
434
|
+
headers: {
|
|
435
|
+
'Content-Type': 'application/json',
|
|
436
|
+
// Note: Production usage may require API key
|
|
437
|
+
// 'Authorization': `Bearer ${apiKey}`
|
|
438
|
+
},
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
if (!response.ok) {
|
|
442
|
+
if (response.status === 404) {
|
|
443
|
+
// Token not found or no balance
|
|
444
|
+
return 0n
|
|
445
|
+
}
|
|
446
|
+
throw new Error(`API returned ${response.status}`)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const data = await response.json()
|
|
450
|
+
|
|
451
|
+
// Unisat API response format:
|
|
452
|
+
// { code: 0, msg: 'ok', data: { ticker, overallBalance, transferableBalance, availableBalance } }
|
|
453
|
+
if (data.code === 0 && data.data) {
|
|
454
|
+
// availableBalance is the spendable balance (not in pending transfers)
|
|
455
|
+
const balance = data.data.availableBalance || data.data.overallBalance || '0'
|
|
456
|
+
// BRC-20 balances are strings, convert to bigint
|
|
457
|
+
// Note: BRC-20 uses 18 decimal places by default
|
|
458
|
+
return BigInt(balance.replace('.', '').padEnd(18, '0'))
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return 0n
|
|
462
|
+
} catch (error) {
|
|
463
|
+
// Fallback: try alternative API or return 0
|
|
464
|
+
return this.queryBrc20BalanceFallback(address, ticker)
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Fallback BRC-20 balance query using Hiro/Ordinals API
|
|
470
|
+
*/
|
|
471
|
+
private async queryBrc20BalanceFallback(address: string, ticker: string): Promise<bigint> {
|
|
472
|
+
try {
|
|
473
|
+
// Hiro Ordinals API
|
|
474
|
+
const apiUrl = `https://api.hiro.so/ordinals/v1/brc-20/balances/${address}`
|
|
475
|
+
|
|
476
|
+
const response = await fetch(apiUrl)
|
|
477
|
+
if (!response.ok) return 0n
|
|
478
|
+
|
|
479
|
+
const data = await response.json()
|
|
480
|
+
|
|
481
|
+
// Find the specific ticker in results
|
|
482
|
+
const tokenBalance = data.results?.find(
|
|
483
|
+
(t: { ticker: string }) => t.ticker.toLowerCase() === ticker.toLowerCase()
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
if (tokenBalance?.overall_balance) {
|
|
487
|
+
return BigInt(tokenBalance.overall_balance)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return 0n
|
|
491
|
+
} catch {
|
|
492
|
+
return 0n
|
|
493
|
+
}
|
|
350
494
|
}
|
|
351
495
|
|
|
352
496
|
/**
|