@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 +97 -2
- package/dist/client/SimplicityClient.d.ts +1 -0
- package/dist/client/SimplicityClient.js +1 -0
- package/dist/domain/rwaDvp.d.ts +2 -0
- package/dist/domain/rwaDvp.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/x402/atomicDvp.d.ts +197 -0
- package/dist/x402/atomicDvp.js +482 -0
- package/dist/x402/index.d.ts +22 -1
- package/dist/x402/index.js +158 -1
- package/package.json +1 -1
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
|
|
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.
|
|
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),
|
package/dist/domain/rwaDvp.d.ts
CHANGED
|
@@ -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;
|
package/dist/domain/rwaDvp.js
CHANGED
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
|
+
}
|
package/dist/x402/index.d.ts
CHANGED
|
@@ -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>;
|
package/dist/x402/index.js
CHANGED
|
@@ -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:
|
|
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) {
|