@hazbase/simplicity 0.4.2 → 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
@@ -20,20 +20,25 @@ With the current public SDK you can build and test:
20
20
  - bond redemption / settlement / close-out flows
21
21
  - LP fund capital call / distribution / close-out flows
22
22
  - receivable repayment-first funding / partial repayment / closing flows
23
+ - RWA delivery-versus-payment flows over Liquid PSETs and Liquid x402
24
+ - atomic Liquid DvP proposals where service delivery and buyer payment settle in one PSET
23
25
  - evidence, trust summary, lineage, and finality exports
24
26
 
25
27
  ## Public Architecture
26
28
 
27
- The public SDK is organized into five layers:
29
+ The public SDK is organized into domain clients and lower-level payment helpers:
28
30
  - `sdk.outputBinding`: shared output-binding support, evaluation, and fallback behavior
29
31
  - `sdk.policies`: generic constrained transfer and recursive policy engine
30
32
  - `sdk.bonds`: private bond / credit settlement business layer
31
33
  - `sdk.funds`: LP fund settlement business layer
32
34
  - `sdk.receivables`: repayment-first receivable business layer
35
+ - `sdk.rwaDvp`: RWA purchase terms, payment requirements, claim descriptors, and evidence
36
+ - `sdk.payments.x402`: Liquid x402 assets, PSET payment helpers, verification, and settlement
33
37
 
34
38
  A useful mental model is:
35
39
  - `sdk.outputBinding` + `sdk.policies` provide the shared settlement kernel
36
- - `sdk.bonds`, `sdk.funds`, and `sdk.receivables` build domain flows on top of that kernel
40
+ - `sdk.bonds`, `sdk.funds`, `sdk.receivables`, and `sdk.rwaDvp` build domain flows on top of that kernel
41
+ - `sdk.payments.x402` and the root-level `x402` exports provide the Liquid PSET payment layer used by those flows
37
42
 
38
43
  ## Quickstart
39
44
 
@@ -158,12 +163,27 @@ Use `sdk.rwaDvp` when a purchase flow needs a Liquid PSET payment request,
158
163
  delivery/refund claim descriptors, and an evidence bundle that ties the Liquid
159
164
  payment and delivery to an external lock or allocation record.
160
165
 
166
+ The EVM-side lock reference can describe the source asset being held for
167
+ settlement. Set `evmLock.tokenStandard` to `"ERC3475"`, `"ERC20"`, `"ERC721"`,
168
+ or `"ERC1155"` and include the token address / id fields used by your lock
169
+ manager. Existing callers that omit `tokenStandard` are treated as `"ERC3475"`
170
+ for backwards compatibility.
171
+
161
172
  For standard Liquid assets, `payment.asset` can be `"lbtc"` or `"usdt"`. If a
162
173
  testnet issuer or deployment uses a different USDt asset id, set
163
174
  `payment.asset: "usdt"` and pass the explicit `payment.assetId`; the generated
164
175
  payment requirements will preserve that asset id instead of replacing it with
165
176
  the SDK's default registry id.
166
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
+
167
187
  Typical entrypoints:
168
188
  - `sdk.rwaDvp.definePurchase(...)`
169
189
  - `sdk.rwaDvp.buildPaymentRequirements(...)`
@@ -192,6 +212,81 @@ delivery/refund output checks are the practical default; descriptor-bound checks
192
212
  should be used only when the caller can provide the exact output data required
193
213
  by the descriptor.
194
214
 
215
+ #### Atomic Liquid DvP PSET helpers
216
+
217
+ For flows that should avoid a separate "buyer pays first, service delivers
218
+ later" step, the SDK also exposes lower-level atomic DvP helpers from the root
219
+ package:
220
+
221
+ - `buildLiquidAtomicDvpRequirements(...)`
222
+ - `prepareLiquidAtomicDvpLwkWasmMakerProposal(...)`
223
+ - `prepareLiquidAtomicDvpLwkWasmTakerPayment(...)`
224
+ - `buildLiquidAtomicDvpPaymentFromPset(...)`
225
+ - `verifyLiquidAtomicDvpPayment(...)`
226
+ - `encodeLiquidAtomicDvpPayment(...)`
227
+ - `decodeLiquidAtomicDvpPayment(...)`
228
+
229
+ These helpers model a Liquidex-style exchange:
230
+ - the service/maker selects an exact RWA delivery UTXO and creates a proposal
231
+ - the buyer/taker adds the required payment output and signs the combined PSET
232
+ - the resulting `X-PAYMENT` payload commits to both the payment output and the
233
+ delivery output through a summary hash
234
+
235
+ The LWK convenience helpers dynamically load `lwk_node` or `lwk_wasm` from the
236
+ consuming application. Install one of them in the application that prepares or
237
+ takes proposals:
238
+
239
+ ```bash
240
+ npm install lwk_node
241
+ ```
242
+
243
+ Typical server-side shape:
244
+
245
+ ```ts
246
+ import {
247
+ buildLiquidAtomicDvpRequirements,
248
+ prepareLiquidAtomicDvpLwkWasmMakerProposal,
249
+ verifyLiquidAtomicDvpPayment,
250
+ } from "@hazbase/simplicity";
251
+
252
+ const requirements = buildLiquidAtomicDvpRequirements({
253
+ network: "liquidtestnet",
254
+ resource: "/v1/orders/<order-id>/liquid-atomic-pset",
255
+ paymentToTreasury: {
256
+ assetId: "<lbtc-or-usdt-asset-id>",
257
+ amountAtomic: "10000",
258
+ recipient: "<treasury-confidential-address>",
259
+ },
260
+ rwaToBuyer: {
261
+ assetId: "<rwa-liquid-asset-id>",
262
+ amountAtomic: "10",
263
+ recipient: "<buyer-confidential-address>",
264
+ },
265
+ expiresAt: new Date(Date.now() + 15 * 60_000),
266
+ });
267
+
268
+ const maker = await prepareLiquidAtomicDvpLwkWasmMakerProposal({
269
+ requirements,
270
+ mnemonic: process.env.SERVICE_LIQUID_MNEMONIC!,
271
+ descriptor: process.env.SERVICE_LIQUID_DESCRIPTOR!,
272
+ electrumUrl: process.env.LIQUID_ELECTRUM_URL!,
273
+ });
274
+
275
+ // Store maker.proposalPsetBase64 with the order/payment requirements.
276
+ // When the buyer submits X-PAYMENT, verify it before broadcasting.
277
+ const verified = verifyLiquidAtomicDvpPayment({
278
+ requirements,
279
+ paymentPayload: buyerPaymentPayload,
280
+ });
281
+ if (!verified.ok) throw new Error(verified.reason);
282
+ ```
283
+
284
+ The SDK-level payload check is intentionally lightweight: it verifies the
285
+ scheme, network, resource, expiry, PSET value, and summary hash. Production
286
+ settlement services should still decode the final PSET with their Liquid node or
287
+ wallet stack, verify the concrete payment and delivery outputs, and only then
288
+ broadcast.
289
+
195
290
  ## CLI and Confidence Commands
