@hazbase/simplicity 0.3.0 → 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/README.md CHANGED
@@ -193,6 +193,22 @@ RECEIVABLE_OUTPUT_BINDING_MODE=script-bound npm run e2e:receivable-testnet
193
193
  RECEIVABLE_OUTPUT_BINDING_MODE=descriptor-bound npm run e2e:receivable-testnet
194
194
  ```
195
195
 
196
+ Liquid x402 buyer smoke test:
197
+
198
+ ```bash
199
+ npm install --no-save lwk_node@^0.17.1
200
+ npm run e2e:x402-lwk-address -- --generate
201
+ npm run e2e:x402-lwk-buyer -- https://share.example/v/slug --dry-run
202
+ LIQUID_X402_E2E_MNEMONIC="..." npm run e2e:x402-lwk-buyer -- https://share.example/v/slug --no-submit
203
+ LIQUID_X402_E2E_MNEMONIC="..." npm run e2e:x402-lwk-buyer -- https://share.example/v/slug --output unlocked.bin
204
+ ```
205
+
206
+ The address script derives a confidential Liquid receive address for funding a
207
+ test buyer wallet. The buyer script then uses LWK locally to scan, build, sign,
208
+ and finalize a Liquid PSET, then sends the resulting `X-PAYMENT` header back to
209
+ the protected URL. `--dry-run` only inspects the 402 requirements, while
210
+ `--no-submit` signs a payment without broadcasting through the seller backend.
211
+
196
212
  These commands are useful both as reproducible checks and as examples of the SDK's current validated surface.
197
213
 
198
214
  ## Where To Go Next
@@ -22,6 +22,9 @@ export declare class SimplicityClient {
22
22
  encodePayment: typeof liquidX402.encodeLiquidXPayment;
23
23
  decodePayment: typeof liquidX402.decodeLiquidXPayment;
24
24
  preparePsetPayment: (input: liquidX402.LiquidX402PreparePsetPaymentInput) => Promise<liquidX402.LiquidX402PreparePsetPaymentResult>;
25
+ buildPaymentFromPset: typeof liquidX402.buildLiquidX402PaymentFromPset;
26
+ prepareLwkWasmPayment: typeof liquidX402.prepareLiquidX402LwkWasmPayment;
27
+ deriveLwkWasmAddress: typeof liquidX402.deriveLiquidX402LwkWasmAddress;
25
28
  verify: (input: liquidX402.LiquidX402VerifyPaymentInput) => Promise<liquidX402.LiquidX402VerifyPaymentResult>;
26
29
  settle: (input: liquidX402.LiquidX402SettlePaymentInput) => Promise<liquidX402.LiquidX402SettlePaymentResult>;
27
30
  };
@@ -71,6 +71,9 @@ class SimplicityClient {
71
71
  encodePayment: liquidX402.encodeLiquidXPayment,
72
72
  decodePayment: liquidX402.decodeLiquidXPayment,
73
73
  preparePsetPayment: async (input) => liquidX402.prepareLiquidX402PsetPayment(this.rpc, input),
74
+ buildPaymentFromPset: liquidX402.buildLiquidX402PaymentFromPset,
75
+ prepareLwkWasmPayment: liquidX402.prepareLiquidX402LwkWasmPayment,
76
+ deriveLwkWasmAddress: liquidX402.deriveLiquidX402LwkWasmAddress,
74
77
  verify: async (input) => liquidX402.verifyLiquidX402Payment(this.rpc, input),
75
78
  settle: async (input) => liquidX402.settleLiquidX402Payment(this.rpc, input),
76
79
  },
@@ -75,6 +75,49 @@ export interface LiquidX402PreparePsetPaymentResult {
75
75
  psetBase64: string;
76
76
  summaryHash: string;
77
77
  }
78
+ export interface LiquidX402BuildPaymentFromPsetInput {
79
+ requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
80
+ psetBase64: string;
81
+ decoded?: DecodePsbtResult;
82
+ payer?: string;
83
+ summaryHash?: string;
84
+ }
85
+ export interface LiquidX402PrepareLwkWasmPaymentInput {
86
+ requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
87
+ mnemonic: string;
88
+ payer?: string;
89
+ esploraUrl?: string;
90
+ waterfalls?: boolean;
91
+ concurrency?: number;
92
+ utxoOnly?: boolean;
93
+ scan?: boolean;
94
+ scanToIndex?: number;
95
+ feeRate?: number;
96
+ finalize?: boolean;
97
+ lwk?: LiquidX402LwkWasmModule;
98
+ importLwk?: () => Promise<LiquidX402LwkWasmModule>;
99
+ }
100
+ export type LiquidX402PrepareLwkWasmPaymentResult = LiquidX402PreparePsetPaymentResult & {
101
+ descriptor: string;
102
+ dwid?: string;
103
+ };
104
+ export interface LiquidX402DeriveLwkWasmAddressInput {
105
+ mnemonic: string;
106
+ network?: LiquidX402Network;
107
+ index?: number;
108
+ lwk?: LiquidX402LwkWasmModule;
109
+ importLwk?: () => Promise<LiquidX402LwkWasmModule>;
110
+ }
111
+ export interface LiquidX402DeriveLwkWasmAddressResult {
112
+ network: LiquidX402Network;
113
+ address: string;
114
+ unconfidentialAddress?: string;
115
+ index: number;
116
+ isBlinded?: boolean;
117
+ descriptor: string;
118
+ dwid?: string;
119
+ policyAsset?: string;
120
+ }
78
121
  export interface LiquidX402VerifyPaymentInput {
79
122
  requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
80
123
  paymentPayload: LiquidX402PaymentPayload | Record<string, unknown>;
@@ -100,7 +143,7 @@ export interface LiquidX402SettlePaymentResult {
100
143
  rawTxHex?: string;
101
144
  summaryHash?: string;
102
145
  }
103
- type DecodePsbtResult = {
146
+ export type DecodePsbtResult = {
104
147
  fees?: Record<string, number>;
105
148
  outputs?: Array<{
106
149
  amount?: number;
@@ -111,6 +154,24 @@ type DecodePsbtResult = {
111
154
  };
112
155
  }>;
113
156
  };
157
+ export type LiquidX402LwkWasmModule = {
158
+ Address: {
159
+ new (value: string): any;
160
+ parse?: (value: string, network: any) => any;
161
+ };
162
+ AssetId: {
163
+ new (value: string): any;
164
+ fromString?: (value: string) => any;
165
+ };
166
+ EsploraClient: new (network: any, url: string, waterfalls: boolean, concurrency: number, utxoOnly: boolean) => any;
167
+ Mnemonic: new (value: string) => any;
168
+ Network: {
169
+ mainnet: () => any;
170
+ testnet: () => any;
171
+ };
172
+ Signer: new (mnemonic: any, network: any) => any;
173
+ Wollet: new (network: any, descriptor: any) => any;
174
+ };
114
175
  export declare const LIQUID_X402_ASSETS: Record<LiquidX402Network, Record<LiquidX402AssetKey, LiquidX402Asset>>;
115
176
  export declare function listLiquidX402Assets(network?: LiquidX402Network): LiquidX402Asset[];
116
177
  export declare function resolveLiquidX402Asset(asset: LiquidX402AssetKey | string, network?: LiquidX402Network): LiquidX402Asset;
@@ -118,8 +179,10 @@ export declare function buildLiquidX402Requirements(input: LiquidX402Requirement
118
179
  export declare function encodeLiquidXPayment(payload: LiquidX402PaymentPayload): string;
119
180
  export declare function decodeLiquidXPayment(raw: string): LiquidX402PaymentPayload | null;
120
181
  export declare function prepareLiquidX402PsetPayment(rpc: ElementsRpcClient, input: LiquidX402PreparePsetPaymentInput): Promise<LiquidX402PreparePsetPaymentResult>;
182
+ export declare function buildLiquidX402PaymentFromPset(input: LiquidX402BuildPaymentFromPsetInput): LiquidX402PreparePsetPaymentResult;
183
+ export declare function prepareLiquidX402LwkWasmPayment(input: LiquidX402PrepareLwkWasmPaymentInput): Promise<LiquidX402PrepareLwkWasmPaymentResult>;
184
+ export declare function deriveLiquidX402LwkWasmAddress(input: LiquidX402DeriveLwkWasmAddressInput): Promise<LiquidX402DeriveLwkWasmAddressResult>;
121
185
  export declare function verifyLiquidX402Payment(rpc: ElementsRpcClient | null, input: LiquidX402VerifyPaymentInput): Promise<LiquidX402VerifyPaymentResult>;
122
186
  export declare function settleLiquidX402Payment(rpc: ElementsRpcClient, input: LiquidX402SettlePaymentInput): Promise<LiquidX402SettlePaymentResult>;
123
187
  export declare function verifyLiquidX402PayloadFields(requirements: LiquidX402PaymentRequirements | Record<string, unknown>, payload: LiquidX402PaymentPayload | Record<string, unknown>, now?: Date): LiquidX402VerifyPaymentResult;
124
- export declare function buildLiquidPsetSummaryHash(decoded: DecodePsbtResult, requirements: LiquidX402PaymentRequirements | Record<string, unknown>): string;
125
- export {};
188
+ export declare function buildLiquidPsetSummaryHash(_decoded: DecodePsbtResult, requirements: LiquidX402PaymentRequirements | Record<string, unknown>): string;
@@ -7,6 +7,9 @@ exports.buildLiquidX402Requirements = buildLiquidX402Requirements;
7
7
  exports.encodeLiquidXPayment = encodeLiquidXPayment;
8
8
  exports.decodeLiquidXPayment = decodeLiquidXPayment;
9
9
  exports.prepareLiquidX402PsetPayment = prepareLiquidX402PsetPayment;
10
+ exports.buildLiquidX402PaymentFromPset = buildLiquidX402PaymentFromPset;
11
+ exports.prepareLiquidX402LwkWasmPayment = prepareLiquidX402LwkWasmPayment;
12
+ exports.deriveLiquidX402LwkWasmAddress = deriveLiquidX402LwkWasmAddress;
10
13
  exports.verifyLiquidX402Payment = verifyLiquidX402Payment;
11
14
  exports.settleLiquidX402Payment = settleLiquidX402Payment;
12
15
  exports.verifyLiquidX402PayloadFields = verifyLiquidX402PayloadFields;
@@ -18,13 +21,15 @@ exports.LIQUID_X402_DEFAULT_NETWORK = "liquidtestnet";
18
21
  exports.LIQUID_X402_DEFAULT_TIMEOUT_SECONDS = 60;
19
22
  const LIQUID_TESTNET_USDT_ASSET_ID = "b612eb46313a2cd6ebabd8b7a8eed5696e29898b87a43bff41c94f51acef9d73";
20
23
  const LIQUID_MAINNET_USDT_ASSET_ID = "ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2";
24
+ const LIQUID_TESTNET_LBTC_ASSET_ID = "144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49";
25
+ const LIQUID_MAINNET_LBTC_ASSET_ID = "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d";
21
26
  exports.LIQUID_X402_ASSETS = {
22
27
  liquidtestnet: {
23
28
  lbtc: {
24
29
  key: "lbtc",
25
30
  label: "Liquid Bitcoin",
26
31
  network: "liquidtestnet",
27
- assetId: "bitcoin",
32
+ assetId: LIQUID_TESTNET_LBTC_ASSET_ID,
28
33
  decimals: 8,
29
34
  isNative: true,
30
35
  },
@@ -42,7 +47,7 @@ exports.LIQUID_X402_ASSETS = {
42
47
  key: "lbtc",
43
48
  label: "Liquid Bitcoin",
44
49
  network: "liquidv1",
45
- assetId: "bitcoin",
50
+ assetId: LIQUID_MAINNET_LBTC_ASSET_ID,
46
51
  decimals: 8,
47
52
  isNative: true,
48
53
  },
@@ -161,6 +166,100 @@ async function prepareLiquidX402PsetPayment(rpc, input) {
161
166
  summaryHash,
162
167
  };
163
168
  }
169
+ function buildLiquidX402PaymentFromPset(input) {
170
+ const requirements = coerceRequirements(input.requirements);
171
+ const psetBase64 = String(input.psetBase64 ?? "").trim();
172
+ if (!psetBase64)
173
+ throw new Error("psetBase64 is required");
174
+ const summaryHash = input.summaryHash ?? buildLiquidPsetSummaryHash(input.decoded ?? {}, requirements);
175
+ const paymentPayload = {
176
+ scheme: exports.LIQUID_X402_SCHEME,
177
+ network: requirements.network,
178
+ paymentRequestId: requirements.extra.paymentRequestId,
179
+ asset: requirements.extra.asset,
180
+ assetId: requirements.asset,
181
+ amountAtomic: requirements.maxAmountRequired,
182
+ payTo: requirements.payTo,
183
+ psetBase64,
184
+ summaryHash,
185
+ ...(input.payer ? { payer: input.payer } : {}),
186
+ expiresAt: requirements.extra.expiresAt,
187
+ };
188
+ return {
189
+ paymentPayload,
190
+ xPayment: encodeLiquidXPayment(paymentPayload),
191
+ psetBase64,
192
+ summaryHash,
193
+ };
194
+ }
195
+ async function prepareLiquidX402LwkWasmPayment(input) {
196
+ const requirements = coerceRequirements(input.requirements);
197
+ ensureLwkWasmNodeTimerCompat();
198
+ const lwk = input.lwk ?? await loadLwkWasm(input.importLwk);
199
+ const network = requirements.network === "liquidv1" ? lwk.Network.mainnet() : lwk.Network.testnet();
200
+ const mnemonic = new lwk.Mnemonic(input.mnemonic);
201
+ const signer = new lwk.Signer(mnemonic, network);
202
+ const descriptor = signer.wpkhSlip77Descriptor();
203
+ const wollet = new lwk.Wollet(network, descriptor);
204
+ const client = input.esploraUrl
205
+ ? new lwk.EsploraClient(network, input.esploraUrl, input.waterfalls ?? false, input.concurrency ?? 4, input.utxoOnly ?? false)
206
+ : network.defaultEsploraClient();
207
+ if (input.scan !== false) {
208
+ const update = input.scanToIndex !== undefined
209
+ ? await client.fullScanToIndex(wollet, input.scanToIndex)
210
+ : await client.fullScan(wollet);
211
+ if (update)
212
+ wollet.applyUpdate(update);
213
+ }
214
+ let builder = network.txBuilder();
215
+ if (input.feeRate !== undefined)
216
+ builder = builder.feeRate(input.feeRate);
217
+ const recipient = parseLwkAddress(lwk, requirements.payTo, network);
218
+ const amount = BigInt(requirements.maxAmountRequired);
219
+ if (requirements.extra.asset === "lbtc") {
220
+ builder = builder.addLbtcRecipient(recipient, amount);
221
+ }
222
+ else {
223
+ builder = builder.addRecipient(recipient, amount, parseLwkAssetId(lwk, requirements.asset));
224
+ }
225
+ const unsigned = builder.finish(wollet);
226
+ const signed = signer.sign(unsigned);
227
+ const pset = input.finalize === false ? signed : wollet.finalize(signed);
228
+ const decoded = decodeLwkPsetForX402(pset, wollet);
229
+ const payment = buildLiquidX402PaymentFromPset({
230
+ requirements,
231
+ psetBase64: pset.toString(),
232
+ decoded,
233
+ payer: input.payer,
234
+ });
235
+ return {
236
+ ...payment,
237
+ descriptor: descriptor.toString(),
238
+ ...(typeof wollet.dwid === "function" ? { dwid: wollet.dwid() } : {}),
239
+ };
240
+ }
241
+ async function deriveLiquidX402LwkWasmAddress(input) {
242
+ const networkKey = normalizeNetwork(input.network ?? exports.LIQUID_X402_DEFAULT_NETWORK);
243
+ ensureLwkWasmNodeTimerCompat();
244
+ const lwk = input.lwk ?? await loadLwkWasm(input.importLwk);
245
+ const network = networkKey === "liquidv1" ? lwk.Network.mainnet() : lwk.Network.testnet();
246
+ const mnemonic = new lwk.Mnemonic(input.mnemonic);
247
+ const signer = new lwk.Signer(mnemonic, network);
248
+ const descriptor = signer.wpkhSlip77Descriptor();
249
+ const wollet = new lwk.Wollet(network, descriptor);
250
+ const addressResult = wollet.address(input.index ?? null);
251
+ const address = addressResult.address();
252
+ return {
253
+ network: networkKey,
254
+ address: address.toString(),
255
+ ...(typeof address.toUnconfidential === "function" ? { unconfidentialAddress: address.toUnconfidential().toString() } : {}),
256
+ index: typeof addressResult.index === "function" ? addressResult.index() : input.index ?? 0,
257
+ ...(typeof address.isBlinded === "function" ? { isBlinded: address.isBlinded() } : {}),
258
+ descriptor: descriptor.toString(),
259
+ ...(typeof wollet.dwid === "function" ? { dwid: wollet.dwid() } : {}),
260
+ ...(typeof network.policyAsset === "function" ? { policyAsset: network.policyAsset().toString() } : {}),
261
+ };
262
+ }
164
263
  async function verifyLiquidX402Payment(rpc, input) {
165
264
  const requirements = coerceRequirements(input.requirements);
166
265
  const payload = coercePaymentPayload(input.paymentPayload);
@@ -257,7 +356,7 @@ function verifyLiquidX402PayloadFields(requirements, payload, now = new Date())
257
356
  }
258
357
  return { ...base, isValid: true };
259
358
  }
260
- function buildLiquidPsetSummaryHash(decoded, requirements) {
359
+ function buildLiquidPsetSummaryHash(_decoded, requirements) {
261
360
  const req = coerceRequirements(requirements);
262
361
  return sha256Hex(stableStringify({
263
362
  scheme: exports.LIQUID_X402_SCHEME,
@@ -267,8 +366,14 @@ function buildLiquidPsetSummaryHash(decoded, requirements) {
267
366
  assetId: req.asset,
268
367
  amountAtomic: req.maxAmountRequired,
269
368
  payTo: req.payTo,
270
- outputs: normalizeDecodedOutputs(decoded),
271
- fees: decoded.fees ?? {},
369
+ expectedOutput: {
370
+ amountAtomic: req.maxAmountRequired,
371
+ assetId: req.asset,
372
+ payTo: req.payTo,
373
+ },
374
+ feeAsset: req.extra.feeAsset,
375
+ feeAssetId: req.extra.feeAssetId ?? exports.LIQUID_X402_ASSETS[req.network].lbtc.assetId,
376
+ maxFeeSat: req.extra.maxFeeSat ?? null,
272
377
  }));
273
378
  }
274
379
  function decodedContainsExpectedOutput(decoded, requirements) {
@@ -278,14 +383,6 @@ function decodedContainsExpectedOutput(decoded, requirements) {
278
383
  Math.round(expectedAmount * 10 ** requirements.extra.decimals) &&
279
384
  String(output.script?.address ?? "") === requirements.payTo));
280
385
  }
281
- function normalizeDecodedOutputs(decoded) {
282
- return (decoded.outputs ?? []).map((output) => ({
283
- amount: output.amount ?? null,
284
- asset: output.asset ?? null,
285
- address: output.script?.address ?? null,
286
- scriptPubKey: output.script?.hex ?? null,
287
- }));
288
- }
289
386
  function coerceRequirements(value) {
290
387
  if (!value || typeof value !== "object" || Array.isArray(value))
291
388
  throw new Error("requirements must be an object");
@@ -293,6 +390,8 @@ function coerceRequirements(value) {
293
390
  const extra = raw.extra && typeof raw.extra === "object" && !Array.isArray(raw.extra) ? raw.extra : {};
294
391
  const network = normalizeNetwork(raw.network);
295
392
  const asset = resolveLiquidX402Asset(extra.asset ?? raw.asset, network);
393
+ const rawAssetId = String(raw.asset ?? extra.assetId ?? asset.assetId);
394
+ const assetId = isLiquidAssetAlias(rawAssetId) ? asset.assetId : rawAssetId;
296
395
  return {
297
396
  scheme: exports.LIQUID_X402_SCHEME,
298
397
  network,
@@ -302,11 +401,11 @@ function coerceRequirements(value) {
302
401
  mimeType: String(raw.mimeType ?? ""),
303
402
  payTo: String(raw.payTo ?? ""),
304
403
  maxTimeoutSeconds: normalizeTimeout(raw.maxTimeoutSeconds),
305
- asset: String(raw.asset ?? extra.assetId ?? asset.assetId),
404
+ asset: assetId,
306
405
  extra: {
307
406
  paymentRequestId: String(extra.paymentRequestId ?? raw.paymentRequestId ?? ""),
308
407
  asset: asset.key,
309
- assetId: String(extra.assetId ?? raw.asset ?? asset.assetId),
408
+ assetId,
310
409
  decimals: Number(extra.decimals ?? asset.decimals),
311
410
  expiresAt: String(extra.expiresAt ?? ""),
312
411
  feeAsset: "lbtc",
@@ -326,6 +425,56 @@ function decodedFeeWithinCap(decoded, requirements) {
326
425
  }, 0n);
327
426
  return feeSat <= maxFeeSat;
328
427
  }
428
+ async function loadLwkWasm(importer) {
429
+ ensureLwkWasmNodeTimerCompat();
430
+ if (importer)
431
+ return importer();
432
+ const dynamicImport = new Function("specifier", "return import(specifier)");
433
+ const failures = [];
434
+ for (const specifier of ["lwk_wasm", "lwk_node"]) {
435
+ try {
436
+ const imported = await dynamicImport(specifier);
437
+ return imported.default ?? imported;
438
+ }
439
+ catch (error) {
440
+ failures.push(`${specifier}: ${error instanceof Error ? error.message : String(error)}`);
441
+ }
442
+ }
443
+ throw new Error("lwk_wasm or lwk_node is required for lightweight Liquid x402 payments. " +
444
+ "Install one of them in the consuming app and bundle its WASM artifact when needed. " +
445
+ `Import failed: ${failures.join("; ")}`);
446
+ }
447
+ function ensureLwkWasmNodeTimerCompat() {
448
+ const scope = globalThis;
449
+ if (typeof scope.window === "undefined" && typeof scope.setTimeout === "function") {
450
+ scope.window = scope;
451
+ }
452
+ if (typeof scope.Window === "undefined" && typeof scope.window !== "undefined") {
453
+ scope.Window = Object;
454
+ }
455
+ }
456
+ function parseLwkAddress(lwk, address, network) {
457
+ return typeof lwk.Address.parse === "function" ? lwk.Address.parse(address, network) : new lwk.Address(address);
458
+ }
459
+ function parseLwkAssetId(lwk, assetId) {
460
+ return typeof lwk.AssetId.fromString === "function" ? lwk.AssetId.fromString(assetId) : new lwk.AssetId(assetId);
461
+ }
462
+ function decodeLwkPsetForX402(pset, wollet) {
463
+ const details = wollet.psetDetails(pset);
464
+ const balance = details.balance();
465
+ const recipients = typeof balance.recipients === "function" ? balance.recipients() : [];
466
+ const fee = typeof balance.fee === "function" ? balance.fee() : 0n;
467
+ return {
468
+ fees: { bitcoin: atomicToDecimalNumber(String(fee), 8) },
469
+ outputs: recipients.map((recipient) => ({
470
+ amount: recipient.value() === undefined ? undefined : atomicToDecimalNumber(String(recipient.value()), 8),
471
+ asset: recipient.asset()?.toString(),
472
+ script: {
473
+ address: recipient.address()?.toString(),
474
+ },
475
+ })),
476
+ };
477
+ }
329
478
  function coercePaymentPayload(value) {
330
479
  if (!value || typeof value !== "object" || Array.isArray(value))
331
480
  throw new Error("payment payload must be an object");
@@ -352,12 +501,26 @@ function normalizeNetwork(input) {
352
501
  }
353
502
  function normalizeAssetKey(input) {
354
503
  const value = String(input ?? "").trim().toLowerCase();
355
- if (value === "lbtc" || value === "bitcoin")
504
+ if (value === "lbtc" || value === "bitcoin" || value === LIQUID_TESTNET_LBTC_ASSET_ID || value === LIQUID_MAINNET_LBTC_ASSET_ID) {
356
505
  return "lbtc";
357
- if (value === "usdt" || value === "usdt-liquid" || value === "tether")
506
+ }
507
+ if (value === "usdt" ||
508
+ value === "usdt-liquid" ||
509
+ value === "tether" ||
510
+ value === LIQUID_TESTNET_USDT_ASSET_ID ||
511
+ value === LIQUID_MAINNET_USDT_ASSET_ID) {
358
512
  return "usdt";
513
+ }
359
514
  throw new Error(`unsupported Liquid x402 asset: ${String(input)}`);
360
515
  }
516
+ function isLiquidAssetAlias(input) {
517
+ const value = String(input ?? "").trim().toLowerCase();
518
+ return value === "lbtc" ||
519
+ value === "bitcoin" ||
520
+ value === "usdt" ||
521
+ value === "usdt-liquid" ||
522
+ value === "tether";
523
+ }
361
524
  function normalizeAmountAtomic(input) {
362
525
  const raw = typeof input === "bigint" ? input.toString() : String(input ?? "").trim();
363
526
  if (!/^\d+$/u.test(raw))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hazbase/simplicity",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "An SDK for Simplicity on Liquid",
5
5
  "author": "IndieSquare Inc <info@hazbase.com>",
6
6
  "keywords": [
@@ -50,6 +50,8 @@
50
50
  "e2e:receivable-local": "npm run build && node scripts/e2e-receivable-local.mjs",
51
51
  "e2e:receivable-testnet": "npm run build && node scripts/e2e-receivable-testnet.mjs",
52
52
  "e2e:receivable-consumer": "npm run build && node scripts/e2e-receivable-consumer.mjs",
53
+ "e2e:x402-lwk-address": "npm run build && node scripts/e2e-x402-lwk-address.mjs",
54
+ "e2e:x402-lwk-buyer": "npm run build && node scripts/e2e-x402-lwk-buyer.mjs",
53
55
  "e2e:simplicity-relayer": "npm run build && node scripts/e2e-simplicity-relayer.mjs",
54
56
  "prepublishOnly": "npm run build"
55
57
  },