@hazbase/simplicity 0.4.3 → 0.4.5
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 +19 -0
- package/dist/client/SimplicityClient.d.ts +1 -0
- package/dist/client/SimplicityClient.js +1 -0
- package/dist/core/executor.js +25 -4
- package/dist/core/types.d.ts +2 -1
- package/dist/domain/policies.d.ts +9 -1
- package/dist/domain/policies.js +164 -12
- package/dist/x402/index.d.ts +21 -1
- package/dist/x402/index.js +143 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -175,6 +175,25 @@ testnet issuer or deployment uses a different USDt asset id, set
|
|
|
175
175
|
payment requirements will preserve that asset id instead of replacing it with
|
|
176
176
|
the SDK's default registry id.
|
|
177
177
|
|
|
178
|
+
For policy-locked Liquid RWA positions, redemption can be represented as a
|
|
179
|
+
Liquid x402 request with `extra.redemptionSource.type:
|
|
180
|
+
"policy_locked_position"`. Wallets can use
|
|
181
|
+
`buildLiquidPolicyLockedRedemptionPaymentFromProposal(...)` when a service has
|
|
182
|
+
already prepared the Simplicity policy-spend PSET proposal. The helper binds the
|
|
183
|
+
custom RWA asset, policy position, vault output, expiry, and summary hash into
|
|
184
|
+
the `X-PAYMENT` payload, and rejects proposals that still require holder-side
|
|
185
|
+
Simplicity witness construction.
|
|
186
|
+
|
|
187
|
+
Services can prepare that policy-spend proposal with
|
|
188
|
+
`sdk.policies.inspectTransfer(...)` or `sdk.policies.executeTransfer(...)`.
|
|
189
|
+
When the current policy state carries a custom Liquid asset id, the policy
|
|
190
|
+
executor automatically uses the SDK multi-asset PSET path instead of the legacy
|
|
191
|
+
L-BTC-only spend path. Pass `extraInputs` for the L-BTC fee/sponsor inputs and
|
|
192
|
+
`contractInput` when the policy position UTXO should be selected by outpoint,
|
|
193
|
+
asset id, amount, blinding data, or sequence. If the policy UTXO amount is not
|
|
194
|
+
fully transferred, pass an explicit `changeAddress`; exact-position redemptions
|
|
195
|
+
do not need one.
|
|
196
|
+
|
|
178
197
|
Typical entrypoints:
|
|
179
198
|
- `sdk.rwaDvp.definePurchase(...)`
|
|
180
199
|
- `sdk.rwaDvp.buildPaymentRequirements(...)`
|
|
@@ -24,6 +24,7 @@ export declare class SimplicityClient {
|
|
|
24
24
|
decodePayment: typeof liquidX402.decodeLiquidXPayment;
|
|
25
25
|
preparePsetPayment: (input: liquidX402.LiquidX402PreparePsetPaymentInput) => Promise<liquidX402.LiquidX402PreparePsetPaymentResult>;
|
|
26
26
|
buildPaymentFromPset: typeof liquidX402.buildLiquidX402PaymentFromPset;
|
|
27
|
+
buildPolicyLockedRedemptionPaymentFromProposal: typeof liquidX402.buildLiquidPolicyLockedRedemptionPaymentFromProposal;
|
|
27
28
|
prepareLwkWasmPayment: typeof liquidX402.prepareLiquidX402LwkWasmPayment;
|
|
28
29
|
deriveLwkWasmAddress: typeof liquidX402.deriveLiquidX402LwkWasmAddress;
|
|
29
30
|
verify: (input: liquidX402.LiquidX402VerifyPaymentInput) => Promise<liquidX402.LiquidX402VerifyPaymentResult>;
|
|
@@ -74,6 +74,7 @@ class SimplicityClient {
|
|
|
74
74
|
decodePayment: liquidX402.decodeLiquidXPayment,
|
|
75
75
|
preparePsetPayment: async (input) => liquidX402.prepareLiquidX402PsetPayment(this.rpc, input),
|
|
76
76
|
buildPaymentFromPset: liquidX402.buildLiquidX402PaymentFromPset,
|
|
77
|
+
buildPolicyLockedRedemptionPaymentFromProposal: liquidX402.buildLiquidPolicyLockedRedemptionPaymentFromProposal,
|
|
77
78
|
prepareLwkWasmPayment: liquidX402.prepareLiquidX402LwkWasmPayment,
|
|
78
79
|
deriveLwkWasmAddress: liquidX402.deriveLiquidX402LwkWasmAddress,
|
|
79
80
|
verify: async (input) => liquidX402.verifyLiquidX402Payment(this.rpc, input),
|
package/dist/core/executor.js
CHANGED
|
@@ -429,7 +429,7 @@ async function findContractUtxos(config, artifact) {
|
|
|
429
429
|
return scanUtxosByAddress(config, artifact.compiled.contractAddress);
|
|
430
430
|
}
|
|
431
431
|
function findRequestedContractUtxo(utxos, input) {
|
|
432
|
-
if (!input)
|
|
432
|
+
if (!input?.txid)
|
|
433
433
|
return null;
|
|
434
434
|
const requested = utxos.find((utxo) => (utxo.txid === input.txid
|
|
435
435
|
&& (input.vout === undefined || utxo.vout === input.vout))) ?? null;
|
|
@@ -441,6 +441,21 @@ function findRequestedContractUtxo(utxos, input) {
|
|
|
441
441
|
sat: input.amountSat ?? requested.sat,
|
|
442
442
|
};
|
|
443
443
|
}
|
|
444
|
+
function filterContractUtxosByRequestedAsset(utxos, input) {
|
|
445
|
+
if (!input)
|
|
446
|
+
return utxos;
|
|
447
|
+
return utxos.filter((utxo) => {
|
|
448
|
+
if (input.txid && utxo.txid !== input.txid)
|
|
449
|
+
return false;
|
|
450
|
+
if (input.vout !== undefined && utxo.vout !== input.vout)
|
|
451
|
+
return false;
|
|
452
|
+
if (input.asset && normalizeAssetId(utxo.asset) !== normalizeAssetId(input.asset))
|
|
453
|
+
return false;
|
|
454
|
+
if (input.amountSat !== undefined && utxo.sat !== input.amountSat)
|
|
455
|
+
return false;
|
|
456
|
+
return true;
|
|
457
|
+
});
|
|
458
|
+
}
|
|
444
459
|
function buildMultiAssetBlindingSecrets(input) {
|
|
445
460
|
const secrets = [];
|
|
446
461
|
if (input.contractInput?.rawTxHex && input.contractInput.blindingPrivateKey) {
|
|
@@ -472,9 +487,15 @@ async function buildMultiAssetExecutionState(config, artifact, input) {
|
|
|
472
487
|
const contractUtxos = await scanUtxosByAddress(config, artifact.compiled.contractAddress);
|
|
473
488
|
const feeSat = input.feeSat ?? config.defaults?.feeSat ?? DEFAULT_FEE_SAT;
|
|
474
489
|
const requested = findRequestedContractUtxo(contractUtxos, input.contractInput);
|
|
475
|
-
const
|
|
490
|
+
const candidateContractUtxos = filterContractUtxosByRequestedAsset(contractUtxos, input.contractInput);
|
|
491
|
+
const contractUtxo = requested ?? chooseUtxo(candidateContractUtxos, 1, config.defaults?.utxoPolicy ?? "smallest_over");
|
|
476
492
|
if (!contractUtxo) {
|
|
477
|
-
throw new errors_1.UtxoNotFoundError(`No contract UTXO found for address=${artifact.compiled.contractAddress}
|
|
493
|
+
throw new errors_1.UtxoNotFoundError(`No contract UTXO found for address=${artifact.compiled.contractAddress}`, {
|
|
494
|
+
requestedAsset: input.contractInput?.asset,
|
|
495
|
+
requestedAmountSat: input.contractInput?.amountSat,
|
|
496
|
+
requestedTxid: input.contractInput?.txid,
|
|
497
|
+
requestedVout: input.contractInput?.vout,
|
|
498
|
+
});
|
|
478
499
|
}
|
|
479
500
|
const extraInputs = input.extraInputs ?? [];
|
|
480
501
|
const inputTotals = new Map();
|
|
@@ -521,7 +542,7 @@ async function buildMultiAssetExecutionState(config, artifact, input) {
|
|
|
521
542
|
{
|
|
522
543
|
txid: contractUtxo.txid,
|
|
523
544
|
vout: contractUtxo.vout,
|
|
524
|
-
sequence: input.
|
|
545
|
+
sequence: input.contractInput?.sequence ?? DEFAULT_SEQUENCE,
|
|
525
546
|
},
|
|
526
547
|
...extraInputs.map((extra) => ({
|
|
527
548
|
txid: extra.txid,
|
package/dist/core/types.d.ts
CHANGED
|
@@ -332,10 +332,11 @@ export interface MultiAssetContractCallInput {
|
|
|
332
332
|
signer: SignerConfig;
|
|
333
333
|
witness?: WitnessConfig;
|
|
334
334
|
contractInput?: {
|
|
335
|
-
txid
|
|
335
|
+
txid?: string;
|
|
336
336
|
vout?: number;
|
|
337
337
|
asset?: string;
|
|
338
338
|
amountSat?: number;
|
|
339
|
+
sequence?: number;
|
|
339
340
|
rawTxHex?: string;
|
|
340
341
|
blindingPrivateKey?: string;
|
|
341
342
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SimplicityClient } from "../client/SimplicityClient";
|
|
2
|
-
import type { BondOutputBindingMode, OutputRawFields, PolicyOutputAmountForm, PolicyOutputAssetForm, PolicyEvidenceBundle, PolicyEvidenceBundleSchemaVersion, PolicyOutputBindingReasonCode, PolicyOutputBindingSupportedForm, PolicyOutputDescriptor, PolicyOutputNonceForm, PolicyReceiver, PolicyOutputRangeProofForm, PolicyState, PolicyTemplateDocument, PolicyTemplateManifest, PolicyTemplateManifestValidationResult, PolicyTemplateManifestVersion, PolicyTemplateInput, PolicyTransferDescriptor, PolicyVerificationReportSchemaVersion, PropagationMode, SimplicityArtifact } from "../core/types";
|
|
2
|
+
import type { BondOutputBindingMode, OutputRawFields, PolicyOutputAmountForm, PolicyOutputAssetForm, PolicyEvidenceBundle, PolicyEvidenceBundleSchemaVersion, PolicyOutputBindingReasonCode, PolicyOutputBindingSupportedForm, PolicyOutputDescriptor, PolicyOutputNonceForm, PolicyReceiver, PolicyOutputRangeProofForm, PolicyState, PolicyTemplateDocument, PolicyTemplateManifest, PolicyTemplateManifestValidationResult, PolicyTemplateManifestVersion, PolicyTemplateInput, PolicyTransferDescriptor, PolicyVerificationReportSchemaVersion, PropagationMode, MultiAssetContractCallInput, MultiAssetContractInput, SimplicityArtifact } from "../core/types";
|
|
3
3
|
export declare const POLICY_TEMPLATE_MANIFEST_SCHEMA_VERSION: PolicyTemplateManifestVersion;
|
|
4
4
|
export declare const POLICY_VERIFICATION_REPORT_SCHEMA_VERSION: PolicyVerificationReportSchemaVersion;
|
|
5
5
|
export declare const POLICY_EVIDENCE_BUNDLE_SCHEMA_VERSION: PolicyEvidenceBundleSchemaVersion;
|
|
@@ -576,6 +576,10 @@ export declare function executeDirectTransfer(sdk: SimplicityClient, input: {
|
|
|
576
576
|
broadcast?: boolean;
|
|
577
577
|
feeSat?: number;
|
|
578
578
|
utxoPolicy?: "smallest_over" | "largest" | "newest";
|
|
579
|
+
contractInput?: MultiAssetContractCallInput["contractInput"];
|
|
580
|
+
extraInputs?: MultiAssetContractInput[];
|
|
581
|
+
changeAddress?: string;
|
|
582
|
+
useMultiAssetExecutor?: boolean;
|
|
579
583
|
}): Promise<{
|
|
580
584
|
mode: "direct-hop";
|
|
581
585
|
prepared: {
|
|
@@ -912,6 +916,10 @@ export declare function executeTransfer(sdk: SimplicityClient, input: {
|
|
|
912
916
|
broadcast?: boolean;
|
|
913
917
|
feeSat?: number;
|
|
914
918
|
utxoPolicy?: "smallest_over" | "largest" | "newest";
|
|
919
|
+
contractInput?: MultiAssetContractCallInput["contractInput"];
|
|
920
|
+
extraInputs?: MultiAssetContractInput[];
|
|
921
|
+
changeAddress?: string;
|
|
922
|
+
useMultiAssetExecutor?: boolean;
|
|
915
923
|
}): Promise<{
|
|
916
924
|
mode: "plain-exit";
|
|
917
925
|
prepared: {
|
package/dist/domain/policies.js
CHANGED
|
@@ -33,6 +33,7 @@ const node_fs_1 = require("node:fs");
|
|
|
33
33
|
const node_path_1 = __importDefault(require("node:path"));
|
|
34
34
|
const summary_1 = require("../core/summary");
|
|
35
35
|
const errors_1 = require("../core/errors");
|
|
36
|
+
const executor_1 = require("../core/executor");
|
|
36
37
|
const outputBinding_1 = require("../core/outputBinding");
|
|
37
38
|
const reporting_1 = require("../core/reporting");
|
|
38
39
|
function resolvePolicyDocsAsset(filename) {
|
|
@@ -602,6 +603,53 @@ function buildPolicyRecursiveWitness(prepared) {
|
|
|
602
603
|
},
|
|
603
604
|
};
|
|
604
605
|
}
|
|
606
|
+
function isBitcoinPolicyAsset(assetId) {
|
|
607
|
+
return assetId.trim().toLowerCase() === "bitcoin";
|
|
608
|
+
}
|
|
609
|
+
function shouldUseMultiAssetPolicyExecutor(assetId, input) {
|
|
610
|
+
if (input.useMultiAssetExecutor === true)
|
|
611
|
+
return true;
|
|
612
|
+
if (!isBitcoinPolicyAsset(assetId))
|
|
613
|
+
return true;
|
|
614
|
+
if (input.contractInput?.asset)
|
|
615
|
+
return true;
|
|
616
|
+
if ((input.extraInputs?.length ?? 0) > 0)
|
|
617
|
+
return true;
|
|
618
|
+
return Boolean(input.changeAddress);
|
|
619
|
+
}
|
|
620
|
+
function buildPolicyMultiAssetCallInput(input) {
|
|
621
|
+
const contractAmountSat = input.contractInput?.amountSat ?? input.currentState.amountSat;
|
|
622
|
+
if (contractAmountSat !== input.outputAmountSat && !input.changeAddress) {
|
|
623
|
+
throw new errors_1.ValidationError("changeAddress is required when policy contract input amount differs from the transfer output amount", {
|
|
624
|
+
contractAmountSat,
|
|
625
|
+
outputAmountSat: input.outputAmountSat,
|
|
626
|
+
assetId: input.currentState.assetId,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
return {
|
|
630
|
+
wallet: input.wallet,
|
|
631
|
+
signer: input.signer,
|
|
632
|
+
contractInput: {
|
|
633
|
+
...(input.contractInput ?? {}),
|
|
634
|
+
asset: input.contractInput?.asset ?? input.currentState.assetId,
|
|
635
|
+
amountSat: contractAmountSat,
|
|
636
|
+
sequence: input.contractInput?.sequence ?? input.contractSequence,
|
|
637
|
+
},
|
|
638
|
+
...(input.extraInputs ? { extraInputs: input.extraInputs } : {}),
|
|
639
|
+
outputs: [
|
|
640
|
+
{
|
|
641
|
+
address: input.outputAddress,
|
|
642
|
+
asset: input.currentState.assetId,
|
|
643
|
+
amountSat: input.outputAmountSat,
|
|
644
|
+
},
|
|
645
|
+
],
|
|
646
|
+
feeSat: input.feeSat,
|
|
647
|
+
...(input.changeAddress ? { changeAddress: input.changeAddress } : {}),
|
|
648
|
+
purpose: input.purpose,
|
|
649
|
+
...(input.broadcast !== undefined ? { broadcast: input.broadcast } : {}),
|
|
650
|
+
witness: input.witness,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
605
653
|
function camelToUpperSnake(value) {
|
|
606
654
|
return value
|
|
607
655
|
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
@@ -1396,6 +1444,24 @@ async function inspectDirectTransfer(sdk, input) {
|
|
|
1396
1444
|
});
|
|
1397
1445
|
}
|
|
1398
1446
|
const currentContract = sdk.fromArtifact(currentArtifact);
|
|
1447
|
+
const currentState = prepared.current.stateValue;
|
|
1448
|
+
const directWitness = buildPolicyDirectWitness(prepared);
|
|
1449
|
+
if (shouldUseMultiAssetPolicyExecutor(currentState.assetId, input)) {
|
|
1450
|
+
const inspect = await (0, executor_1.inspectMultiAssetContractCall)(sdk.config, currentArtifact, buildPolicyMultiAssetCallInput({
|
|
1451
|
+
...input,
|
|
1452
|
+
currentState,
|
|
1453
|
+
outputAddress: prepared.nextCompiled.contractAddress,
|
|
1454
|
+
outputAmountSat: input.nextAmountSat,
|
|
1455
|
+
contractSequence: resolvePolicySequence(currentState),
|
|
1456
|
+
witness: directWitness,
|
|
1457
|
+
purpose: "sdk_policy_direct_transfer",
|
|
1458
|
+
}));
|
|
1459
|
+
return {
|
|
1460
|
+
mode: "direct-hop",
|
|
1461
|
+
prepared,
|
|
1462
|
+
inspect,
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1399
1465
|
const inspect = await currentContract.inspectCall({
|
|
1400
1466
|
wallet: input.wallet,
|
|
1401
1467
|
toAddress: prepared.nextCompiled.contractAddress,
|
|
@@ -1403,8 +1469,8 @@ async function inspectDirectTransfer(sdk, input) {
|
|
|
1403
1469
|
sendAmount: satToBtcAmount(input.nextAmountSat),
|
|
1404
1470
|
feeSat: input.feeSat,
|
|
1405
1471
|
utxoPolicy: input.utxoPolicy,
|
|
1406
|
-
sequence: resolvePolicySequence(
|
|
1407
|
-
witness:
|
|
1472
|
+
sequence: resolvePolicySequence(currentState),
|
|
1473
|
+
witness: directWitness,
|
|
1408
1474
|
});
|
|
1409
1475
|
return {
|
|
1410
1476
|
mode: "direct-hop",
|
|
@@ -1422,6 +1488,24 @@ async function executeDirectTransfer(sdk, input) {
|
|
|
1422
1488
|
});
|
|
1423
1489
|
}
|
|
1424
1490
|
const currentContract = sdk.fromArtifact(currentArtifact);
|
|
1491
|
+
const currentState = prepared.current.stateValue;
|
|
1492
|
+
const directWitness = buildPolicyDirectWitness(prepared);
|
|
1493
|
+
if (shouldUseMultiAssetPolicyExecutor(currentState.assetId, input)) {
|
|
1494
|
+
const execution = await (0, executor_1.executeMultiAssetContractCall)(sdk.config, currentArtifact, buildPolicyMultiAssetCallInput({
|
|
1495
|
+
...input,
|
|
1496
|
+
currentState,
|
|
1497
|
+
outputAddress: prepared.nextCompiled.contractAddress,
|
|
1498
|
+
outputAmountSat: input.nextAmountSat,
|
|
1499
|
+
contractSequence: resolvePolicySequence(currentState),
|
|
1500
|
+
witness: directWitness,
|
|
1501
|
+
purpose: "sdk_policy_direct_transfer",
|
|
1502
|
+
}));
|
|
1503
|
+
return {
|
|
1504
|
+
mode: "direct-hop",
|
|
1505
|
+
prepared,
|
|
1506
|
+
execution,
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1425
1509
|
const execution = await currentContract.execute({
|
|
1426
1510
|
wallet: input.wallet,
|
|
1427
1511
|
toAddress: prepared.nextCompiled.contractAddress,
|
|
@@ -1430,8 +1514,8 @@ async function executeDirectTransfer(sdk, input) {
|
|
|
1430
1514
|
feeSat: input.feeSat,
|
|
1431
1515
|
broadcast: input.broadcast,
|
|
1432
1516
|
utxoPolicy: input.utxoPolicy,
|
|
1433
|
-
sequence: resolvePolicySequence(
|
|
1434
|
-
witness:
|
|
1517
|
+
sequence: resolvePolicySequence(currentState),
|
|
1518
|
+
witness: directWitness,
|
|
1435
1519
|
});
|
|
1436
1520
|
return {
|
|
1437
1521
|
mode: "direct-hop",
|
|
@@ -1500,7 +1584,25 @@ async function inspectTransfer(sdk, input) {
|
|
|
1500
1584
|
});
|
|
1501
1585
|
}
|
|
1502
1586
|
const currentContract = sdk.fromArtifact(currentArtifact);
|
|
1587
|
+
const currentState = prepared.current.stateValue;
|
|
1588
|
+
const recursiveWitness = buildPolicyRecursiveWitness(prepared);
|
|
1503
1589
|
if (!prepared.nextCompiled) {
|
|
1590
|
+
if (shouldUseMultiAssetPolicyExecutor(currentState.assetId, input)) {
|
|
1591
|
+
const inspect = await (0, executor_1.inspectMultiAssetContractCall)(sdk.config, currentArtifact, buildPolicyMultiAssetCallInput({
|
|
1592
|
+
...input,
|
|
1593
|
+
currentState,
|
|
1594
|
+
outputAddress: input.nextReceiver.address,
|
|
1595
|
+
outputAmountSat: input.nextAmountSat,
|
|
1596
|
+
contractSequence: resolvePolicySequence(currentState),
|
|
1597
|
+
witness: recursiveWitness,
|
|
1598
|
+
purpose: "sdk_policy_transfer_plain_exit",
|
|
1599
|
+
}));
|
|
1600
|
+
return {
|
|
1601
|
+
mode: "plain-exit",
|
|
1602
|
+
prepared,
|
|
1603
|
+
inspect,
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1504
1606
|
const inspect = await currentContract.inspectCall({
|
|
1505
1607
|
wallet: input.wallet,
|
|
1506
1608
|
toAddress: input.nextReceiver.address,
|
|
@@ -1508,8 +1610,8 @@ async function inspectTransfer(sdk, input) {
|
|
|
1508
1610
|
sendAmount: satToBtcAmount(input.nextAmountSat),
|
|
1509
1611
|
feeSat: input.feeSat,
|
|
1510
1612
|
utxoPolicy: input.utxoPolicy,
|
|
1511
|
-
sequence: resolvePolicySequence(
|
|
1512
|
-
witness:
|
|
1613
|
+
sequence: resolvePolicySequence(currentState),
|
|
1614
|
+
witness: recursiveWitness,
|
|
1513
1615
|
});
|
|
1514
1616
|
return {
|
|
1515
1617
|
mode: "plain-exit",
|
|
@@ -1517,6 +1619,22 @@ async function inspectTransfer(sdk, input) {
|
|
|
1517
1619
|
inspect,
|
|
1518
1620
|
};
|
|
1519
1621
|
}
|
|
1622
|
+
if (shouldUseMultiAssetPolicyExecutor(currentState.assetId, input)) {
|
|
1623
|
+
const inspect = await (0, executor_1.inspectMultiAssetContractCall)(sdk.config, currentArtifact, buildPolicyMultiAssetCallInput({
|
|
1624
|
+
...input,
|
|
1625
|
+
currentState,
|
|
1626
|
+
outputAddress: prepared.nextCompiled.contractAddress,
|
|
1627
|
+
outputAmountSat: input.nextAmountSat,
|
|
1628
|
+
contractSequence: resolvePolicySequence(currentState),
|
|
1629
|
+
witness: recursiveWitness,
|
|
1630
|
+
purpose: "sdk_policy_transfer",
|
|
1631
|
+
}));
|
|
1632
|
+
return {
|
|
1633
|
+
mode: prepared.verificationReport.enforcement,
|
|
1634
|
+
prepared,
|
|
1635
|
+
inspect,
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1520
1638
|
const inspect = await currentContract.inspectCall({
|
|
1521
1639
|
wallet: input.wallet,
|
|
1522
1640
|
toAddress: prepared.nextCompiled.contractAddress,
|
|
@@ -1524,8 +1642,8 @@ async function inspectTransfer(sdk, input) {
|
|
|
1524
1642
|
sendAmount: satToBtcAmount(input.nextAmountSat),
|
|
1525
1643
|
feeSat: input.feeSat,
|
|
1526
1644
|
utxoPolicy: input.utxoPolicy,
|
|
1527
|
-
sequence: resolvePolicySequence(
|
|
1528
|
-
witness:
|
|
1645
|
+
sequence: resolvePolicySequence(currentState),
|
|
1646
|
+
witness: recursiveWitness,
|
|
1529
1647
|
});
|
|
1530
1648
|
return {
|
|
1531
1649
|
mode: prepared.verificationReport.enforcement,
|
|
@@ -1543,7 +1661,25 @@ async function executeTransfer(sdk, input) {
|
|
|
1543
1661
|
});
|
|
1544
1662
|
}
|
|
1545
1663
|
const currentContract = sdk.fromArtifact(currentArtifact);
|
|
1664
|
+
const currentState = prepared.current.stateValue;
|
|
1665
|
+
const recursiveWitness = buildPolicyRecursiveWitness(prepared);
|
|
1546
1666
|
if (!prepared.nextCompiled) {
|
|
1667
|
+
if (shouldUseMultiAssetPolicyExecutor(currentState.assetId, input)) {
|
|
1668
|
+
const execution = await (0, executor_1.executeMultiAssetContractCall)(sdk.config, currentArtifact, buildPolicyMultiAssetCallInput({
|
|
1669
|
+
...input,
|
|
1670
|
+
currentState,
|
|
1671
|
+
outputAddress: input.nextReceiver.address,
|
|
1672
|
+
outputAmountSat: input.nextAmountSat,
|
|
1673
|
+
contractSequence: resolvePolicySequence(currentState),
|
|
1674
|
+
witness: recursiveWitness,
|
|
1675
|
+
purpose: "sdk_policy_transfer_plain_exit",
|
|
1676
|
+
}));
|
|
1677
|
+
return {
|
|
1678
|
+
mode: "plain-exit",
|
|
1679
|
+
prepared,
|
|
1680
|
+
execution,
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1547
1683
|
const execution = await currentContract.execute({
|
|
1548
1684
|
wallet: input.wallet,
|
|
1549
1685
|
toAddress: input.nextReceiver.address,
|
|
@@ -1552,8 +1688,8 @@ async function executeTransfer(sdk, input) {
|
|
|
1552
1688
|
feeSat: input.feeSat,
|
|
1553
1689
|
broadcast: input.broadcast,
|
|
1554
1690
|
utxoPolicy: input.utxoPolicy,
|
|
1555
|
-
sequence: resolvePolicySequence(
|
|
1556
|
-
witness:
|
|
1691
|
+
sequence: resolvePolicySequence(currentState),
|
|
1692
|
+
witness: recursiveWitness,
|
|
1557
1693
|
});
|
|
1558
1694
|
return {
|
|
1559
1695
|
mode: "plain-exit",
|
|
@@ -1561,6 +1697,22 @@ async function executeTransfer(sdk, input) {
|
|
|
1561
1697
|
execution,
|
|
1562
1698
|
};
|
|
1563
1699
|
}
|
|
1700
|
+
if (shouldUseMultiAssetPolicyExecutor(currentState.assetId, input)) {
|
|
1701
|
+
const execution = await (0, executor_1.executeMultiAssetContractCall)(sdk.config, currentArtifact, buildPolicyMultiAssetCallInput({
|
|
1702
|
+
...input,
|
|
1703
|
+
currentState,
|
|
1704
|
+
outputAddress: prepared.nextCompiled.contractAddress,
|
|
1705
|
+
outputAmountSat: input.nextAmountSat,
|
|
1706
|
+
contractSequence: resolvePolicySequence(currentState),
|
|
1707
|
+
witness: recursiveWitness,
|
|
1708
|
+
purpose: "sdk_policy_transfer",
|
|
1709
|
+
}));
|
|
1710
|
+
return {
|
|
1711
|
+
mode: prepared.verificationReport.enforcement,
|
|
1712
|
+
prepared,
|
|
1713
|
+
execution,
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1564
1716
|
const execution = await currentContract.execute({
|
|
1565
1717
|
wallet: input.wallet,
|
|
1566
1718
|
toAddress: prepared.nextCompiled.contractAddress,
|
|
@@ -1569,8 +1721,8 @@ async function executeTransfer(sdk, input) {
|
|
|
1569
1721
|
feeSat: input.feeSat,
|
|
1570
1722
|
broadcast: input.broadcast,
|
|
1571
1723
|
utxoPolicy: input.utxoPolicy,
|
|
1572
|
-
sequence: resolvePolicySequence(
|
|
1573
|
-
witness:
|
|
1724
|
+
sequence: resolvePolicySequence(currentState),
|
|
1725
|
+
witness: recursiveWitness,
|
|
1574
1726
|
});
|
|
1575
1727
|
return {
|
|
1576
1728
|
mode: prepared.verificationReport.enforcement,
|
package/dist/x402/index.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export interface LiquidX402PaymentPayload {
|
|
|
55
55
|
scheme: typeof LIQUID_X402_SCHEME;
|
|
56
56
|
network: LiquidX402Network;
|
|
57
57
|
paymentRequestId: string;
|
|
58
|
-
asset: LiquidX402AssetKey;
|
|
58
|
+
asset: LiquidX402AssetKey | "custom" | string;
|
|
59
59
|
assetId: string;
|
|
60
60
|
amountAtomic: string;
|
|
61
61
|
payTo: string;
|
|
@@ -84,6 +84,25 @@ export interface LiquidX402BuildPaymentFromPsetInput {
|
|
|
84
84
|
payer?: string;
|
|
85
85
|
summaryHash?: string;
|
|
86
86
|
}
|
|
87
|
+
export interface LiquidPolicyLockedRedemptionSpendProposal {
|
|
88
|
+
mode?: "service_prepared_policy_spend_v1" | string;
|
|
89
|
+
psetBase64?: string;
|
|
90
|
+
summaryHash?: string;
|
|
91
|
+
holderSignatureRequired?: boolean;
|
|
92
|
+
metadata?: Record<string, unknown>;
|
|
93
|
+
}
|
|
94
|
+
export interface LiquidPolicyLockedRedemptionPaymentFromProposalInput {
|
|
95
|
+
requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
|
|
96
|
+
proposal?: LiquidPolicyLockedRedemptionSpendProposal | Record<string, unknown>;
|
|
97
|
+
proposalPsetBase64?: string;
|
|
98
|
+
summaryHash?: string;
|
|
99
|
+
payer?: string;
|
|
100
|
+
metadata?: Record<string, unknown>;
|
|
101
|
+
}
|
|
102
|
+
export type LiquidPolicyLockedRedemptionPaymentFromProposalResult = LiquidX402PreparePsetPaymentResult & {
|
|
103
|
+
proposalMode?: string;
|
|
104
|
+
redemptionSource: Record<string, unknown>;
|
|
105
|
+
};
|
|
87
106
|
export interface LiquidX402PrepareLwkWasmPaymentInput {
|
|
88
107
|
requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
|
|
89
108
|
mnemonic: string;
|
|
@@ -186,6 +205,7 @@ export declare function encodeLiquidXPayment(payload: LiquidX402PaymentPayload):
|
|
|
186
205
|
export declare function decodeLiquidXPayment(raw: string): LiquidX402PaymentPayload | null;
|
|
187
206
|
export declare function prepareLiquidX402PsetPayment(rpc: ElementsRpcClient, input: LiquidX402PreparePsetPaymentInput): Promise<LiquidX402PreparePsetPaymentResult>;
|
|
188
207
|
export declare function buildLiquidX402PaymentFromPset(input: LiquidX402BuildPaymentFromPsetInput): LiquidX402PreparePsetPaymentResult;
|
|
208
|
+
export declare function buildLiquidPolicyLockedRedemptionPaymentFromProposal(input: LiquidPolicyLockedRedemptionPaymentFromProposalInput): LiquidPolicyLockedRedemptionPaymentFromProposalResult;
|
|
189
209
|
export declare function prepareLiquidX402LwkWasmPayment(input: LiquidX402PrepareLwkWasmPaymentInput): Promise<LiquidX402PrepareLwkWasmPaymentResult>;
|
|
190
210
|
export declare function deriveLiquidX402LwkWasmAddress(input: LiquidX402DeriveLwkWasmAddressInput): Promise<LiquidX402DeriveLwkWasmAddressResult>;
|
|
191
211
|
export declare function verifyLiquidX402Payment(rpc: ElementsRpcClient | null, input: LiquidX402VerifyPaymentInput): Promise<LiquidX402VerifyPaymentResult>;
|
package/dist/x402/index.js
CHANGED
|
@@ -22,6 +22,7 @@ exports.encodeLiquidXPayment = encodeLiquidXPayment;
|
|
|
22
22
|
exports.decodeLiquidXPayment = decodeLiquidXPayment;
|
|
23
23
|
exports.prepareLiquidX402PsetPayment = prepareLiquidX402PsetPayment;
|
|
24
24
|
exports.buildLiquidX402PaymentFromPset = buildLiquidX402PaymentFromPset;
|
|
25
|
+
exports.buildLiquidPolicyLockedRedemptionPaymentFromProposal = buildLiquidPolicyLockedRedemptionPaymentFromProposal;
|
|
25
26
|
exports.prepareLiquidX402LwkWasmPayment = prepareLiquidX402LwkWasmPayment;
|
|
26
27
|
exports.deriveLiquidX402LwkWasmAddress = deriveLiquidX402LwkWasmAddress;
|
|
27
28
|
exports.verifyLiquidX402Payment = verifyLiquidX402Payment;
|
|
@@ -208,6 +209,32 @@ function buildLiquidX402PaymentFromPset(input) {
|
|
|
208
209
|
summaryHash,
|
|
209
210
|
};
|
|
210
211
|
}
|
|
212
|
+
function buildLiquidPolicyLockedRedemptionPaymentFromProposal(input) {
|
|
213
|
+
const requirements = coercePolicyLockedRedemptionRequirements(input.requirements);
|
|
214
|
+
const proposal = coercePolicyLockedRedemptionProposal(input.proposal ?? getRecordValue(input.requirements, "policySpendProposal") ?? getExtraRecord(input.requirements).policySpendProposal, input.proposalPsetBase64);
|
|
215
|
+
const summaryHash = input.summaryHash ?? proposal.summaryHash ?? requirements.summaryHash ?? buildLiquidPolicyLockedRedemptionSummaryHash(requirements);
|
|
216
|
+
const paymentPayload = {
|
|
217
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
218
|
+
network: requirements.network,
|
|
219
|
+
paymentRequestId: requirements.paymentRequestId,
|
|
220
|
+
asset: requirements.assetKey,
|
|
221
|
+
assetId: requirements.assetId,
|
|
222
|
+
amountAtomic: requirements.amountAtomic,
|
|
223
|
+
payTo: requirements.payTo,
|
|
224
|
+
psetBase64: proposal.psetBase64,
|
|
225
|
+
summaryHash,
|
|
226
|
+
...(input.payer ? { payer: input.payer } : {}),
|
|
227
|
+
expiresAt: requirements.expiresAt,
|
|
228
|
+
};
|
|
229
|
+
return {
|
|
230
|
+
paymentPayload,
|
|
231
|
+
xPayment: encodeLiquidXPayment(paymentPayload),
|
|
232
|
+
psetBase64: proposal.psetBase64,
|
|
233
|
+
summaryHash,
|
|
234
|
+
...(proposal.mode ? { proposalMode: proposal.mode } : {}),
|
|
235
|
+
redemptionSource: requirements.redemptionSource,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
211
238
|
async function prepareLiquidX402LwkWasmPayment(input) {
|
|
212
239
|
const requirements = coerceRequirements(input.requirements);
|
|
213
240
|
ensureLwkWasmNodeTimerCompat();
|
|
@@ -453,6 +480,108 @@ function mempoolRejectReason(result) {
|
|
|
453
480
|
const raw = result;
|
|
454
481
|
return String(raw["reject-reason"] ?? raw.reject_reason ?? raw.error ?? "mempool_rejected").trim() || "mempool_rejected";
|
|
455
482
|
}
|
|
483
|
+
function coercePolicyLockedRedemptionRequirements(value) {
|
|
484
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
485
|
+
throw new Error("requirements must be an object");
|
|
486
|
+
const raw = value;
|
|
487
|
+
const extra = getExtraRecord(raw);
|
|
488
|
+
const network = normalizeNetwork(raw.network);
|
|
489
|
+
const paymentRequestId = stringValue(extra.paymentRequestId ?? raw.paymentRequestId, "paymentRequestId");
|
|
490
|
+
const payTo = stringValue(raw.payTo ?? extra.recipient ?? extra.payTo, "payTo");
|
|
491
|
+
const assetId = stringValue(raw.assetId ?? extra.assetId ?? raw.asset, "assetId");
|
|
492
|
+
const amountAtomic = normalizeAmountAtomic((raw.maxAmountRequired ?? extra.amount ?? raw.amountAtomic));
|
|
493
|
+
const assetKey = normalizePaymentAssetKey(extra.asset ?? raw.assetKey ?? "custom");
|
|
494
|
+
const expiresAt = stringValue(extra.expiresAt ?? raw.expiresAt, "expiresAt");
|
|
495
|
+
const redemptionSource = coercePolicyLockedRedemptionSource(raw.redemptionSource ?? extra.redemptionSource);
|
|
496
|
+
const feeAssetId = String(extra.feeAssetId ?? exports.LIQUID_X402_ASSETS[network].lbtc.assetId);
|
|
497
|
+
return {
|
|
498
|
+
network,
|
|
499
|
+
paymentRequestId,
|
|
500
|
+
resource: String(raw.resource ?? ""),
|
|
501
|
+
description: String(raw.description ?? ""),
|
|
502
|
+
mimeType: String(raw.mimeType ?? ""),
|
|
503
|
+
payTo,
|
|
504
|
+
maxTimeoutSeconds: normalizeTimeout(raw.maxTimeoutSeconds),
|
|
505
|
+
assetKey,
|
|
506
|
+
assetId,
|
|
507
|
+
amountAtomic,
|
|
508
|
+
decimals: Number(extra.decimals ?? raw.decimals ?? 0),
|
|
509
|
+
expiresAt,
|
|
510
|
+
feeAsset: "lbtc",
|
|
511
|
+
feeAssetId,
|
|
512
|
+
...(extra.maxFeeSat !== undefined ? { maxFeeSat: normalizeAmountAtomic(extra.maxFeeSat) } : {}),
|
|
513
|
+
...(typeof raw.x402SummaryHash === "string" ? { summaryHash: raw.x402SummaryHash } : {}),
|
|
514
|
+
...(typeof raw.summaryHash === "string" ? { summaryHash: raw.summaryHash } : {}),
|
|
515
|
+
...(typeof extra.x402SummaryHash === "string" ? { summaryHash: extra.x402SummaryHash } : {}),
|
|
516
|
+
...(typeof extra.summaryHash === "string" ? { summaryHash: extra.summaryHash } : {}),
|
|
517
|
+
redemptionSource,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function coercePolicyLockedRedemptionSource(value) {
|
|
521
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
522
|
+
throw new Error("redemptionSource is required for policy-locked redemption");
|
|
523
|
+
}
|
|
524
|
+
const source = value;
|
|
525
|
+
const type = String(source.type ?? "").trim();
|
|
526
|
+
if (type !== "policy_locked_position") {
|
|
527
|
+
throw new Error("redemptionSource.type must be policy_locked_position");
|
|
528
|
+
}
|
|
529
|
+
return source;
|
|
530
|
+
}
|
|
531
|
+
function coercePolicyLockedRedemptionProposal(value, proposalPsetBase64) {
|
|
532
|
+
const raw = value && typeof value === "object" && !Array.isArray(value)
|
|
533
|
+
? value
|
|
534
|
+
: {};
|
|
535
|
+
if (raw.holderSignatureRequired === true) {
|
|
536
|
+
throw new Error("policy-locked redemption proposal still requires holder-side Simplicity signing; provide a service-prepared spend proposal");
|
|
537
|
+
}
|
|
538
|
+
const psetBase64 = String(proposalPsetBase64 ?? raw.psetBase64 ?? "").trim();
|
|
539
|
+
if (!psetBase64)
|
|
540
|
+
throw new Error("policy spend proposal psetBase64 is required");
|
|
541
|
+
return {
|
|
542
|
+
psetBase64,
|
|
543
|
+
...(raw.mode ? { mode: String(raw.mode) } : {}),
|
|
544
|
+
...(raw.summaryHash ? { summaryHash: String(raw.summaryHash) } : {}),
|
|
545
|
+
...(raw.holderSignatureRequired !== undefined ? { holderSignatureRequired: raw.holderSignatureRequired === true } : {}),
|
|
546
|
+
...(raw.metadata && typeof raw.metadata === "object" && !Array.isArray(raw.metadata)
|
|
547
|
+
? { metadata: raw.metadata }
|
|
548
|
+
: {}),
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function buildLiquidPolicyLockedRedemptionSummaryHash(requirements) {
|
|
552
|
+
return sha256Hex(stableStringify({
|
|
553
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
554
|
+
network: requirements.network,
|
|
555
|
+
paymentRequestId: requirements.paymentRequestId,
|
|
556
|
+
asset: requirements.assetKey,
|
|
557
|
+
assetId: requirements.assetId,
|
|
558
|
+
amountAtomic: requirements.amountAtomic,
|
|
559
|
+
payTo: requirements.payTo,
|
|
560
|
+
expectedOutput: {
|
|
561
|
+
amountAtomic: requirements.amountAtomic,
|
|
562
|
+
assetId: requirements.assetId,
|
|
563
|
+
payTo: requirements.payTo,
|
|
564
|
+
},
|
|
565
|
+
feeAsset: requirements.feeAsset,
|
|
566
|
+
feeAssetId: requirements.feeAssetId ?? exports.LIQUID_X402_ASSETS[requirements.network].lbtc.assetId,
|
|
567
|
+
maxFeeSat: requirements.maxFeeSat ?? null,
|
|
568
|
+
}));
|
|
569
|
+
}
|
|
570
|
+
function getRecordValue(value, key) {
|
|
571
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
572
|
+
return undefined;
|
|
573
|
+
return value[key];
|
|
574
|
+
}
|
|
575
|
+
function getExtraRecord(value) {
|
|
576
|
+
const extra = getRecordValue(value, "extra");
|
|
577
|
+
return extra && typeof extra === "object" && !Array.isArray(extra) ? extra : {};
|
|
578
|
+
}
|
|
579
|
+
function stringValue(value, name) {
|
|
580
|
+
const result = String(value ?? "").trim();
|
|
581
|
+
if (!result)
|
|
582
|
+
throw new Error(`${name} is required`);
|
|
583
|
+
return result;
|
|
584
|
+
}
|
|
456
585
|
function coerceRequirements(value) {
|
|
457
586
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
458
587
|
throw new Error("requirements must be an object");
|
|
@@ -563,7 +692,7 @@ function coercePaymentPayload(value) {
|
|
|
563
692
|
scheme: exports.LIQUID_X402_SCHEME,
|
|
564
693
|
network: normalizeNetwork(raw.network),
|
|
565
694
|
paymentRequestId: String(raw.paymentRequestId ?? ""),
|
|
566
|
-
asset:
|
|
695
|
+
asset: normalizePaymentAssetKey(String(raw.asset ?? "")),
|
|
567
696
|
assetId: String(raw.assetId ?? ""),
|
|
568
697
|
amountAtomic: normalizeAmountAtomic(raw.amountAtomic),
|
|
569
698
|
payTo: String(raw.payTo ?? ""),
|
|
@@ -579,6 +708,19 @@ function normalizeNetwork(input) {
|
|
|
579
708
|
return value;
|
|
580
709
|
throw new Error(`unsupported Liquid x402 network: ${String(input)}`);
|
|
581
710
|
}
|
|
711
|
+
function normalizePaymentAssetKey(input) {
|
|
712
|
+
const value = String(input ?? "").trim().toLowerCase();
|
|
713
|
+
if (value === "custom")
|
|
714
|
+
return "custom";
|
|
715
|
+
try {
|
|
716
|
+
return normalizeAssetKey(value);
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
if (/^[a-z0-9_-]{1,64}$/u.test(value) || /^[0-9a-f]{64}$/u.test(value))
|
|
720
|
+
return value;
|
|
721
|
+
throw new Error(`unsupported Liquid x402 asset: ${String(input)}`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
582
724
|
function normalizeAssetKey(input) {
|
|
583
725
|
const value = String(input ?? "").trim().toLowerCase();
|
|
584
726
|
if (value === "lbtc" || value === "bitcoin" || value === LIQUID_TESTNET_LBTC_ASSET_ID || value === LIQUID_MAINNET_LBTC_ASSET_ID) {
|