196
291
 
197
292
  ### Validation Surface
@@ -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),
@@ -7,10 +7,12 @@ export declare const RWA_DVP_REFUND_CLAIM_SCHEMA_VERSION: "rwa-dvp-refund-claim/
7
7
  export declare const RWA_DVP_VERIFICATION_SCHEMA_VERSION: "rwa-dvp-verification/v1";
8
8
  export declare const RWA_DVP_EVIDENCE_SCHEMA_VERSION: "rwa-dvp-evidence/v1";
9
9
  export type RwaDvpPaymentAsset = LiquidX402AssetKey;
10
+ export type RwaDvpEvmTokenStandard = "ERC3475" | "ERC20" | "ERC721" | "ERC1155";
10
11
  export interface RwaDvpEvmLockReference {
11
12
  chainId: number;
12
13
  lockManager: string;
13
14
  orderKey: string;
15
+ tokenStandard?: RwaDvpEvmTokenStandard;
14
16
  token?: string;
15
17
  backingOwner?: string;
16
18
  classId?: string;
@@ -48,6 +48,7 @@ function definePurchase(_sdk, input) {
48
48
  network,
49
49
  evmLock: {
50
50
  ...input.evmLock,
51
+ tokenStandard: input.evmLock.tokenStandard ?? "ERC3475",
51
52
  amountAtomic: normalizeInteger(input.evmLock.amountAtomic),
52
53
  },
53
54
  payment: {
package/dist/index.d.ts CHANGED
@@ -19,5 +19,5 @@ export { summarizeBondSettlementDescriptor, validateBondSettlementDescriptor, va
19
19
  export { summarizeFundDefinition, summarizeCapitalCallState, summarizeLPPositionReceipt, summarizeDistributionDescriptor, summarizeFundClosingDescriptor, summarizeFundFinalityPayload, validateFundDefinition, validateCapitalCallState, validateLPPositionReceipt, validateDistributionDescriptor, validateFundClosingDescriptor, validateFundCrossChecks, validateDistributionAgainstReceipt, validateClosingAgainstReceipt, buildClaimedCapitalCallState, buildRefundedCapitalCallState, buildLPPositionReceipt, applyDistributionToReceipt, applyDistributionsToReceipt, buildDistributionDescriptor, buildFundClosingDescriptor, } from "./domain/fundValidation";
20
20
  export { applyReceivableRepayment, buildDefaultedReceivableState, buildFundedReceivableState, buildReceivableClosingDescriptor, buildReceivableFundingClaimDescriptor, buildReceivableRepaymentClaimDescriptor, summarizeReceivableClosingDescriptor, summarizeReceivableDefinition, summarizeReceivableFundingClaimDescriptor, summarizeReceivableRepaymentClaimDescriptor, summarizeReceivableState, validateReceivableClosingAgainstState, validateReceivableClosingDescriptor, validateReceivableDefinition, validateReceivableFundingClaimAgainstState, validateReceivableFundingClaimDescriptor, validateReceivableRepaymentClaimAgainstState, validateReceivableRepaymentClaimDescriptor, validateReceivableState, validateReceivableCrossChecks, validateReceivableFundingTransition, validateReceivableRepaymentTransition, validateReceivableWriteOffTransition, verifyReceivableStateHistory as verifyReceivableStateHistoryValidation, } from "./domain/receivableValidation";
21
21
  export { RWA_DVP_DELIVERY_CLAIM_SCHEMA_VERSION, RWA_DVP_EVIDENCE_SCHEMA_VERSION, RWA_DVP_PURCHASE_SCHEMA_VERSION, RWA_DVP_REFUND_CLAIM_SCHEMA_VERSION, RWA_DVP_VERIFICATION_SCHEMA_VERSION, buildPaymentRequirements as buildRwaDvpPaymentRequirements, compileEscrowContract as compileRwaDvpEscrowContract, definePurchase as defineRwaDvpPurchase, executeDeliveryClaim as executeRwaDvpDeliveryClaim, executeRefundClaim as executeRwaDvpRefundClaim, exportEvidence as exportRwaDvpEvidence, inspectDeliveryClaim as inspectRwaDvpDeliveryClaim, inspectRefundClaim as inspectRwaDvpRefundClaim, prepareDeliveryClaim as prepareRwaDvpDeliveryClaim, prepareRefundClaim as prepareRwaDvpRefundClaim, summarizePurchase as summarizeRwaDvpPurchase, verifyDeliveryClaim as verifyRwaDvpDeliveryClaim, verifyPaymentPset as verifyRwaDvpPaymentPset, verifyRefundClaim as verifyRwaDvpRefundClaim, } from "./domain/rwaDvp";
22
- export type { RwaDvpDefinePurchaseInput, RwaDvpClaimOutputBinding, RwaDvpCompiledEscrowContract, RwaDvpCompileEscrowContractInput, RwaDvpDeliveryClaimDescriptor, RwaDvpDeliveryClaimExecution, RwaDvpDeliveryClaimInspection, RwaDvpEvidenceBundle, RwaDvpEvmLockReference, RwaDvpExecuteDeliveryClaimInput, RwaDvpExecuteRefundClaimInput, RwaDvpInspectDeliveryClaimInput, RwaDvpInspectRefundClaimInput, RwaDvpPaymentAsset, RwaDvpPreparedPurchase, RwaDvpPrepareDeliveryClaimInput, RwaDvpPrepareRefundClaimInput, RwaDvpPurchaseDefinition, RwaDvpRefundClaimDescriptor, RwaDvpRefundClaimExecution, RwaDvpRefundClaimInspection, RwaDvpSummary, RwaDvpVerificationReport, } from "./domain/rwaDvp";
22
+ export type { RwaDvpDefinePurchaseInput, RwaDvpClaimOutputBinding, RwaDvpCompiledEscrowContract, RwaDvpCompileEscrowContractInput, RwaDvpDeliveryClaimDescriptor, RwaDvpDeliveryClaimExecution, RwaDvpDeliveryClaimInspection, RwaDvpEvidenceBundle, RwaDvpEvmLockReference, RwaDvpEvmTokenStandard, RwaDvpExecuteDeliveryClaimInput, RwaDvpExecuteRefundClaimInput, RwaDvpInspectDeliveryClaimInput, RwaDvpInspectRefundClaimInput, RwaDvpPaymentAsset, RwaDvpPreparedPurchase, RwaDvpPrepareDeliveryClaimInput, RwaDvpPrepareRefundClaimInput, RwaDvpPurchaseDefinition, RwaDvpRefundClaimDescriptor, RwaDvpRefundClaimExecution, RwaDvpRefundClaimInspection, RwaDvpSummary, RwaDvpVerificationReport, } from "./domain/rwaDvp";
23
23
  export { compilePolicyStateContract, buildPolicyOutputDescriptor, listPolicyTemplates, loadPolicyTemplateManifest, validatePolicyTemplateManifest, describePolicyTemplate, validatePolicyTemplateParams, issue, prepareTransfer, executeTransfer, inspectTransfer, verifyState, verifyTransfer, exportEvidence as exportPolicyEvidence, summarizePolicyState, summarizePolicyOutputDescriptor, summarizePolicyTransferDescriptor, validatePolicyState, validatePolicyOutputDescriptor, validatePolicyTransferDescriptor, } from "./domain/policies";
@@ -0,0 +1,197 @@
1
+ export declare const LIQUID_ATOMIC_DVP_PSET_SCHEME: "rwa-liquid-atomic-dvp-pset";
2
+ export declare const LIQUID_ATOMIC_DVP_PSET_MODE: "service_cosign_atomic_pset_v1";
3
+ export type LiquidAtomicDvpNetwork = "liquidtestnet" | "liquidv1";
4
+ export interface LiquidAtomicDvpOutputRequirement {
5
+ assetId: string;
6
+ amountAtomic: string;
7
+ recipient: string;
8
+ }
9
+ export interface LiquidAtomicDvpServiceSigner {
10
+ type: "operator_wallet" | "simplicity";
11
+ xonly?: string;
12
+ }
13
+ export interface LiquidAtomicDvpRequirementsInput {
14
+ network?: LiquidAtomicDvpNetwork;
15
+ paymentRequestId: string;
16
+ resource: string;
17
+ termsHash: string;
18
+ policyHash?: string | null;
19
+ expiresAt: string | Date;
20
+ maxTimeoutSeconds?: number;
21
+ description?: string;
22
+ mimeType?: string;
23
+ payment: {
24
+ assetId: string;
25
+ amountAtomic: string | number | bigint;
26
+ recipient: string;
27
+ };
28
+ delivery: {
29
+ assetId: string;
30
+ amountAtomic: string | number | bigint;
31
+ recipient: string;
32
+ };
33
+ maxFeeSat?: string | number | bigint;
34
+ serviceSigner?: LiquidAtomicDvpServiceSigner | null;
35
+ }
36
+ export interface LiquidAtomicDvpRequirements {
37
+ scheme: typeof LIQUID_ATOMIC_DVP_PSET_SCHEME;
38
+ mode: typeof LIQUID_ATOMIC_DVP_PSET_MODE;
39
+ network: LiquidAtomicDvpNetwork;
40
+ paymentRequestId: string;
41
+ resource: string;
42
+ description: string;
43
+ mimeType: string;
44
+ expiresAt: string;
45
+ maxTimeoutSeconds: number;
46
+ termsHash: string;
47
+ policyHash: string | null;
48
+ summaryHash: string;
49
+ maxFeeSat: string | null;
50
+ outputs: {
51
+ paymentToTreasury: LiquidAtomicDvpOutputRequirement;
52
+ rwaToBuyer: LiquidAtomicDvpOutputRequirement;
53
+ };
54
+ serviceSigner: LiquidAtomicDvpServiceSigner | null;
55
+ extra: {
56
+ settlementMode: typeof LIQUID_ATOMIC_DVP_PSET_MODE;
57
+ payment: LiquidAtomicDvpOutputRequirement;
58
+ delivery: LiquidAtomicDvpOutputRequirement;
59
+ feeAsset: "lbtc";
60
+ maxFeeSat: string | null;
61
+ };
62
+ }
63
+ export interface LiquidAtomicDvpPaymentPayload {
64
+ scheme: typeof LIQUID_ATOMIC_DVP_PSET_SCHEME;
65
+ mode?: typeof LIQUID_ATOMIC_DVP_PSET_MODE;
66
+ network: LiquidAtomicDvpNetwork;
67
+ paymentRequestId: string;
68
+ psetBase64: string;
69
+ summaryHash: string;
70
+ expiresAt?: string;
71
+ payer?: string;
72
+ metadata?: Record<string, unknown>;
73
+ }
74
+ export interface LiquidAtomicDvpLwkWasmModule {
75
+ Address: {
76
+ new (value: string): any;
77
+ parse?: (value: string, network: any) => any;
78
+ };
79
+ AssetId: {
80
+ new (value: string): any;
81
+ fromString?: (value: string) => any;
82
+ };
83
+ EsploraClient: new (network: any, url: string, waterfalls: boolean, concurrency: number, utxoOnly: boolean) => any;
84
+ Mnemonic: new (value: string) => any;
85
+ Network: {
86
+ mainnet: () => any;
87
+ testnet: () => any;
88
+ };
89
+ OutPoint: {
90
+ new (value: string): any;
91
+ };
92
+ Pset: {
93
+ new (value: string): any;
94
+ };
95
+ Signer: new (mnemonic: any, network: any) => any;
96
+ Transaction?: {
97
+ new (value: string): any;
98
+ fromString?: (value: string) => any;
99
+ };
100
+ UnvalidatedLiquidexProposal: {
101
+ fromPset: (pset: any) => any;
102
+ new?: (value: string) => any;
103
+ };
104
+ Wollet: new (network: any, descriptor: any) => any;
105
+ }
106
+ export interface LiquidAtomicDvpPrepareMakerProposalInput {
107
+ requirements: LiquidAtomicDvpRequirements | Record<string, unknown>;
108
+ mnemonic: string;
109
+ deliveryOutpoint?: string;
110
+ esploraUrl?: string;
111
+ waterfalls?: boolean;
112
+ concurrency?: number;
113
+ utxoOnly?: boolean;
114
+ scan?: boolean;
115
+ scanToIndex?: number;
116
+ feeRate?: number;
117
+ sign?: boolean;
118
+ lwk?: LiquidAtomicDvpLwkWasmModule;
119
+ importLwk?: () => Promise<LiquidAtomicDvpLwkWasmModule>;
120
+ }
121
+ export interface LiquidAtomicDvpPrepareMakerProposalResult {
122
+ proposalPsetBase64: string;
123
+ proposal: string;
124
+ deliveryOutpoint: string;
125
+ deliveryAssetId: string;
126
+ deliveryAmountAtomic: string;
127
+ paymentAssetId: string;
128
+ paymentAmountAtomic: string;
129
+ paymentRecipient: string;
130
+ summaryHash: string;
131
+ descriptor: string;
132
+ dwid?: string;
133
+ }
134
+ export interface LiquidAtomicDvpTakeProposalInput {
135
+ requirements: LiquidAtomicDvpRequirements | Record<string, unknown>;
136
+ mnemonic: string;
137
+ proposal?: string;
138
+ proposalPsetBase64?: string;
139
+ proposalTxHex?: string;
140
+ payer?: string;
141
+ esploraUrl?: string;
142
+ waterfalls?: boolean;
143
+ concurrency?: number;
144
+ utxoOnly?: boolean;
145
+ scan?: boolean;
146
+ scanToIndex?: number;
147
+ feeRate?: number;
148
+ finalize?: boolean;
149
+ lwk?: LiquidAtomicDvpLwkWasmModule;
150
+ importLwk?: () => Promise<LiquidAtomicDvpLwkWasmModule>;
151
+ }
152
+ export type LiquidAtomicDvpTakeProposalResult = ReturnType<typeof buildLiquidAtomicDvpPaymentFromPset> & {
153
+ descriptor: string;
154
+ dwid?: string;
155
+ proposalInput: LiquidAtomicDvpOutputRequirement;
156
+ proposalOutput: LiquidAtomicDvpOutputRequirement;
157
+ };
158
+ export interface LiquidAtomicDvpVerifyPayloadResult {
159
+ isValid: boolean;
160
+ invalidReason?: string;
161
+ network?: LiquidAtomicDvpNetwork;
162
+ paymentRequestId?: string;
163
+ summaryHash?: string;
164
+ }
165
+ export declare function buildLiquidAtomicDvpRequirements(input: LiquidAtomicDvpRequirementsInput): LiquidAtomicDvpRequirements;
166
+ export declare function prepareLiquidAtomicDvpLwkWasmMakerProposal(input: LiquidAtomicDvpPrepareMakerProposalInput): Promise<LiquidAtomicDvpPrepareMakerProposalResult>;
167
+ export declare function prepareLiquidAtomicDvpLwkWasmTakerPayment(input: LiquidAtomicDvpTakeProposalInput): Promise<LiquidAtomicDvpTakeProposalResult>;
168
+ export declare function buildLiquidAtomicDvpSummaryHash(input: {
169
+ network: LiquidAtomicDvpNetwork;
170
+ paymentRequestId: string;
171
+ termsHash: string;
172
+ policyHash: string | null;
173
+ expiresAt: string;
174
+ maxFeeSat: string | null;
175
+ payment: LiquidAtomicDvpOutputRequirement;
176
+ delivery: LiquidAtomicDvpOutputRequirement;
177
+ serviceSigner: LiquidAtomicDvpServiceSigner | null;
178
+ }): string;
179
+ export declare function encodeLiquidAtomicDvpPayment(payload: LiquidAtomicDvpPaymentPayload): string;
180
+ export declare function decodeLiquidAtomicDvpPayment(raw: string): LiquidAtomicDvpPaymentPayload | null;
181
+ export declare function buildLiquidAtomicDvpPaymentFromPset(input: {
182
+ requirements: LiquidAtomicDvpRequirements | Record<string, unknown>;
183
+ psetBase64: string;
184
+ payer?: string;
185
+ metadata?: Record<string, unknown>;
186
+ }): {
187
+ paymentPayload: LiquidAtomicDvpPaymentPayload;
188
+ xPayment: string;
189
+ psetBase64: string;
190
+ summaryHash: string;
191
+ };
192
+ export declare function verifyLiquidAtomicDvpPayment(input: {
193
+ requirements: LiquidAtomicDvpRequirements | Record<string, unknown>;
194
+ paymentPayload: LiquidAtomicDvpPaymentPayload | Record<string, unknown>;
195
+ now?: Date;
196
+ }): LiquidAtomicDvpVerifyPayloadResult;
197
+ export declare function coerceAtomicRequirements(value: LiquidAtomicDvpRequirements | Record<string, unknown>): LiquidAtomicDvpRequirements;
@@ -0,0 +1,482 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LIQUID_ATOMIC_DVP_PSET_MODE = exports.LIQUID_ATOMIC_DVP_PSET_SCHEME = void 0;
4
+ exports.buildLiquidAtomicDvpRequirements = buildLiquidAtomicDvpRequirements;
5
+ exports.prepareLiquidAtomicDvpLwkWasmMakerProposal = prepareLiquidAtomicDvpLwkWasmMakerProposal;
6
+ exports.prepareLiquidAtomicDvpLwkWasmTakerPayment = prepareLiquidAtomicDvpLwkWasmTakerPayment;
7
+ exports.buildLiquidAtomicDvpSummaryHash = buildLiquidAtomicDvpSummaryHash;
8
+ exports.encodeLiquidAtomicDvpPayment = encodeLiquidAtomicDvpPayment;
9
+ exports.decodeLiquidAtomicDvpPayment = decodeLiquidAtomicDvpPayment;
10
+ exports.buildLiquidAtomicDvpPaymentFromPset = buildLiquidAtomicDvpPaymentFromPset;
11
+ exports.verifyLiquidAtomicDvpPayment = verifyLiquidAtomicDvpPayment;
12
+ exports.coerceAtomicRequirements = coerceAtomicRequirements;
13
+ const node_crypto_1 = require("node:crypto");
14
+ const summary_1 = require("../core/summary");
15
+ exports.LIQUID_ATOMIC_DVP_PSET_SCHEME = "rwa-liquid-atomic-dvp-pset";
16
+ exports.LIQUID_ATOMIC_DVP_PSET_MODE = "service_cosign_atomic_pset_v1";
17
+ ;
18
+ function buildLiquidAtomicDvpRequirements(input) {
19
+ const network = normalizeAtomicNetwork(input.network ?? "liquidtestnet");
20
+ const paymentRequestId = requiredString(input.paymentRequestId, "paymentRequestId");
21
+ const resource = requiredString(input.resource, "resource");
22
+ const termsHash = requiredString(input.termsHash, "termsHash");
23
+ const policyHash = input.policyHash ?? null;
24
+ const expiresAt = normalizeDateTime(input.expiresAt, "expiresAt");
25
+ const maxTimeoutSeconds = normalizeTimeout(input.maxTimeoutSeconds);
26
+ const payment = normalizeOutput(input.payment, "payment");
27
+ const delivery = normalizeOutput(input.delivery, "delivery");
28
+ const maxFeeSat = input.maxFeeSat === undefined ? null : normalizeInteger(input.maxFeeSat, "maxFeeSat");
29
+ const serviceSigner = normalizeServiceSigner(input.serviceSigner);
30
+ const summaryHash = buildLiquidAtomicDvpSummaryHash({
31
+ network,
32
+ paymentRequestId,
33
+ termsHash,
34
+ policyHash,
35
+ expiresAt,
36
+ maxFeeSat,
37
+ payment,
38
+ delivery,
39
+ serviceSigner,
40
+ });
41
+ return {
42
+ scheme: exports.LIQUID_ATOMIC_DVP_PSET_SCHEME,
43
+ mode: exports.LIQUID_ATOMIC_DVP_PSET_MODE,
44
+ network,
45
+ paymentRequestId,
46
+ resource,
47
+ description: String(input.description ?? "").trim() || "Hazbase RWA atomic Liquid DvP payment",
48
+ mimeType: String(input.mimeType ?? "").trim() || "application/json",
49
+ expiresAt,
50
+ maxTimeoutSeconds,
51
+ termsHash,
52
+ policyHash,
53
+ summaryHash,
54
+ maxFeeSat,
55
+ outputs: {
56
+ paymentToTreasury: payment,
57
+ rwaToBuyer: delivery,
58
+ },
59
+ serviceSigner,
60
+ extra: {
61
+ settlementMode: exports.LIQUID_ATOMIC_DVP_PSET_MODE,
62
+ payment,
63
+ delivery,
64
+ feeAsset: "lbtc",
65
+ maxFeeSat,
66
+ },
67
+ };
68
+ }
69
+ async function prepareLiquidAtomicDvpLwkWasmMakerProposal(input) {
70
+ const requirements = coerceAtomicRequirements(input.requirements);
71
+ ensureLwkWasmNodeTimerCompat();
72
+ const lwk = input.lwk ?? await loadLwkWasm(input.importLwk);
73
+ const { network, signer, descriptor, wollet } = await buildLwkContext(lwk, requirements.network, input.mnemonic, input);
74
+ const delivery = requirements.outputs.rwaToBuyer;
75
+ const payment = requirements.outputs.paymentToTreasury;
76
+ const deliveryUtxo = selectExactDeliveryUtxo(wollet, delivery, input.deliveryOutpoint);
77
+ const paymentRecipient = parseLwkAddress(lwk, payment.recipient, network);
78
+ let builder = network.txBuilder();
79
+ if (input.feeRate !== undefined)
80
+ builder = builder.feeRate(input.feeRate);
81
+ builder = builder.liquidexMake(deliveryUtxo.outpoint, paymentRecipient, BigInt(payment.amountAtomic), parseLwkAssetId(lwk, payment.assetId));
82
+ const unsigned = builder.finish(wollet);
83
+ const proposalPset = input.sign === false ? unsigned : signer.sign(unsigned);
84
+ const proposalPsetBase64 = proposalPset.toString();
85
+ const proposal = lwk.UnvalidatedLiquidexProposal.fromPset(proposalPset).toString();
86
+ return {
87
+ proposalPsetBase64,
88
+ proposal,
89
+ deliveryOutpoint: deliveryUtxo.outpointText,
90
+ deliveryAssetId: delivery.assetId,
91
+ deliveryAmountAtomic: delivery.amountAtomic,
92
+ paymentAssetId: payment.assetId,
93
+ paymentAmountAtomic: payment.amountAtomic,
94
+ paymentRecipient: payment.recipient,
95
+ summaryHash: requirements.summaryHash,
96
+ descriptor: descriptor.toString(),
97
+ ...(typeof wollet.dwid === "function" ? { dwid: wollet.dwid() } : {}),
98
+ };
99
+ }
100
+ async function prepareLiquidAtomicDvpLwkWasmTakerPayment(input) {
101
+ const requirements = coerceAtomicRequirements(input.requirements);
102
+ ensureLwkWasmNodeTimerCompat();
103
+ const lwk = input.lwk ?? await loadLwkWasm(input.importLwk);
104
+ const { network, signer, descriptor, wollet } = await buildLwkContext(lwk, requirements.network, input.mnemonic, input);
105
+ const proposal = parseLiquidexProposal(lwk, input.proposalPsetBase64 ?? input.proposal);
106
+ const validated = input.proposalTxHex
107
+ ? proposal.validate(parseLwkTransaction(lwk, input.proposalTxHex))
108
+ : proposal.insecureValidate();
109
+ const proposalInput = assetAmountRequirement(validated.input());
110
+ const proposalOutput = assetAmountRequirement(validated.output());
111
+ assertOutputMatches(proposalInput, requirements.outputs.rwaToBuyer, "Liquidex maker input");
112
+ assertOutputMatches(proposalOutput, requirements.outputs.paymentToTreasury, "Liquidex maker output");
113
+ let builder = network.txBuilder();
114
+ if (input.feeRate !== undefined)
115
+ builder = builder.feeRate(input.feeRate);
116
+ builder = builder.liquidexTake([validated]);
117
+ const unsigned = builder.finish(wollet);
118
+ const signed = signer.sign(unsigned);
119
+ const pset = input.finalize === false ? signed : wollet.finalize(signed);
120
+ const payment = buildLiquidAtomicDvpPaymentFromPset({
121
+ requirements,
122
+ psetBase64: pset.toString(),
123
+ payer: input.payer,
124
+ });
125
+ return {
126
+ ...payment,
127
+ descriptor: descriptor.toString(),
128
+ ...(typeof wollet.dwid === "function" ? { dwid: wollet.dwid() } : {}),
129
+ proposalInput,
130
+ proposalOutput,
131
+ };
132
+ }
133
+ function buildLiquidAtomicDvpSummaryHash(input) {
134
+ return sha256Hex((0, summary_1.stableStringify)({
135
+ scheme: exports.LIQUID_ATOMIC_DVP_PSET_SCHEME,
136
+ mode: exports.LIQUID_ATOMIC_DVP_PSET_MODE,
137
+ network: input.network,
138
+ paymentRequestId: input.paymentRequestId,
139
+ termsHash: input.termsHash,
140
+ policyHash: input.policyHash,
141
+ expiresAt: input.expiresAt,
142
+ maxFeeSat: input.maxFeeSat,
143
+ outputs: {
144
+ paymentToTreasury: input.payment,
145
+ rwaToBuyer: input.delivery,
146
+ },
147
+ serviceSigner: input.serviceSigner
148
+ ? {
149
+ type: input.serviceSigner.type,
150
+ xonly: input.serviceSigner.xonly ?? null,
151
+ }
152
+ : null,
153
+ }));
154
+ }
155
+ function encodeLiquidAtomicDvpPayment(payload) {
156
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
157
+ }
158
+ function decodeLiquidAtomicDvpPayment(raw) {
159
+ const value = String(raw ?? "").trim();
160
+ if (!value)
161
+ return null;
162
+ try {
163
+ const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
164
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
165
+ return null;
166
+ if (String(parsed.scheme ?? "") !== exports.LIQUID_ATOMIC_DVP_PSET_SCHEME)
167
+ return null;
168
+ return coerceAtomicPayload(parsed);
169
+ }
170
+ catch {
171
+ return null;
172
+ }
173
+ }
174
+ function buildLiquidAtomicDvpPaymentFromPset(input) {
175
+ const requirements = coerceAtomicRequirements(input.requirements);
176
+ const psetBase64 = requiredString(input.psetBase64, "psetBase64");
177
+ const paymentPayload = {
178
+ scheme: exports.LIQUID_ATOMIC_DVP_PSET_SCHEME,
179
+ mode: exports.LIQUID_ATOMIC_DVP_PSET_MODE,
180
+ network: requirements.network,
181
+ paymentRequestId: requirements.paymentRequestId,
182
+ psetBase64,
183
+ summaryHash: requirements.summaryHash,
184
+ expiresAt: requirements.expiresAt,
185
+ ...(input.payer ? { payer: input.payer } : {}),
186
+ ...(input.metadata ? { metadata: input.metadata } : {}),
187
+ };
188
+ return {
189
+ paymentPayload,
190
+ xPayment: encodeLiquidAtomicDvpPayment(paymentPayload),
191
+ psetBase64,
192
+ summaryHash: requirements.summaryHash,
193
+ };
194
+ }
195
+ function verifyLiquidAtomicDvpPayment(input) {
196
+ const requirements = coerceAtomicRequirements(input.requirements);
197
+ const payload = coerceAtomicPayload(input.paymentPayload);
198
+ const now = input.now ?? new Date();
199
+ const base = {
200
+ network: payload.network,
201
+ paymentRequestId: payload.paymentRequestId,
202
+ summaryHash: payload.summaryHash,
203
+ };
204
+ if (payload.scheme !== exports.LIQUID_ATOMIC_DVP_PSET_SCHEME) {
205
+ return { ...base, isValid: false, invalidReason: "scheme_mismatch" };
206
+ }
207
+ if (payload.network !== requirements.network)
208
+ return { ...base, isValid: false, invalidReason: "network_mismatch" };
209
+ if (payload.paymentRequestId !== requirements.paymentRequestId) {
210
+ return { ...base, isValid: false, invalidReason: "payment_request_id_mismatch" };
211
+ }
212
+ if (normalizeHexHash(payload.summaryHash) !== normalizeHexHash(requirements.summaryHash)) {
213
+ return { ...base, isValid: false, invalidReason: "summary_hash_mismatch" };
214
+ }
215
+ if (!payload.psetBase64)
216
+ return { ...base, isValid: false, invalidReason: "pset_missing" };
217
+ if (!looksBase64(payload.psetBase64))
218
+ return { ...base, isValid: false, invalidReason: "pset_invalid_base64" };
219
+ if (new Date(payload.expiresAt ?? requirements.expiresAt).getTime() <= now.getTime()) {
220
+ return { ...base, isValid: false, invalidReason: "payment_expired" };
221
+ }
222
+ return { ...base, isValid: true };
223
+ }
224
+ function coerceAtomicRequirements(value) {
225
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
226
+ throw new Error("atomic DvP requirements must be an object");
227
+ }
228
+ const raw = value;
229
+ const outputs = raw.outputs && typeof raw.outputs === "object" && !Array.isArray(raw.outputs) ? raw.outputs : {};
230
+ const extra = raw.extra && typeof raw.extra === "object" && !Array.isArray(raw.extra) ? raw.extra : {};
231
+ const payment = normalizeOutput(outputs.paymentToTreasury ?? extra.payment, "paymentToTreasury");
232
+ const delivery = normalizeOutput(outputs.rwaToBuyer ?? extra.delivery, "rwaToBuyer");
233
+ const network = normalizeAtomicNetwork(raw.network);
234
+ const policyHash = typeof raw.policyHash === "string" && raw.policyHash.trim() ? raw.policyHash.trim() : null;
235
+ const maxFeeSat = raw.maxFeeSat === null || raw.maxFeeSat === undefined ? null : normalizeInteger(raw.maxFeeSat, "maxFeeSat");
236
+ const serviceSigner = normalizeServiceSigner(raw.serviceSigner);
237
+ const fallbackSummaryHash = buildLiquidAtomicDvpSummaryHash({
238
+ network,
239
+ paymentRequestId: requiredString(raw.paymentRequestId, "paymentRequestId"),
240
+ termsHash: requiredString(raw.termsHash, "termsHash"),
241
+ policyHash,
242
+ expiresAt: normalizeDateTime(raw.expiresAt, "expiresAt"),
243
+ maxFeeSat,
244
+ payment,
245
+ delivery,
246
+ serviceSigner,
247
+ });
248
+ return {
249
+ scheme: exports.LIQUID_ATOMIC_DVP_PSET_SCHEME,
250
+ mode: exports.LIQUID_ATOMIC_DVP_PSET_MODE,
251
+ network,
252
+ paymentRequestId: requiredString(raw.paymentRequestId, "paymentRequestId"),
253
+ resource: requiredString(raw.resource, "resource"),
254
+ description: String(raw.description ?? "").trim() || "Hazbase RWA atomic Liquid DvP payment",
255
+ mimeType: String(raw.mimeType ?? "").trim() || "application/json",
256
+ expiresAt: normalizeDateTime(raw.expiresAt, "expiresAt"),
257
+ maxTimeoutSeconds: normalizeTimeout(raw.maxTimeoutSeconds),
258
+ termsHash: requiredString(raw.termsHash, "termsHash"),
259
+ policyHash,
260
+ summaryHash: typeof raw.summaryHash === "string" && raw.summaryHash.trim() ? raw.summaryHash.trim() : fallbackSummaryHash,
261
+ maxFeeSat,
262
+ outputs: {
263
+ paymentToTreasury: payment,
264
+ rwaToBuyer: delivery,
265
+ },
266
+ serviceSigner,
267
+ extra: {
268
+ settlementMode: exports.LIQUID_ATOMIC_DVP_PSET_MODE,
269
+ payment,
270
+ delivery,
271
+ feeAsset: "lbtc",
272
+ maxFeeSat,
273
+ },
274
+ };
275
+ }
276
+ function coerceAtomicPayload(value) {
277
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
278
+ throw new Error("atomic DvP payment payload must be an object");
279
+ }
280
+ const raw = value;
281
+ return {
282
+ scheme: String(raw.scheme ?? exports.LIQUID_ATOMIC_DVP_PSET_SCHEME),
283
+ mode: raw.mode === exports.LIQUID_ATOMIC_DVP_PSET_MODE ? exports.LIQUID_ATOMIC_DVP_PSET_MODE : undefined,
284
+ network: normalizeAtomicNetwork(raw.network),
285
+ paymentRequestId: requiredString(raw.paymentRequestId, "paymentRequestId"),
286
+ psetBase64: requiredString(raw.psetBase64, "psetBase64"),
287
+ summaryHash: requiredString(raw.summaryHash, "summaryHash"),
288
+ expiresAt: typeof raw.expiresAt === "string" ? raw.expiresAt : undefined,
289
+ ...(typeof raw.payer === "string" && raw.payer.trim() ? { payer: raw.payer.trim() } : {}),
290
+ ...(raw.metadata && typeof raw.metadata === "object" && !Array.isArray(raw.metadata)
291
+ ? { metadata: raw.metadata }
292
+ : {}),
293
+ };
294
+ }
295
+ async function loadLwkWasm(importer) {
296
+ ensureLwkWasmNodeTimerCompat();
297
+ if (importer)
298
+ return importer();
299
+ const dynamicImport = new Function("specifier", "return import(specifier)");
300
+ const failures = [];
301
+ for (const specifier of ["lwk_wasm", "lwk_node"]) {
302
+ try {
303
+ const imported = await dynamicImport(specifier);
304
+ return imported.default ?? imported;
305
+ }
306
+ catch (error) {
307
+ failures.push(`${specifier}: ${error instanceof Error ? error.message : String(error)}`);
308
+ }
309
+ }
310
+ throw new Error("lwk_wasm or lwk_node is required for atomic Liquid DvP PSETs. " +
311
+ `Import failed: ${failures.join("; ")}`);
312
+ }
313
+ function ensureLwkWasmNodeTimerCompat() {
314
+ const scope = globalThis;
315
+ if (typeof scope.window === "undefined" && typeof scope.setTimeout === "function") {
316
+ scope.window = scope;
317
+ }
318
+ if (typeof scope.Window === "undefined" && typeof scope.window !== "undefined") {
319
+ scope.Window = Object;
320
+ }
321
+ }
322
+ async function buildLwkContext(lwk, networkKey, mnemonicText, options) {
323
+ const network = networkKey === "liquidv1" ? lwk.Network.mainnet() : lwk.Network.testnet();
324
+ const mnemonic = new lwk.Mnemonic(mnemonicText);
325
+ const signer = new lwk.Signer(mnemonic, network);
326
+ const descriptor = signer.wpkhSlip77Descriptor();
327
+ const wollet = new lwk.Wollet(network, descriptor);
328
+ if (options.scan !== false) {
329
+ const client = options.esploraUrl
330
+ ? new lwk.EsploraClient(network, options.esploraUrl, options.waterfalls ?? false, options.concurrency ?? 4, options.utxoOnly ?? false)
331
+ : network.defaultEsploraClient();
332
+ const update = options.scanToIndex !== undefined
333
+ ? await client.fullScanToIndex(wollet, options.scanToIndex)
334
+ : await client.fullScan(wollet);
335
+ if (update)
336
+ wollet.applyUpdate(update);
337
+ }
338
+ return { network, signer, descriptor, wollet };
339
+ }
340
+ function parseLwkAddress(lwk, address, network) {
341
+ if (typeof lwk.Address.parse === "function") {
342
+ try {
343
+ return lwk.Address.parse(address, network);
344
+ }
345
+ catch (error) {
346
+ const message = error instanceof Error ? error.message : String(error);
347
+ if (!message.includes("non-blinded"))
348
+ throw error;
349
+ }
350
+ }
351
+ return new lwk.Address(address);
352
+ }
353
+ function parseLwkAssetId(lwk, assetId) {
354
+ return typeof lwk.AssetId.fromString === "function" ? lwk.AssetId.fromString(assetId) : new lwk.AssetId(assetId);
355
+ }
356
+ function parseLwkTransaction(lwk, txHex) {
357
+ if (lwk.Transaction?.fromString)
358
+ return lwk.Transaction.fromString(txHex);
359
+ if (lwk.Transaction)
360
+ return new lwk.Transaction(txHex);
361
+ throw new Error("LWK Transaction support is required to validate a Liquidex proposal against a transaction.");
362
+ }
363
+ function parseLiquidexProposal(lwk, value) {
364
+ const encoded = String(value ?? "").trim();
365
+ if (!encoded)
366
+ throw new Error("Liquidex proposal PSET is required");
367
+ try {
368
+ return lwk.UnvalidatedLiquidexProposal.fromPset(new lwk.Pset(encoded));
369
+ }
370
+ catch (psetError) {
371
+ if (typeof lwk.UnvalidatedLiquidexProposal.new === "function") {
372
+ try {
373
+ return lwk.UnvalidatedLiquidexProposal.new(encoded);
374
+ }
375
+ catch { }
376
+ }
377
+ throw psetError;
378
+ }
379
+ }
380
+ function selectExactDeliveryUtxo(wollet, delivery, requestedOutpoint) {
381
+ const utxos = typeof wollet.utxos === "function" ? wollet.utxos() : [];
382
+ for (const utxo of utxos) {
383
+ const outpoint = utxo.outpoint();
384
+ const outpointText = outpointString(outpoint);
385
+ if (requestedOutpoint && outpointText !== requestedOutpoint)
386
+ continue;
387
+ const secrets = utxo.unblinded();
388
+ const assetId = secrets?.asset?.().toString?.();
389
+ const amountAtomic = secrets?.value?.().toString?.();
390
+ if (assetId === delivery.assetId && amountAtomic === delivery.amountAtomic) {
391
+ return { outpoint, outpointText };
392
+ }
393
+ }
394
+ if (requestedOutpoint) {
395
+ throw new Error(`Delivery UTXO ${requestedOutpoint} is not an exact match for the atomic DvP delivery output.`);
396
+ }
397
+ throw new Error("No exact Liquid RWA UTXO is available for the atomic DvP delivery output.");
398
+ }
399
+ function outpointString(outpoint) {
400
+ try {
401
+ return `${outpoint.txid().toString()}:${outpoint.vout()}`;
402
+ }
403
+ catch {
404
+ return String(outpoint);
405
+ }
406
+ }
407
+ function assetAmountRequirement(value) {
408
+ return {
409
+ assetId: value.asset().toString(),
410
+ amountAtomic: value.amount().toString(),
411
+ recipient: "",
412
+ };
413
+ }
414
+ function assertOutputMatches(actual, expected, label) {
415
+ if (actual.assetId !== expected.assetId || actual.amountAtomic !== expected.amountAtomic) {
416
+ throw new Error(`${label} does not match atomic DvP requirements: ` +
417
+ `expected ${expected.amountAtomic} ${expected.assetId}, got ${actual.amountAtomic} ${actual.assetId}`);
418
+ }
419
+ }
420
+ function normalizeAtomicNetwork(value) {
421
+ if (value === "liquidv1")
422
+ return "liquidv1";
423
+ if (value === "liquidtestnet" || value === undefined || value === null || value === "")
424
+ return "liquidtestnet";
425
+ throw new Error(`unsupported Liquid atomic DvP network: ${String(value)}`);
426
+ }
427
+ function normalizeOutput(value, name) {
428
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
429
+ throw new Error(`${name} output requirement is required`);
430
+ }
431
+ const raw = value;
432
+ return {
433
+ assetId: requiredString(raw.assetId, `${name}.assetId`),
434
+ amountAtomic: normalizeInteger(raw.amountAtomic, `${name}.amountAtomic`),
435
+ recipient: requiredString(raw.recipient, `${name}.recipient`),
436
+ };
437
+ }
438
+ function normalizeServiceSigner(value) {
439
+ if (value === null || value === undefined)
440
+ return null;
441
+ if (!value || typeof value !== "object" || Array.isArray(value))
442
+ return null;
443
+ const raw = value;
444
+ const type = raw.type === "simplicity" ? "simplicity" : raw.type === "operator_wallet" ? "operator_wallet" : null;
445
+ if (!type)
446
+ return null;
447
+ const xonly = typeof raw.xonly === "string" && raw.xonly.trim() ? raw.xonly.trim() : undefined;
448
+ return { type, ...(xonly ? { xonly } : {}) };
449
+ }
450
+ function normalizeInteger(value, name) {
451
+ const text = String(value ?? "").trim();
452
+ if (!/^\d+$/u.test(text))
453
+ throw new Error(`${name} must be a non-negative integer string`);
454
+ return BigInt(text).toString();
455
+ }
456
+ function normalizeDateTime(value, name) {
457
+ const date = value instanceof Date ? value : new Date(String(value ?? ""));
458
+ if (!Number.isFinite(date.getTime()))
459
+ throw new Error(`${name} must be a valid date`);
460
+ return date.toISOString();
461
+ }
462
+ function normalizeTimeout(value) {
463
+ const timeout = Number(value ?? 60);
464
+ if (!Number.isFinite(timeout) || timeout <= 0)
465
+ return 60;
466
+ return Math.floor(timeout);
467
+ }
468
+ function requiredString(value, name) {
469
+ const text = String(value ?? "").trim();
470
+ if (!text)
471
+ throw new Error(`${name} is required`);
472
+ return text;
473
+ }
474
+ function looksBase64(value) {
475
+ return /^[A-Za-z0-9+/]+={0,2}$/u.test(value) || /^[A-Za-z0-9_-]+={0,2}$/u.test(value);
476
+ }
477
+ function sha256Hex(value) {
478
+ return (0, node_crypto_1.createHash)("sha256").update(value, "utf8").digest("hex");
479
+ }
480
+ function normalizeHexHash(value) {
481
+ return value.trim().toLowerCase().replace(/^0x/u, "");
482
+ }
@@ -1,4 +1,5 @@
1
1
  import { ElementsRpcClient } from "../core/rpc";
2
+ export * from "./atomicDvp";
2
3
  export declare const LIQUID_X402_SCHEME: "exact-liquid-pset";
3
4
  export declare const LIQUID_X402_VERSION: 1;
4
5
  export declare const LIQUID_X402_DEFAULT_NETWORK: "liquidtestnet";
@@ -54,7 +55,7 @@ export interface LiquidX402PaymentPayload {
54
55
  scheme: typeof LIQUID_X402_SCHEME;
55
56
  network: LiquidX402Network;
56
57
  paymentRequestId: string;
57
- asset: LiquidX402AssetKey;
58
+ asset: LiquidX402AssetKey | "custom" | string;
58
59
  assetId: string;
59
60
  amountAtomic: string;
60
61
  payTo: string;
@@ -83,6 +84,25 @@ export interface LiquidX402BuildPaymentFromPsetInput {
83
84
  payer?: string;
84
85
  summaryHash?: string;
85
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
+ };
86
106
  export interface LiquidX402PrepareLwkWasmPaymentInput {
87
107
  requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
88
108
  mnemonic: string;
@@ -185,6 +205,7 @@ export declare function encodeLiquidXPayment(payload: LiquidX402PaymentPayload):
185
205
  export declare function decodeLiquidXPayment(raw: string): LiquidX402PaymentPayload | null;
186
206
  export declare function prepareLiquidX402PsetPayment(rpc: ElementsRpcClient, input: LiquidX402PreparePsetPaymentInput): Promise<LiquidX402PreparePsetPaymentResult>;
187
207
  export declare function buildLiquidX402PaymentFromPset(input: LiquidX402BuildPaymentFromPsetInput): LiquidX402PreparePsetPaymentResult;
208
+ export declare function buildLiquidPolicyLockedRedemptionPaymentFromProposal(input: LiquidPolicyLockedRedemptionPaymentFromProposalInput): LiquidPolicyLockedRedemptionPaymentFromProposalResult;
188
209
  export declare function prepareLiquidX402LwkWasmPayment(input: LiquidX402PrepareLwkWasmPaymentInput): Promise<LiquidX402PrepareLwkWasmPaymentResult>;
189
210
  export declare function deriveLiquidX402LwkWasmAddress(input: LiquidX402DeriveLwkWasmAddressInput): Promise<LiquidX402DeriveLwkWasmAddressResult>;
190
211
  export declare function verifyLiquidX402Payment(rpc: ElementsRpcClient | null, input: LiquidX402VerifyPaymentInput): Promise<LiquidX402VerifyPaymentResult>;
@@ -1,4 +1,18 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
17
  exports.LIQUID_X402_ASSETS = exports.LIQUID_X402_DEFAULT_TIMEOUT_SECONDS = exports.LIQUID_X402_DEFAULT_NETWORK = exports.LIQUID_X402_VERSION = exports.LIQUID_X402_SCHEME = void 0;
4
18
  exports.listLiquidX402Assets = listLiquidX402Assets;
@@ -8,6 +22,7 @@ exports.encodeLiquidXPayment = encodeLiquidXPayment;
8
22
  exports.decodeLiquidXPayment = decodeLiquidXPayment;
9
23
  exports.prepareLiquidX402PsetPayment = prepareLiquidX402PsetPayment;
10
24
  exports.buildLiquidX402PaymentFromPset = buildLiquidX402PaymentFromPset;
25
+ exports.buildLiquidPolicyLockedRedemptionPaymentFromProposal = buildLiquidPolicyLockedRedemptionPaymentFromProposal;
11
26
  exports.prepareLiquidX402LwkWasmPayment = prepareLiquidX402LwkWasmPayment;
12
27
  exports.deriveLiquidX402LwkWasmAddress = deriveLiquidX402LwkWasmAddress;
13
28
  exports.verifyLiquidX402Payment = verifyLiquidX402Payment;
@@ -15,6 +30,7 @@ exports.settleLiquidX402Payment = settleLiquidX402Payment;
15
30
  exports.verifyLiquidX402PayloadFields = verifyLiquidX402PayloadFields;
16
31
  exports.buildLiquidPsetSummaryHash = buildLiquidPsetSummaryHash;
17
32
  const node_crypto_1 = require("node:crypto");
33
+ __exportStar(require("./atomicDvp"), exports);
18
34
  exports.LIQUID_X402_SCHEME = "exact-liquid-pset";
19
35
  exports.LIQUID_X402_VERSION = 1;
20
36
  exports.LIQUID_X402_DEFAULT_NETWORK = "liquidtestnet";
@@ -193,6 +209,32 @@ function buildLiquidX402PaymentFromPset(input) {
193
209
  summaryHash,
194
210
  };
195
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
+ }
196
238
  async function prepareLiquidX402LwkWasmPayment(input) {
197
239
  const requirements = coerceRequirements(input.requirements);
198
240
  ensureLwkWasmNodeTimerCompat();
@@ -438,6 +480,108 @@ function mempoolRejectReason(result) {
438
480
  const raw = result;
439
481
  return String(raw["reject-reason"] ?? raw.reject_reason ?? raw.error ?? "mempool_rejected").trim() || "mempool_rejected";
440
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
+ }
441
585
  function coerceRequirements(value) {
442
586
  if (!value || typeof value !== "object" || Array.isArray(value))
443
587
  throw new Error("requirements must be an object");
@@ -548,7 +692,7 @@ function coercePaymentPayload(value) {
548
692
  scheme: exports.LIQUID_X402_SCHEME,
549
693
  network: normalizeNetwork(raw.network),
550
694
  paymentRequestId: String(raw.paymentRequestId ?? ""),
551
- asset: normalizeAssetKey(String(raw.asset ?? "")),
695
+ asset: normalizePaymentAssetKey(String(raw.asset ?? "")),
552
696
  assetId: String(raw.assetId ?? ""),
553
697
  amountAtomic: normalizeAmountAtomic(raw.amountAtomic),
554
698
  payTo: String(raw.payTo ?? ""),
@@ -564,6 +708,19 @@ function normalizeNetwork(input) {
564
708
  return value;
565
709
  throw new Error(`unsupported Liquid x402 network: ${String(input)}`);
566
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
+ }
567
724
  function normalizeAssetKey(input) {
568
725
  const value = String(input ?? "").trim().toLowerCase();
569
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.2",
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": [