@hazbase/simplicity 0.4.3 → 0.4.4

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
@@ -175,6 +175,15 @@ testnet issuer or deployment uses a different USDt asset id, set
175
175
  payment requirements will preserve that asset id instead of replacing it with
176
176
  the SDK's default registry id.
177
177
 
178
+ For policy-locked Liquid RWA positions, redemption can be represented as a
179
+ Liquid x402 request with `extra.redemptionSource.type:
180
+ "policy_locked_position"`. Wallets can use
181
+ `buildLiquidPolicyLockedRedemptionPaymentFromProposal(...)` when a service has
182
+ already prepared the Simplicity policy-spend PSET proposal. The helper binds the
183
+ custom RWA asset, policy position, vault output, expiry, and summary hash into
184
+ the `X-PAYMENT` payload, and rejects proposals that still require holder-side
185
+ Simplicity witness construction.
186
+
178
187
  Typical entrypoints:
179
188
  - `sdk.rwaDvp.definePurchase(...)`
180
189
  - `sdk.rwaDvp.buildPaymentRequirements(...)`
@@ -24,6 +24,7 @@ export declare class SimplicityClient {
24
24
  decodePayment: typeof liquidX402.decodeLiquidXPayment;
25
25
  preparePsetPayment: (input: liquidX402.LiquidX402PreparePsetPaymentInput) => Promise<liquidX402.LiquidX402PreparePsetPaymentResult>;
26
26
  buildPaymentFromPset: typeof liquidX402.buildLiquidX402PaymentFromPset;
27
+ buildPolicyLockedRedemptionPaymentFromProposal: typeof liquidX402.buildLiquidPolicyLockedRedemptionPaymentFromProposal;
27
28
  prepareLwkWasmPayment: typeof liquidX402.prepareLiquidX402LwkWasmPayment;
28
29
  deriveLwkWasmAddress: typeof liquidX402.deriveLiquidX402LwkWasmAddress;
29
30
  verify: (input: liquidX402.LiquidX402VerifyPaymentInput) => Promise<liquidX402.LiquidX402VerifyPaymentResult>;
@@ -74,6 +74,7 @@ class SimplicityClient {
74
74
  decodePayment: liquidX402.decodeLiquidXPayment,
75
75
  preparePsetPayment: async (input) => liquidX402.prepareLiquidX402PsetPayment(this.rpc, input),
76
76
  buildPaymentFromPset: liquidX402.buildLiquidX402PaymentFromPset,
77
+ buildPolicyLockedRedemptionPaymentFromProposal: liquidX402.buildLiquidPolicyLockedRedemptionPaymentFromProposal,
77
78
  prepareLwkWasmPayment: liquidX402.prepareLiquidX402LwkWasmPayment,
78
79
  deriveLwkWasmAddress: liquidX402.deriveLiquidX402LwkWasmAddress,
79
80
  verify: async (input) => liquidX402.verifyLiquidX402Payment(this.rpc, input),
@@ -55,7 +55,7 @@ export interface LiquidX402PaymentPayload {
55
55
  scheme: typeof LIQUID_X402_SCHEME;
56
56
  network: LiquidX402Network;
57
57
  paymentRequestId: string;
58
- asset: LiquidX402AssetKey;
58
+ asset: LiquidX402AssetKey | "custom" | string;
59
59
  assetId: string;
60
60
  amountAtomic: string;
61
61
  payTo: string;
@@ -84,6 +84,25 @@ export interface LiquidX402BuildPaymentFromPsetInput {
84
84
  payer?: string;
85
85
  summaryHash?: string;
86
86
  }
87
+ export interface LiquidPolicyLockedRedemptionSpendProposal {
88
+ mode?: "service_prepared_policy_spend_v1" | string;
89
+ psetBase64?: string;
90
+ summaryHash?: string;
91
+ holderSignatureRequired?: boolean;
92
+ metadata?: Record<string, unknown>;
93
+ }
94
+ export interface LiquidPolicyLockedRedemptionPaymentFromProposalInput {
95
+ requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
96
+ proposal?: LiquidPolicyLockedRedemptionSpendProposal | Record<string, unknown>;
97
+ proposalPsetBase64?: string;
98
+ summaryHash?: string;
99
+ payer?: string;
100
+ metadata?: Record<string, unknown>;
101
+ }
102
+ export type LiquidPolicyLockedRedemptionPaymentFromProposalResult = LiquidX402PreparePsetPaymentResult & {
103
+ proposalMode?: string;
104
+ redemptionSource: Record<string, unknown>;
105
+ };
87
106
  export interface LiquidX402PrepareLwkWasmPaymentInput {
88
107
  requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
89
108
  mnemonic: string;
@@ -186,6 +205,7 @@ export declare function encodeLiquidXPayment(payload: LiquidX402PaymentPayload):
186
205
  export declare function decodeLiquidXPayment(raw: string): LiquidX402PaymentPayload | null;
187
206
  export declare function prepareLiquidX402PsetPayment(rpc: ElementsRpcClient, input: LiquidX402PreparePsetPaymentInput): Promise<LiquidX402PreparePsetPaymentResult>;
188
207
  export declare function buildLiquidX402PaymentFromPset(input: LiquidX402BuildPaymentFromPsetInput): LiquidX402PreparePsetPaymentResult;
208
+ export declare function buildLiquidPolicyLockedRedemptionPaymentFromProposal(input: LiquidPolicyLockedRedemptionPaymentFromProposalInput): LiquidPolicyLockedRedemptionPaymentFromProposalResult;
189
209
  export declare function prepareLiquidX402LwkWasmPayment(input: LiquidX402PrepareLwkWasmPaymentInput): Promise<LiquidX402PrepareLwkWasmPaymentResult>;
190
210
  export declare function deriveLiquidX402LwkWasmAddress(input: LiquidX402DeriveLwkWasmAddressInput): Promise<LiquidX402DeriveLwkWasmAddressResult>;
191
211
  export declare function verifyLiquidX402Payment(rpc: ElementsRpcClient | null, input: LiquidX402VerifyPaymentInput): Promise<LiquidX402VerifyPaymentResult>;
@@ -22,6 +22,7 @@ exports.encodeLiquidXPayment = encodeLiquidXPayment;
22
22
  exports.decodeLiquidXPayment = decodeLiquidXPayment;
23
23
  exports.prepareLiquidX402PsetPayment = prepareLiquidX402PsetPayment;
24
24
  exports.buildLiquidX402PaymentFromPset = buildLiquidX402PaymentFromPset;
25
+ exports.buildLiquidPolicyLockedRedemptionPaymentFromProposal = buildLiquidPolicyLockedRedemptionPaymentFromProposal;
25
26
  exports.prepareLiquidX402LwkWasmPayment = prepareLiquidX402LwkWasmPayment;
26
27
  exports.deriveLiquidX402LwkWasmAddress = deriveLiquidX402LwkWasmAddress;
27
28
  exports.verifyLiquidX402Payment = verifyLiquidX402Payment;
@@ -208,6 +209,32 @@ function buildLiquidX402PaymentFromPset(input) {
208
209
  summaryHash,
209
210
  };
210
211
  }
212
+ function buildLiquidPolicyLockedRedemptionPaymentFromProposal(input) {
213
+ const requirements = coercePolicyLockedRedemptionRequirements(input.requirements);
214
+ const proposal = coercePolicyLockedRedemptionProposal(input.proposal ?? getRecordValue(input.requirements, "policySpendProposal") ?? getExtraRecord(input.requirements).policySpendProposal, input.proposalPsetBase64);
215
+ const summaryHash = input.summaryHash ?? proposal.summaryHash ?? requirements.summaryHash ?? buildLiquidPolicyLockedRedemptionSummaryHash(requirements);
216
+ const paymentPayload = {
217
+ scheme: exports.LIQUID_X402_SCHEME,
218
+ network: requirements.network,
219
+ paymentRequestId: requirements.paymentRequestId,
220
+ asset: requirements.assetKey,
221
+ assetId: requirements.assetId,
222
+ amountAtomic: requirements.amountAtomic,
223
+ payTo: requirements.payTo,
224
+ psetBase64: proposal.psetBase64,
225
+ summaryHash,
226
+ ...(input.payer ? { payer: input.payer } : {}),
227
+ expiresAt: requirements.expiresAt,
228
+ };
229
+ return {
230
+ paymentPayload,
231
+ xPayment: encodeLiquidXPayment(paymentPayload),
232
+ psetBase64: proposal.psetBase64,
233
+ summaryHash,
234
+ ...(proposal.mode ? { proposalMode: proposal.mode } : {}),
235
+ redemptionSource: requirements.redemptionSource,
236
+ };
237
+ }
211
238
  async function prepareLiquidX402LwkWasmPayment(input) {
212
239
  const requirements = coerceRequirements(input.requirements);
213
240
  ensureLwkWasmNodeTimerCompat();
@@ -453,6 +480,108 @@ function mempoolRejectReason(result) {
453
480
  const raw = result;
454
481
  return String(raw["reject-reason"] ?? raw.reject_reason ?? raw.error ?? "mempool_rejected").trim() || "mempool_rejected";
455
482
  }
483
+ function coercePolicyLockedRedemptionRequirements(value) {
484
+ if (!value || typeof value !== "object" || Array.isArray(value))
485
+ throw new Error("requirements must be an object");
486
+ const raw = value;
487
+ const extra = getExtraRecord(raw);
488
+ const network = normalizeNetwork(raw.network);
489
+ const paymentRequestId = stringValue(extra.paymentRequestId ?? raw.paymentRequestId, "paymentRequestId");
490
+ const payTo = stringValue(raw.payTo ?? extra.recipient ?? extra.payTo, "payTo");
491
+ const assetId = stringValue(raw.assetId ?? extra.assetId ?? raw.asset, "assetId");
492
+ const amountAtomic = normalizeAmountAtomic((raw.maxAmountRequired ?? extra.amount ?? raw.amountAtomic));
493
+ const assetKey = normalizePaymentAssetKey(extra.asset ?? raw.assetKey ?? "custom");
494
+ const expiresAt = stringValue(extra.expiresAt ?? raw.expiresAt, "expiresAt");
495
+ const redemptionSource = coercePolicyLockedRedemptionSource(raw.redemptionSource ?? extra.redemptionSource);
496
+ const feeAssetId = String(extra.feeAssetId ?? exports.LIQUID_X402_ASSETS[network].lbtc.assetId);
497
+ return {
498
+ network,
499
+ paymentRequestId,
500
+ resource: String(raw.resource ?? ""),
501
+ description: String(raw.description ?? ""),
502
+ mimeType: String(raw.mimeType ?? ""),
503
+ payTo,
504
+ maxTimeoutSeconds: normalizeTimeout(raw.maxTimeoutSeconds),
505
+ assetKey,
506
+ assetId,
507
+ amountAtomic,
508
+ decimals: Number(extra.decimals ?? raw.decimals ?? 0),
509
+ expiresAt,
510
+ feeAsset: "lbtc",
511
+ feeAssetId,
512
+ ...(extra.maxFeeSat !== undefined ? { maxFeeSat: normalizeAmountAtomic(extra.maxFeeSat) } : {}),
513
+ ...(typeof raw.x402SummaryHash === "string" ? { summaryHash: raw.x402SummaryHash } : {}),
514
+ ...(typeof raw.summaryHash === "string" ? { summaryHash: raw.summaryHash } : {}),
515
+ ...(typeof extra.x402SummaryHash === "string" ? { summaryHash: extra.x402SummaryHash } : {}),
516
+ ...(typeof extra.summaryHash === "string" ? { summaryHash: extra.summaryHash } : {}),
517
+ redemptionSource,
518
+ };
519
+ }
520
+ function coercePolicyLockedRedemptionSource(value) {
521
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
522
+ throw new Error("redemptionSource is required for policy-locked redemption");
523
+ }
524
+ const source = value;
525
+ const type = String(source.type ?? "").trim();
526
+ if (type !== "policy_locked_position") {
527
+ throw new Error("redemptionSource.type must be policy_locked_position");
528
+ }
529
+ return source;
530
+ }
531
+ function coercePolicyLockedRedemptionProposal(value, proposalPsetBase64) {
532
+ const raw = value && typeof value === "object" && !Array.isArray(value)
533
+ ? value
534
+ : {};
535
+ if (raw.holderSignatureRequired === true) {
536
+ throw new Error("policy-locked redemption proposal still requires holder-side Simplicity signing; provide a service-prepared spend proposal");
537
+ }
538
+ const psetBase64 = String(proposalPsetBase64 ?? raw.psetBase64 ?? "").trim();
539
+ if (!psetBase64)
540
+ throw new Error("policy spend proposal psetBase64 is required");
541
+ return {
542
+ psetBase64,
543
+ ...(raw.mode ? { mode: String(raw.mode) } : {}),
544
+ ...(raw.summaryHash ? { summaryHash: String(raw.summaryHash) } : {}),
545
+ ...(raw.holderSignatureRequired !== undefined ? { holderSignatureRequired: raw.holderSignatureRequired === true } : {}),
546
+ ...(raw.metadata && typeof raw.metadata === "object" && !Array.isArray(raw.metadata)
547
+ ? { metadata: raw.metadata }
548
+ : {}),
549
+ };
550
+ }
551
+ function buildLiquidPolicyLockedRedemptionSummaryHash(requirements) {
552
+ return sha256Hex(stableStringify({
553
+ scheme: exports.LIQUID_X402_SCHEME,
554
+ network: requirements.network,
555
+ paymentRequestId: requirements.paymentRequestId,
556
+ asset: requirements.assetKey,
557
+ assetId: requirements.assetId,
558
+ amountAtomic: requirements.amountAtomic,
559
+ payTo: requirements.payTo,
560
+ expectedOutput: {
561
+ amountAtomic: requirements.amountAtomic,
562
+ assetId: requirements.assetId,
563
+ payTo: requirements.payTo,
564
+ },
565
+ feeAsset: requirements.feeAsset,
566
+ feeAssetId: requirements.feeAssetId ?? exports.LIQUID_X402_ASSETS[requirements.network].lbtc.assetId,
567
+ maxFeeSat: requirements.maxFeeSat ?? null,
568
+ }));
569
+ }
570
+ function getRecordValue(value, key) {
571
+ if (!value || typeof value !== "object" || Array.isArray(value))
572
+ return undefined;
573
+ return value[key];
574
+ }
575
+ function getExtraRecord(value) {
576
+ const extra = getRecordValue(value, "extra");
577
+ return extra && typeof extra === "object" && !Array.isArray(extra) ? extra : {};
578
+ }
579
+ function stringValue(value, name) {
580
+ const result = String(value ?? "").trim();
581
+ if (!result)
582
+ throw new Error(`${name} is required`);
583
+ return result;
584
+ }
456
585
  function coerceRequirements(value) {
457
586
  if (!value || typeof value !== "object" || Array.isArray(value))
458
587
  throw new Error("requirements must be an object");
@@ -563,7 +692,7 @@ function coercePaymentPayload(value) {
563
692
  scheme: exports.LIQUID_X402_SCHEME,
564
693
  network: normalizeNetwork(raw.network),
565
694
  paymentRequestId: String(raw.paymentRequestId ?? ""),
566
- asset: normalizeAssetKey(String(raw.asset ?? "")),
695
+ asset: normalizePaymentAssetKey(String(raw.asset ?? "")),
567
696
  assetId: String(raw.assetId ?? ""),
568
697
  amountAtomic: normalizeAmountAtomic(raw.amountAtomic),
569
698
  payTo: String(raw.payTo ?? ""),
@@ -579,6 +708,19 @@ function normalizeNetwork(input) {
579
708
  return value;
580
709
  throw new Error(`unsupported Liquid x402 network: ${String(input)}`);
581
710
  }
711
+ function normalizePaymentAssetKey(input) {
712
+ const value = String(input ?? "").trim().toLowerCase();
713
+ if (value === "custom")
714
+ return "custom";
715
+ try {
716
+ return normalizeAssetKey(value);
717
+ }
718
+ catch {
719
+ if (/^[a-z0-9_-]{1,64}$/u.test(value) || /^[0-9a-f]{64}$/u.test(value))
720
+ return value;
721
+ throw new Error(`unsupported Liquid x402 asset: ${String(input)}`);
722
+ }
723
+ }
582
724
  function normalizeAssetKey(input) {
583
725
  const value = String(input ?? "").trim().toLowerCase();
584
726
  if (value === "lbtc" || value === "bitcoin" || value === LIQUID_TESTNET_LBTC_ASSET_ID || value === LIQUID_MAINNET_LBTC_ASSET_ID) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hazbase/simplicity",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "An SDK for Simplicity on Liquid",
5
5
  "author": "IndieSquare Inc <info@hazbase.com>",
6
6
  "keywords": [