@hazbase/simplicity 0.2.0 → 0.3.0
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 +9 -2
- package/dist/cli.js +21 -0
- package/dist/client/SimplicityClient.d.ts +11 -0
- package/dist/client/SimplicityClient.js +44 -0
- package/dist/core/types.d.ts +5 -0
- package/dist/docs/definitions/receivable-definition.json +2 -1
- package/dist/docs/definitions/receivable-funding-claim.json +1 -1
- package/dist/docs/definitions/receivable-repayment-claim-partial.json +13 -0
- package/dist/docs/definitions/receivable-repayment-claim.json +5 -5
- package/dist/docs/definitions/receivable-state-funded.json +3 -2
- package/dist/docs/definitions/receivable-state-originated.json +2 -1
- package/dist/docs/definitions/receivable-state-partially-repaid.json +21 -0
- package/dist/docs/definitions/receivable-state-repaid.json +7 -6
- package/dist/domain/receivable.d.ts +1 -0
- package/dist/domain/receivable.js +10 -0
- package/dist/domain/receivableValidation.d.ts +3 -0
- package/dist/domain/receivableValidation.js +47 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -4
- package/dist/x402/index.d.ts +125 -0
- package/dist/x402/index.js +396 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ With the current public SDK you can build and test:
|
|
|
19
19
|
- output binding with public support/fallback reporting
|
|
20
20
|
- bond redemption / settlement / close-out flows
|
|
21
21
|
- LP fund capital call / distribution / close-out flows
|
|
22
|
-
- receivable repayment-first funding / repayment / closing flows
|
|
22
|
+
- receivable repayment-first funding / partial repayment / closing flows
|
|
23
23
|
- evidence, trust summary, lineage, and finality exports
|
|
24
24
|
|
|
25
25
|
## Public Architecture
|
|
@@ -130,9 +130,14 @@ Main entrypoints:
|
|
|
130
130
|
Representative example:
|
|
131
131
|
- [show-fund-claim-close-flow.ts](./examples/show-fund-claim-close-flow.ts)
|
|
132
132
|
|
|
133
|
+
Operational note:
|
|
134
|
+
- [fund-cutoff-runbook.md](./docs/fund-cutoff-runbook.md)
|
|
135
|
+
|
|
133
136
|
### Receivables
|
|
134
137
|
|
|
135
|
-
Use `sdk.receivables` for repayment-first receivable or invoice-style pilots where you want canonical state transitions, runtime claim descriptors, lineage verification, and terminal closing.
|
|
138
|
+
Use `sdk.receivables` for repayment-first receivable or invoice-style pilots where you want canonical state transitions, role-aware runtime claim descriptors, partial repayment handling, lineage verification, and terminal closing.
|
|
139
|
+
|
|
140
|
+
By default, funding claims resolve against the originator claimant key and repayment claims resolve against the current holder claimant key, while `controllerXonly` remains the fallback for simpler pilots.
|
|
136
141
|
|
|
137
142
|
Main entrypoints:
|
|
138
143
|
- `sdk.receivables.define(...)`
|
|
@@ -221,3 +226,5 @@ For practical usage you typically need:
|
|
|
221
226
|
- a reachable Elements / Liquid RPC endpoint
|
|
222
227
|
|
|
223
228
|
Some commands are SDK-only and work without live spends. Local and testnet e2e flows need the toolchain and, for broadcasted flows, an RPC environment.
|
|
229
|
+
|
|
230
|
+
The dedicated restricted OTC testnet runner clean-skips when RPC credentials are missing so it can stay in public CI and local verification scripts without turning missing env into a hard failure.
|
package/dist/cli.js
CHANGED
|
@@ -841,6 +841,10 @@ function formatReceivableClaimSummary(input) {
|
|
|
841
841
|
`payerEntityId=${input.payerEntityId}`,
|
|
842
842
|
`payeeEntityId=${input.payeeEntityId}`,
|
|
843
843
|
`amountSat=${input.amountSat}`,
|
|
844
|
+
input.claimantAuthoritySource ? `claimantAuthoritySource=${input.claimantAuthoritySource}` : undefined,
|
|
845
|
+
input.roleSeparatedAuthorization !== undefined
|
|
846
|
+
? `roleSeparatedAuthorization=${input.roleSeparatedAuthorization}`
|
|
847
|
+
: undefined,
|
|
844
848
|
input.bindingMode ? `bindingMode=${input.bindingMode}` : undefined,
|
|
845
849
|
input.reasonCode ? `reasonCode=${input.reasonCode}` : undefined,
|
|
846
850
|
input.supportedForm ? `supportedForm=${input.supportedForm}` : undefined,
|
|
@@ -3427,6 +3431,7 @@ async function main() {
|
|
|
3427
3431
|
nextStateValue: parseJsonArg("next-state-value"),
|
|
3428
3432
|
stateId: getArg("state-id"),
|
|
3429
3433
|
holderEntityId: getArg("holder-entity-id"),
|
|
3434
|
+
holderClaimantXonly: getArg("holder-claimant-xonly"),
|
|
3430
3435
|
fundedAt: getArg("funded-at"),
|
|
3431
3436
|
});
|
|
3432
3437
|
printJson({
|
|
@@ -3502,6 +3507,8 @@ async function main() {
|
|
|
3502
3507
|
payerEntityId: result.claimValue.payerEntityId,
|
|
3503
3508
|
payeeEntityId: result.claimValue.payeeEntityId,
|
|
3504
3509
|
amountSat: result.claimValue.amountSat,
|
|
3510
|
+
claimantAuthoritySource: result.report.fundingClaimTrust?.claimantAuthoritySource,
|
|
3511
|
+
roleSeparatedAuthorization: result.report.fundingClaimTrust?.roleSeparatedAuthorization,
|
|
3505
3512
|
bindingMode: result.report.fundingClaimTrust?.bindingMode,
|
|
3506
3513
|
reasonCode: result.report.fundingClaimTrust?.reasonCode,
|
|
3507
3514
|
supportedForm: result.report.fundingClaimTrust?.supportedForm,
|
|
@@ -3534,6 +3541,8 @@ async function main() {
|
|
|
3534
3541
|
payerEntityId: result.claimValue.payerEntityId,
|
|
3535
3542
|
payeeEntityId: result.claimValue.payeeEntityId,
|
|
3536
3543
|
amountSat: result.claimValue.amountSat,
|
|
3544
|
+
claimantAuthoritySource: result.report.fundingClaimTrust?.claimantAuthoritySource,
|
|
3545
|
+
roleSeparatedAuthorization: result.report.fundingClaimTrust?.roleSeparatedAuthorization,
|
|
3537
3546
|
bindingMode: result.report.fundingClaimTrust?.bindingMode,
|
|
3538
3547
|
reasonCode: result.report.fundingClaimTrust?.reasonCode,
|
|
3539
3548
|
supportedForm: result.report.fundingClaimTrust?.supportedForm,
|
|
@@ -3575,6 +3584,8 @@ async function main() {
|
|
|
3575
3584
|
payerEntityId: result.claimValue.payerEntityId,
|
|
3576
3585
|
payeeEntityId: result.claimValue.payeeEntityId,
|
|
3577
3586
|
amountSat: result.claimValue.amountSat,
|
|
3587
|
+
claimantAuthoritySource: result.report.fundingClaimTrust?.claimantAuthoritySource,
|
|
3588
|
+
roleSeparatedAuthorization: result.report.fundingClaimTrust?.roleSeparatedAuthorization,
|
|
3578
3589
|
bindingMode: result.report.fundingClaimTrust?.bindingMode,
|
|
3579
3590
|
reasonCode: result.report.fundingClaimTrust?.reasonCode,
|
|
3580
3591
|
supportedForm: result.report.fundingClaimTrust?.supportedForm,
|
|
@@ -3617,6 +3628,8 @@ async function main() {
|
|
|
3617
3628
|
payerEntityId: result.claimValue.payerEntityId,
|
|
3618
3629
|
payeeEntityId: result.claimValue.payeeEntityId,
|
|
3619
3630
|
amountSat: result.claimValue.amountSat,
|
|
3631
|
+
claimantAuthoritySource: result.report.fundingClaimTrust?.claimantAuthoritySource,
|
|
3632
|
+
roleSeparatedAuthorization: result.report.fundingClaimTrust?.roleSeparatedAuthorization,
|
|
3620
3633
|
bindingMode: result.report.fundingClaimTrust?.bindingMode,
|
|
3621
3634
|
reasonCode: result.report.fundingClaimTrust?.reasonCode,
|
|
3622
3635
|
supportedForm: result.report.fundingClaimTrust?.supportedForm,
|
|
@@ -3685,6 +3698,8 @@ async function main() {
|
|
|
3685
3698
|
payerEntityId: result.claimValue.payerEntityId,
|
|
3686
3699
|
payeeEntityId: result.claimValue.payeeEntityId,
|
|
3687
3700
|
amountSat: result.claimValue.amountSat,
|
|
3701
|
+
claimantAuthoritySource: result.report.repaymentClaimTrust?.claimantAuthoritySource,
|
|
3702
|
+
roleSeparatedAuthorization: result.report.repaymentClaimTrust?.roleSeparatedAuthorization,
|
|
3688
3703
|
bindingMode: result.report.repaymentClaimTrust?.bindingMode,
|
|
3689
3704
|
reasonCode: result.report.repaymentClaimTrust?.reasonCode,
|
|
3690
3705
|
supportedForm: result.report.repaymentClaimTrust?.supportedForm,
|
|
@@ -3717,6 +3732,8 @@ async function main() {
|
|
|
3717
3732
|
payerEntityId: result.claimValue.payerEntityId,
|
|
3718
3733
|
payeeEntityId: result.claimValue.payeeEntityId,
|
|
3719
3734
|
amountSat: result.claimValue.amountSat,
|
|
3735
|
+
claimantAuthoritySource: result.report.repaymentClaimTrust?.claimantAuthoritySource,
|
|
3736
|
+
roleSeparatedAuthorization: result.report.repaymentClaimTrust?.roleSeparatedAuthorization,
|
|
3720
3737
|
bindingMode: result.report.repaymentClaimTrust?.bindingMode,
|
|
3721
3738
|
reasonCode: result.report.repaymentClaimTrust?.reasonCode,
|
|
3722
3739
|
supportedForm: result.report.repaymentClaimTrust?.supportedForm,
|
|
@@ -3758,6 +3775,8 @@ async function main() {
|
|
|
3758
3775
|
payerEntityId: result.claimValue.payerEntityId,
|
|
3759
3776
|
payeeEntityId: result.claimValue.payeeEntityId,
|
|
3760
3777
|
amountSat: result.claimValue.amountSat,
|
|
3778
|
+
claimantAuthoritySource: result.report.repaymentClaimTrust?.claimantAuthoritySource,
|
|
3779
|
+
roleSeparatedAuthorization: result.report.repaymentClaimTrust?.roleSeparatedAuthorization,
|
|
3761
3780
|
bindingMode: result.report.repaymentClaimTrust?.bindingMode,
|
|
3762
3781
|
reasonCode: result.report.repaymentClaimTrust?.reasonCode,
|
|
3763
3782
|
supportedForm: result.report.repaymentClaimTrust?.supportedForm,
|
|
@@ -3800,6 +3819,8 @@ async function main() {
|
|
|
3800
3819
|
payerEntityId: result.claimValue.payerEntityId,
|
|
3801
3820
|
payeeEntityId: result.claimValue.payeeEntityId,
|
|
3802
3821
|
amountSat: result.claimValue.amountSat,
|
|
3822
|
+
claimantAuthoritySource: result.report.repaymentClaimTrust?.claimantAuthoritySource,
|
|
3823
|
+
roleSeparatedAuthorization: result.report.repaymentClaimTrust?.roleSeparatedAuthorization,
|
|
3803
3824
|
bindingMode: result.report.repaymentClaimTrust?.bindingMode,
|
|
3804
3825
|
reasonCode: result.report.repaymentClaimTrust?.reasonCode,
|
|
3805
3826
|
supportedForm: result.report.repaymentClaimTrust?.supportedForm,
|
|
@@ -3,6 +3,7 @@ import { ElementsRpcClient } from "../core/rpc";
|
|
|
3
3
|
import { BondEvidenceBundle, OutputBindingSupportEvaluation, OutputBindingSupportMatrix, PolicyEvidenceBundle, ReceivableEvidenceBundle, PolicyTemplateManifestValidationResult, DefinitionInput, DefinitionVerificationResult, CompileFromFileInput, CompileFromPresetInput, DefinitionDescriptor, StateDocumentDescriptor, StateDocumentInput, StateVerificationResult, SimplicityArtifact, SimplicityClientConfig } from "../core/types";
|
|
4
4
|
import { RelayerClient } from "../gasless/RelayerClient";
|
|
5
5
|
import { GaslessTransferInput, GaslessTransferResult, RelayerClientConfig } from "../gasless/types";
|
|
6
|
+
import * as liquidX402 from "../x402";
|
|
6
7
|
import { CompiledContract } from "./ContractFactory";
|
|
7
8
|
import { DeployedContract } from "./DeployedContract";
|
|
8
9
|
import { prepareRedemption, prepareClosing, exportFinalityPayload, buildSettlement, verifySettlement, define, executeClosing, executeRedemption, exportEvidence, inspectClosing, inspectRedemption, issue, load, verify, verifyIssuanceHistory, verifyClosing, verifyRedemption } from "../domain/bond";
|
|
@@ -14,6 +15,16 @@ export declare class SimplicityClient {
|
|
|
14
15
|
readonly rpc: ElementsRpcClient;
|
|
15
16
|
readonly payments: {
|
|
16
17
|
gaslessTransfer: (input: GaslessTransferInput) => Promise<GaslessTransferResult>;
|
|
18
|
+
x402: {
|
|
19
|
+
assets: typeof liquidX402.listLiquidX402Assets;
|
|
20
|
+
resolveAsset: typeof liquidX402.resolveLiquidX402Asset;
|
|
21
|
+
buildRequirements: typeof liquidX402.buildLiquidX402Requirements;
|
|
22
|
+
encodePayment: typeof liquidX402.encodeLiquidXPayment;
|
|
23
|
+
decodePayment: typeof liquidX402.decodeLiquidXPayment;
|
|
24
|
+
preparePsetPayment: (input: liquidX402.LiquidX402PreparePsetPaymentInput) => Promise<liquidX402.LiquidX402PreparePsetPaymentResult>;
|
|
25
|
+
verify: (input: liquidX402.LiquidX402VerifyPaymentInput) => Promise<liquidX402.LiquidX402VerifyPaymentResult>;
|
|
26
|
+
settle: (input: liquidX402.LiquidX402SettlePaymentInput) => Promise<liquidX402.LiquidX402SettlePaymentResult>;
|
|
27
|
+
};
|
|
17
28
|
};
|
|
18
29
|
readonly outputBinding: {
|
|
19
30
|
describeSupport: () => OutputBindingSupportMatrix;
|
|
@@ -1,4 +1,37 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.SimplicityClient = void 0;
|
|
4
37
|
exports.createSimplicityClient = createSimplicityClient;
|
|
@@ -10,6 +43,7 @@ const errors_1 = require("../core/errors");
|
|
|
10
43
|
const outputBinding_1 = require("../core/outputBinding");
|
|
11
44
|
const rpc_1 = require("../core/rpc");
|
|
12
45
|
const RelayerClient_1 = require("../gasless/RelayerClient");
|
|
46
|
+
const liquidX402 = __importStar(require("../x402"));
|
|
13
47
|
const ContractFactory_1 = require("./ContractFactory");
|
|
14
48
|
const DeployedContract_1 = require("./DeployedContract");
|
|
15
49
|
const bond_1 = require("../domain/bond");
|
|
@@ -30,6 +64,16 @@ class SimplicityClient {
|
|
|
30
64
|
this.rpc = new rpc_1.ElementsRpcClient(config.rpc);
|
|
31
65
|
this.payments = {
|
|
32
66
|
gaslessTransfer: async (input) => this.gaslessTransfer(input),
|
|
67
|
+
x402: {
|
|
68
|
+
assets: liquidX402.listLiquidX402Assets,
|
|
69
|
+
resolveAsset: liquidX402.resolveLiquidX402Asset,
|
|
70
|
+
buildRequirements: liquidX402.buildLiquidX402Requirements,
|
|
71
|
+
encodePayment: liquidX402.encodeLiquidXPayment,
|
|
72
|
+
decodePayment: liquidX402.decodeLiquidXPayment,
|
|
73
|
+
preparePsetPayment: async (input) => liquidX402.prepareLiquidX402PsetPayment(this.rpc, input),
|
|
74
|
+
verify: async (input) => liquidX402.verifyLiquidX402Payment(this.rpc, input),
|
|
75
|
+
settle: async (input) => liquidX402.settleLiquidX402Payment(this.rpc, input),
|
|
76
|
+
},
|
|
33
77
|
};
|
|
34
78
|
this.outputBinding = {
|
|
35
79
|
describeSupport: () => (0, outputBinding_1.describeOutputBindingSupport)(),
|
package/dist/core/types.d.ts
CHANGED
|
@@ -548,6 +548,7 @@ export type ReceivableStatus = "ORIGINATED" | "FUNDED" | "PARTIALLY_REPAID" | "R
|
|
|
548
548
|
export type ReceivableTransitionType = "ORIGINATE" | "FUND" | "REPAY" | "WRITE_OFF";
|
|
549
549
|
export type ReceivableClosingReason = "REPAID" | "DEFAULTED" | "CANCELLED";
|
|
550
550
|
export type ReceivableClaimKind = "FUNDING" | "REPAYMENT";
|
|
551
|
+
export type ReceivableClaimAuthoritySource = "originator-claimant" | "holder-claimant" | "controller-fallback";
|
|
551
552
|
export type ReceivableVerificationReportSchemaVersion = "receivable-verification-report/v1";
|
|
552
553
|
export type ReceivableEvidenceBundleSchemaVersion = "receivable-evidence-bundle/v1";
|
|
553
554
|
export type ReceivableFinalityPayloadSchemaVersion = "receivable-finality-payload/v1";
|
|
@@ -559,6 +560,7 @@ export interface ReceivableDefinition {
|
|
|
559
560
|
faceValue: number;
|
|
560
561
|
dueDate: string;
|
|
561
562
|
controllerXonly: string;
|
|
563
|
+
originatorClaimantXonly?: string;
|
|
562
564
|
}
|
|
563
565
|
export interface ReceivableStateTransition {
|
|
564
566
|
type: ReceivableTransitionType;
|
|
@@ -573,6 +575,7 @@ export interface ReceivableState {
|
|
|
573
575
|
holderEntityId: string;
|
|
574
576
|
currencyAssetId: string;
|
|
575
577
|
controllerXonly: string;
|
|
578
|
+
holderClaimantXonly?: string;
|
|
576
579
|
faceValue: number;
|
|
577
580
|
outstandingAmount: number;
|
|
578
581
|
repaidAmount: number;
|
|
@@ -627,6 +630,8 @@ export interface ReceivableClosingTrust {
|
|
|
627
630
|
}
|
|
628
631
|
export interface ReceivableClaimTrust {
|
|
629
632
|
claimKind: ReceivableClaimKind;
|
|
633
|
+
claimantAuthoritySource: ReceivableClaimAuthoritySource;
|
|
634
|
+
roleSeparatedAuthorization: boolean;
|
|
630
635
|
generated: boolean;
|
|
631
636
|
stateStatusEligible: boolean;
|
|
632
637
|
receivableIdMatch: boolean;
|
|
@@ -5,5 +5,6 @@
|
|
|
5
5
|
"currencyAssetId": "bitcoin",
|
|
6
6
|
"faceValue": 10000,
|
|
7
7
|
"dueDate": "2027-12-31T00:00:00Z",
|
|
8
|
-
"controllerXonly": "
|
|
8
|
+
"controllerXonly": "3333333333333333333333333333333333333333333333333333333333333333",
|
|
9
|
+
"originatorClaimantXonly": "1111111111111111111111111111111111111111111111111111111111111111"
|
|
9
10
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"claimId": "REC-001-FUNDING-CLAIM",
|
|
3
3
|
"claimKind": "FUNDING",
|
|
4
4
|
"receivableId": "REC-001",
|
|
5
|
-
"currentStateHash": "
|
|
5
|
+
"currentStateHash": "13147d187ce3200d7044d73d7b86b93140c43f29dd048f919569f5eb7b47c562",
|
|
6
6
|
"currentStatus": "FUNDED",
|
|
7
7
|
"payerEntityId": "fund-1",
|
|
8
8
|
"payeeEntityId": "originator-1",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"claimId": "REC-001-REPAYMENT-CLAIM-PARTIAL",
|
|
3
|
+
"claimKind": "REPAYMENT",
|
|
4
|
+
"receivableId": "REC-001",
|
|
5
|
+
"currentStateHash": "34e4db6639ad5b3ccb555320af22fb0eca2ab393ff198602ce5ca69d398cec31",
|
|
6
|
+
"currentStatus": "PARTIALLY_REPAID",
|
|
7
|
+
"payerEntityId": "debtor-1",
|
|
8
|
+
"payeeEntityId": "fund-1",
|
|
9
|
+
"claimantXonly": "2222222222222222222222222222222222222222222222222222222222222222",
|
|
10
|
+
"currencyAssetId": "bitcoin",
|
|
11
|
+
"amountSat": 4000,
|
|
12
|
+
"eventTimestamp": "2027-02-01T00:00:00Z"
|
|
13
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"claimId": "REC-001-REPAYMENT-CLAIM",
|
|
2
|
+
"claimId": "REC-001-REPAYMENT-CLAIM-FINAL",
|
|
3
3
|
"claimKind": "REPAYMENT",
|
|
4
4
|
"receivableId": "REC-001",
|
|
5
|
-
"currentStateHash": "
|
|
5
|
+
"currentStateHash": "a18fdc99bf37de415bfa242a387cdb41b943d2581bfe177aec51c3e42e9e40bf",
|
|
6
6
|
"currentStatus": "REPAID",
|
|
7
7
|
"payerEntityId": "debtor-1",
|
|
8
8
|
"payeeEntityId": "fund-1",
|
|
9
|
-
"claimantXonly": "
|
|
9
|
+
"claimantXonly": "2222222222222222222222222222222222222222222222222222222222222222",
|
|
10
10
|
"currencyAssetId": "bitcoin",
|
|
11
|
-
"amountSat":
|
|
12
|
-
"eventTimestamp": "2027-
|
|
11
|
+
"amountSat": 6000,
|
|
12
|
+
"eventTimestamp": "2027-03-01T00:00:00Z"
|
|
13
13
|
}
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
"debtorEntityId": "debtor-1",
|
|
6
6
|
"holderEntityId": "fund-1",
|
|
7
7
|
"currencyAssetId": "bitcoin",
|
|
8
|
-
"controllerXonly": "
|
|
8
|
+
"controllerXonly": "3333333333333333333333333333333333333333333333333333333333333333",
|
|
9
|
+
"holderClaimantXonly": "2222222222222222222222222222222222222222222222222222222222222222",
|
|
9
10
|
"faceValue": 10000,
|
|
10
11
|
"outstandingAmount": 10000,
|
|
11
12
|
"repaidAmount": 0,
|
|
12
13
|
"status": "FUNDED",
|
|
13
14
|
"createdAt": "2027-01-02T00:00:00Z",
|
|
14
|
-
"previousStateHash": "
|
|
15
|
+
"previousStateHash": "cdd1a8e867611a0bf486c35a7811c80c2459f28ca988c22f789fbacdd4ee5c9e",
|
|
15
16
|
"lastTransition": {
|
|
16
17
|
"type": "FUND",
|
|
17
18
|
"amount": 10000,
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
"debtorEntityId": "debtor-1",
|
|
6
6
|
"holderEntityId": "originator-1",
|
|
7
7
|
"currencyAssetId": "bitcoin",
|
|
8
|
-
"controllerXonly": "
|
|
8
|
+
"controllerXonly": "3333333333333333333333333333333333333333333333333333333333333333",
|
|
9
|
+
"holderClaimantXonly": "1111111111111111111111111111111111111111111111111111111111111111",
|
|
9
10
|
"faceValue": 10000,
|
|
10
11
|
"outstandingAmount": 10000,
|
|
11
12
|
"repaidAmount": 0,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"stateId": "REC-001-S2",
|
|
3
|
+
"receivableId": "REC-001",
|
|
4
|
+
"originatorEntityId": "originator-1",
|
|
5
|
+
"debtorEntityId": "debtor-1",
|
|
6
|
+
"holderEntityId": "fund-1",
|
|
7
|
+
"currencyAssetId": "bitcoin",
|
|
8
|
+
"controllerXonly": "3333333333333333333333333333333333333333333333333333333333333333",
|
|
9
|
+
"holderClaimantXonly": "2222222222222222222222222222222222222222222222222222222222222222",
|
|
10
|
+
"faceValue": 10000,
|
|
11
|
+
"outstandingAmount": 6000,
|
|
12
|
+
"repaidAmount": 4000,
|
|
13
|
+
"status": "PARTIALLY_REPAID",
|
|
14
|
+
"createdAt": "2027-02-01T00:00:00Z",
|
|
15
|
+
"previousStateHash": "13147d187ce3200d7044d73d7b86b93140c43f29dd048f919569f5eb7b47c562",
|
|
16
|
+
"lastTransition": {
|
|
17
|
+
"type": "REPAY",
|
|
18
|
+
"amount": 4000,
|
|
19
|
+
"at": "2027-02-01T00:00:00Z"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
|
-
"stateId": "REC-001-
|
|
2
|
+
"stateId": "REC-001-S3",
|
|
3
3
|
"receivableId": "REC-001",
|
|
4
4
|
"originatorEntityId": "originator-1",
|
|
5
5
|
"debtorEntityId": "debtor-1",
|
|
6
6
|
"holderEntityId": "fund-1",
|
|
7
7
|
"currencyAssetId": "bitcoin",
|
|
8
|
-
"controllerXonly": "
|
|
8
|
+
"controllerXonly": "3333333333333333333333333333333333333333333333333333333333333333",
|
|
9
|
+
"holderClaimantXonly": "2222222222222222222222222222222222222222222222222222222222222222",
|
|
9
10
|
"faceValue": 10000,
|
|
10
11
|
"outstandingAmount": 0,
|
|
11
12
|
"repaidAmount": 10000,
|
|
12
13
|
"status": "REPAID",
|
|
13
|
-
"createdAt": "2027-
|
|
14
|
-
"previousStateHash": "
|
|
14
|
+
"createdAt": "2027-03-01T00:00:00Z",
|
|
15
|
+
"previousStateHash": "34e4db6639ad5b3ccb555320af22fb0eca2ab393ff198602ce5ca69d398cec31",
|
|
15
16
|
"lastTransition": {
|
|
16
17
|
"type": "REPAY",
|
|
17
|
-
"amount":
|
|
18
|
-
"at": "2027-
|
|
18
|
+
"amount": 6000,
|
|
19
|
+
"at": "2027-03-01T00:00:00Z"
|
|
19
20
|
}
|
|
20
21
|
}
|
|
@@ -142,6 +142,7 @@ export declare function prepareFunding(sdk: SimplicityClient, input: {
|
|
|
142
142
|
nextStateValue?: ReceivableState;
|
|
143
143
|
stateId?: string;
|
|
144
144
|
holderEntityId?: string;
|
|
145
|
+
holderClaimantXonly?: string;
|
|
145
146
|
fundedAt?: string;
|
|
146
147
|
}): Promise<{
|
|
147
148
|
verified: boolean;
|
|
@@ -515,6 +515,7 @@ async function prepareFunding(sdk, input) {
|
|
|
515
515
|
previous: previous.value,
|
|
516
516
|
stateId: input.stateId ?? `${previous.value.receivableId}-FUNDED`,
|
|
517
517
|
holderEntityId: input.holderEntityId ?? previous.value.holderEntityId,
|
|
518
|
+
holderClaimantXonly: input.holderClaimantXonly,
|
|
518
519
|
fundedAt: input.fundedAt ?? new Date().toISOString(),
|
|
519
520
|
}),
|
|
520
521
|
});
|
|
@@ -545,6 +546,7 @@ async function loadReceivableFundingClaimDocument(sdk, input) {
|
|
|
545
546
|
}
|
|
546
547
|
const value = (0, receivableValidation_1.buildReceivableFundingClaimDescriptor)({
|
|
547
548
|
claimId: input.claimId,
|
|
549
|
+
definition: input.definitionValue,
|
|
548
550
|
currentState: input.currentStateValue,
|
|
549
551
|
payerEntityId: input.payerEntityId,
|
|
550
552
|
payeeEntityId: input.payeeEntityId,
|
|
@@ -728,6 +730,7 @@ async function prepareFundingClaim(sdk, input) {
|
|
|
728
730
|
const claim = await loadReceivableFundingClaimDocument(sdk, {
|
|
729
731
|
fundingClaimPath: input.fundingClaimPath,
|
|
730
732
|
fundingClaimValue: input.fundingClaimValue,
|
|
733
|
+
definitionValue: definition.value,
|
|
731
734
|
currentStateValue: currentState.value,
|
|
732
735
|
claimId: input.claimId ?? `${currentState.value.receivableId}-FUNDING-CLAIM`,
|
|
733
736
|
payerEntityId: input.payerEntityId,
|
|
@@ -741,6 +744,7 @@ async function prepareFundingClaim(sdk, input) {
|
|
|
741
744
|
? (0, receivableValidation_1.verifyReceivableStateHistory)({ history: history.map((entry) => entry.value) })
|
|
742
745
|
: undefined;
|
|
743
746
|
const claimChecks = (0, receivableValidation_1.validateReceivableFundingClaimAgainstState)({
|
|
747
|
+
definition: definition.value,
|
|
744
748
|
currentState: currentState.value,
|
|
745
749
|
claim: claim.value,
|
|
746
750
|
});
|
|
@@ -784,12 +788,14 @@ async function verifyFundingClaim(sdk, input) {
|
|
|
784
788
|
const claim = await loadReceivableFundingClaimDocument(sdk, {
|
|
785
789
|
fundingClaimPath: input.fundingClaimPath,
|
|
786
790
|
fundingClaimValue: input.fundingClaimValue,
|
|
791
|
+
definitionValue: definition.value,
|
|
787
792
|
});
|
|
788
793
|
const crossChecks = (0, receivableValidation_1.validateReceivableCrossChecks)(definition.value, currentState.value);
|
|
789
794
|
const lineageChecks = history.length > 0
|
|
790
795
|
? (0, receivableValidation_1.verifyReceivableStateHistory)({ history: history.map((entry) => entry.value) })
|
|
791
796
|
: undefined;
|
|
792
797
|
const claimChecks = (0, receivableValidation_1.validateReceivableFundingClaimAgainstState)({
|
|
798
|
+
definition: definition.value,
|
|
793
799
|
currentState: currentState.value,
|
|
794
800
|
claim: claim.value,
|
|
795
801
|
});
|
|
@@ -887,6 +893,8 @@ async function inspectFundingClaim(sdk, input) {
|
|
|
887
893
|
claimantXonlyCommitted: verified.report.fundingClaimTrust?.claimantXonlyCommitted ?? true,
|
|
888
894
|
checks: {
|
|
889
895
|
claimKind: verified.report.fundingClaimTrust?.claimKind ?? "FUNDING",
|
|
896
|
+
claimantAuthoritySource: verified.report.fundingClaimTrust?.claimantAuthoritySource ?? "controller-fallback",
|
|
897
|
+
roleSeparatedAuthorization: verified.report.fundingClaimTrust?.roleSeparatedAuthorization ?? false,
|
|
890
898
|
stateStatusEligible: verified.report.fundingClaimTrust?.stateStatusEligible ?? false,
|
|
891
899
|
receivableIdMatch: verified.report.fundingClaimTrust?.receivableIdMatch ?? false,
|
|
892
900
|
currentStateHashMatch: verified.report.fundingClaimTrust?.currentStateHashMatch ?? false,
|
|
@@ -1101,6 +1109,8 @@ async function inspectRepaymentClaim(sdk, input) {
|
|
|
1101
1109
|
claimantXonlyCommitted: verified.report.repaymentClaimTrust?.claimantXonlyCommitted ?? true,
|
|
1102
1110
|
checks: {
|
|
1103
1111
|
claimKind: verified.report.repaymentClaimTrust?.claimKind ?? "REPAYMENT",
|
|
1112
|
+
claimantAuthoritySource: verified.report.repaymentClaimTrust?.claimantAuthoritySource ?? "controller-fallback",
|
|
1113
|
+
roleSeparatedAuthorization: verified.report.repaymentClaimTrust?.roleSeparatedAuthorization ?? false,
|
|
1104
1114
|
stateStatusEligible: verified.report.repaymentClaimTrust?.stateStatusEligible ?? false,
|
|
1105
1115
|
receivableIdMatch: verified.report.repaymentClaimTrust?.receivableIdMatch ?? false,
|
|
1106
1116
|
currentStateHashMatch: verified.report.repaymentClaimTrust?.currentStateHashMatch ?? false,
|
|
@@ -37,6 +37,7 @@ export declare function buildFundedReceivableState(input: {
|
|
|
37
37
|
previous: ReceivableState;
|
|
38
38
|
stateId: string;
|
|
39
39
|
holderEntityId: string;
|
|
40
|
+
holderClaimantXonly?: string;
|
|
40
41
|
fundedAt: string;
|
|
41
42
|
}): ReceivableState;
|
|
42
43
|
export declare function applyReceivableRepayment(input: {
|
|
@@ -59,6 +60,7 @@ export declare function buildReceivableClosingDescriptor(input: {
|
|
|
59
60
|
}): ReceivableClosingDescriptor;
|
|
60
61
|
export declare function buildReceivableFundingClaimDescriptor(input: {
|
|
61
62
|
claimId: string;
|
|
63
|
+
definition?: ReceivableDefinition;
|
|
62
64
|
currentState: ReceivableState;
|
|
63
65
|
payerEntityId?: string;
|
|
64
66
|
payeeEntityId?: string;
|
|
@@ -76,6 +78,7 @@ export declare function buildReceivableRepaymentClaimDescriptor(input: {
|
|
|
76
78
|
eventTimestamp?: string;
|
|
77
79
|
}): ReceivableRepaymentClaimDescriptor;
|
|
78
80
|
export declare function validateReceivableFundingClaimAgainstState(input: {
|
|
81
|
+
definition: ReceivableDefinition;
|
|
79
82
|
currentState: ReceivableState;
|
|
80
83
|
claim: ReceivableFundingClaimDescriptor;
|
|
81
84
|
}): Omit<ReceivableClaimTrust, "generated" | "bindingMode" | "requestedMode" | "supportedForm" | "reasonCode" | "nextReceiverRuntimeCommitted" | "nextOutputHashRuntimeBound" | "nextOutputScriptRuntimeBound" | "amountRuntimeBound" | "autoDerived" | "fallbackReason" | "bindingInputs" | "claimantXonlyCommitted">;
|
|
@@ -119,6 +119,9 @@ function validateReceivableDefinition(value) {
|
|
|
119
119
|
assertFinitePositiveNumber(definition?.faceValue, "RECEIVABLE_FACE_VALUE_INVALID", "faceValue must be a positive finite number");
|
|
120
120
|
assertIsoDateLike(definition?.dueDate, "RECEIVABLE_DUE_DATE_INVALID", "dueDate must be an ISO8601 string");
|
|
121
121
|
assertXonlyPubkey(definition?.controllerXonly, "RECEIVABLE_CONTROLLER_INVALID", "controllerXonly must be a 32-byte xonly public key");
|
|
122
|
+
if (definition.originatorClaimantXonly !== undefined) {
|
|
123
|
+
assertXonlyPubkey(definition.originatorClaimantXonly, "RECEIVABLE_ORIGINATOR_CLAIMANT_INVALID", "originatorClaimantXonly must be a 32-byte xonly public key");
|
|
124
|
+
}
|
|
122
125
|
return {
|
|
123
126
|
receivableId: definition.receivableId,
|
|
124
127
|
originatorEntityId: definition.originatorEntityId,
|
|
@@ -127,6 +130,7 @@ function validateReceivableDefinition(value) {
|
|
|
127
130
|
faceValue: definition.faceValue,
|
|
128
131
|
dueDate: definition.dueDate,
|
|
129
132
|
controllerXonly: definition.controllerXonly,
|
|
133
|
+
...(definition.originatorClaimantXonly ? { originatorClaimantXonly: definition.originatorClaimantXonly } : {}),
|
|
130
134
|
};
|
|
131
135
|
}
|
|
132
136
|
function validateReceivableState(value) {
|
|
@@ -138,6 +142,9 @@ function validateReceivableState(value) {
|
|
|
138
142
|
assertNonEmptyString(state?.holderEntityId, "RECEIVABLE_HOLDER_INVALID", "holderEntityId must be a non-empty string");
|
|
139
143
|
assertNonEmptyString(state?.currencyAssetId, "RECEIVABLE_CURRENCY_INVALID", "currencyAssetId must be a non-empty string");
|
|
140
144
|
assertXonlyPubkey(state?.controllerXonly, "RECEIVABLE_CONTROLLER_INVALID", "controllerXonly must be a 32-byte xonly public key");
|
|
145
|
+
if (state.holderClaimantXonly !== undefined) {
|
|
146
|
+
assertXonlyPubkey(state.holderClaimantXonly, "RECEIVABLE_HOLDER_CLAIMANT_INVALID", "holderClaimantXonly must be a 32-byte xonly public key");
|
|
147
|
+
}
|
|
141
148
|
assertFinitePositiveNumber(state?.faceValue, "RECEIVABLE_FACE_VALUE_INVALID", "faceValue must be a positive finite number");
|
|
142
149
|
assertFiniteNonNegativeNumber(state?.outstandingAmount, "RECEIVABLE_OUTSTANDING_INVALID", "outstandingAmount must be a non-negative finite number");
|
|
143
150
|
assertFiniteNonNegativeNumber(state?.repaidAmount, "RECEIVABLE_REPAID_INVALID", "repaidAmount must be a non-negative finite number");
|
|
@@ -240,6 +247,7 @@ function validateReceivableState(value) {
|
|
|
240
247
|
holderEntityId: state.holderEntityId,
|
|
241
248
|
currencyAssetId: state.currencyAssetId,
|
|
242
249
|
controllerXonly: state.controllerXonly,
|
|
250
|
+
...(state.holderClaimantXonly ? { holderClaimantXonly: state.holderClaimantXonly } : {}),
|
|
243
251
|
faceValue: state.faceValue,
|
|
244
252
|
outstandingAmount: state.outstandingAmount,
|
|
245
253
|
repaidAmount: state.repaidAmount,
|
|
@@ -432,6 +440,9 @@ function buildFundedReceivableState(input) {
|
|
|
432
440
|
...previous,
|
|
433
441
|
stateId: input.stateId,
|
|
434
442
|
holderEntityId: input.holderEntityId,
|
|
443
|
+
holderClaimantXonly: input.holderEntityId === previous.holderEntityId
|
|
444
|
+
? previous.holderClaimantXonly ?? previous.controllerXonly
|
|
445
|
+
: input.holderClaimantXonly ?? previous.controllerXonly,
|
|
435
446
|
status: "FUNDED",
|
|
436
447
|
createdAt: input.fundedAt,
|
|
437
448
|
previousStateHash: summarizeReceivableState(previous).hash,
|
|
@@ -508,6 +519,7 @@ function buildReceivableClosingDescriptor(input) {
|
|
|
508
519
|
});
|
|
509
520
|
}
|
|
510
521
|
function buildReceivableFundingClaimDescriptor(input) {
|
|
522
|
+
const definition = input.definition ? validateReceivableDefinition(input.definition) : undefined;
|
|
511
523
|
const currentState = validateReceivableState(input.currentState);
|
|
512
524
|
const currentStateHash = summarizeReceivableState(currentState).hash;
|
|
513
525
|
return validateReceivableFundingClaimDescriptor({
|
|
@@ -518,7 +530,7 @@ function buildReceivableFundingClaimDescriptor(input) {
|
|
|
518
530
|
currentStatus: currentState.status,
|
|
519
531
|
payerEntityId: input.payerEntityId ?? currentState.holderEntityId,
|
|
520
532
|
payeeEntityId: input.payeeEntityId ?? currentState.originatorEntityId,
|
|
521
|
-
claimantXonly: input.claimantXonly ?? currentState.controllerXonly,
|
|
533
|
+
claimantXonly: input.claimantXonly ?? definition?.originatorClaimantXonly ?? currentState.controllerXonly,
|
|
522
534
|
currencyAssetId: currentState.currencyAssetId,
|
|
523
535
|
amountSat: input.amountSat ?? currentState.lastTransition?.amount ?? currentState.outstandingAmount,
|
|
524
536
|
eventTimestamp: input.eventTimestamp ?? currentState.lastTransition?.at ?? currentState.createdAt,
|
|
@@ -535,15 +547,41 @@ function buildReceivableRepaymentClaimDescriptor(input) {
|
|
|
535
547
|
currentStatus: currentState.status,
|
|
536
548
|
payerEntityId: input.payerEntityId ?? currentState.debtorEntityId,
|
|
537
549
|
payeeEntityId: input.payeeEntityId ?? currentState.holderEntityId,
|
|
538
|
-
claimantXonly: input.claimantXonly ?? currentState.controllerXonly,
|
|
550
|
+
claimantXonly: input.claimantXonly ?? currentState.holderClaimantXonly ?? currentState.controllerXonly,
|
|
539
551
|
currencyAssetId: currentState.currencyAssetId,
|
|
540
552
|
amountSat: input.amountSat ?? currentState.lastTransition?.amount ?? currentState.repaidAmount,
|
|
541
553
|
eventTimestamp: input.eventTimestamp ?? currentState.lastTransition?.at ?? currentState.createdAt,
|
|
542
554
|
});
|
|
543
555
|
}
|
|
556
|
+
function resolveFundingClaimAuthority(definition, currentState) {
|
|
557
|
+
if (definition.originatorClaimantXonly) {
|
|
558
|
+
return {
|
|
559
|
+
claimantXonly: definition.originatorClaimantXonly,
|
|
560
|
+
authoritySource: "originator-claimant",
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
claimantXonly: currentState.controllerXonly,
|
|
565
|
+
authoritySource: "controller-fallback",
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function resolveRepaymentClaimAuthority(currentState) {
|
|
569
|
+
if (currentState.holderClaimantXonly) {
|
|
570
|
+
return {
|
|
571
|
+
claimantXonly: currentState.holderClaimantXonly,
|
|
572
|
+
authoritySource: "holder-claimant",
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
return {
|
|
576
|
+
claimantXonly: currentState.controllerXonly,
|
|
577
|
+
authoritySource: "controller-fallback",
|
|
578
|
+
};
|
|
579
|
+
}
|
|
544
580
|
function validateReceivableFundingClaimAgainstState(input) {
|
|
581
|
+
const definition = validateReceivableDefinition(input.definition);
|
|
545
582
|
const currentState = validateReceivableState(input.currentState);
|
|
546
583
|
const claim = validateReceivableFundingClaimDescriptor(input.claim);
|
|
584
|
+
const authority = resolveFundingClaimAuthority(definition, currentState);
|
|
547
585
|
const currentStateHash = summarizeReceivableState(currentState).hash;
|
|
548
586
|
const stateStatusEligible = currentState.status === "FUNDED";
|
|
549
587
|
const receivableIdMatch = claim.receivableId === currentState.receivableId;
|
|
@@ -551,12 +589,14 @@ function validateReceivableFundingClaimAgainstState(input) {
|
|
|
551
589
|
const currentStatusMatch = claim.currentStatus === currentState.status;
|
|
552
590
|
const payerEntityMatch = claim.payerEntityId === currentState.holderEntityId;
|
|
553
591
|
const payeeEntityMatch = claim.payeeEntityId === currentState.originatorEntityId;
|
|
554
|
-
const claimantXonlyMatch = claim.claimantXonly ===
|
|
592
|
+
const claimantXonlyMatch = claim.claimantXonly === authority.claimantXonly;
|
|
555
593
|
const currencyAssetMatch = claim.currencyAssetId === currentState.currencyAssetId;
|
|
556
594
|
const amountMatch = currentState.lastTransition?.type === "FUND" && claim.amountSat === currentState.lastTransition.amount;
|
|
557
595
|
const eventTimestampMatch = currentState.lastTransition?.type === "FUND" && claim.eventTimestamp === currentState.lastTransition.at;
|
|
558
596
|
return {
|
|
559
597
|
claimKind: "FUNDING",
|
|
598
|
+
claimantAuthoritySource: authority.authoritySource,
|
|
599
|
+
roleSeparatedAuthorization: authority.authoritySource !== "controller-fallback",
|
|
560
600
|
stateStatusEligible,
|
|
561
601
|
receivableIdMatch,
|
|
562
602
|
currentStateHashMatch,
|
|
@@ -582,6 +622,7 @@ function validateReceivableFundingClaimAgainstState(input) {
|
|
|
582
622
|
function validateReceivableRepaymentClaimAgainstState(input) {
|
|
583
623
|
const currentState = validateReceivableState(input.currentState);
|
|
584
624
|
const claim = validateReceivableRepaymentClaimDescriptor(input.claim);
|
|
625
|
+
const authority = resolveRepaymentClaimAuthority(currentState);
|
|
585
626
|
const currentStateHash = summarizeReceivableState(currentState).hash;
|
|
586
627
|
const stateStatusEligible = currentState.status === "PARTIALLY_REPAID" || currentState.status === "REPAID";
|
|
587
628
|
const receivableIdMatch = claim.receivableId === currentState.receivableId;
|
|
@@ -589,12 +630,14 @@ function validateReceivableRepaymentClaimAgainstState(input) {
|
|
|
589
630
|
const currentStatusMatch = claim.currentStatus === currentState.status;
|
|
590
631
|
const payerEntityMatch = claim.payerEntityId === currentState.debtorEntityId;
|
|
591
632
|
const payeeEntityMatch = claim.payeeEntityId === currentState.holderEntityId;
|
|
592
|
-
const claimantXonlyMatch = claim.claimantXonly ===
|
|
633
|
+
const claimantXonlyMatch = claim.claimantXonly === authority.claimantXonly;
|
|
593
634
|
const currencyAssetMatch = claim.currencyAssetId === currentState.currencyAssetId;
|
|
594
635
|
const amountMatch = currentState.lastTransition?.type === "REPAY" && claim.amountSat === currentState.lastTransition.amount;
|
|
595
636
|
const eventTimestampMatch = currentState.lastTransition?.type === "REPAY" && claim.eventTimestamp === currentState.lastTransition.at;
|
|
596
637
|
return {
|
|
597
638
|
claimKind: "REPAYMENT",
|
|
639
|
+
claimantAuthoritySource: authority.authoritySource,
|
|
640
|
+
roleSeparatedAuthorization: authority.authoritySource !== "controller-fallback",
|
|
598
641
|
stateStatusEligible,
|
|
599
642
|
receivableIdMatch,
|
|
600
643
|
currentStateHashMatch,
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { createSimplicityClient, SimplicityClient } from "./client/SimplicityCli
|
|
|
2
2
|
export { CompiledContract } from "./client/ContractFactory";
|
|
3
3
|
export { DeployedContract } from "./client/DeployedContract";
|
|
4
4
|
export { RelayerClient } from "./gasless/RelayerClient";
|
|
5
|
+
export { ElementsRpcClient } from "./core/rpc";
|
|
6
|
+
export * from "./x402";
|
|
5
7
|
export * from "./core/types";
|
|
6
8
|
export * from "./core/errors";
|
|
7
9
|
export { listPresets, getPresetOrThrow } from "./core/presets";
|
package/dist/index.js
CHANGED
|
@@ -14,10 +14,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.
|
|
18
|
-
exports.
|
|
19
|
-
exports.
|
|
20
|
-
exports.validatePolicyTransferDescriptor = exports.validatePolicyOutputDescriptor = exports.validatePolicyState = exports.summarizePolicyTransferDescriptor = exports.summarizePolicyOutputDescriptor = exports.summarizePolicyState = exports.exportPolicyEvidence = void 0;
|
|
17
|
+
exports.signPositionReceipt = exports.verifyCapitalCall = exports.executeCapitalCallRefund = exports.inspectCapitalCallRefund = exports.executeCapitalCallRollover = exports.inspectCapitalCallRollover = exports.executeCapitalCallClaim = exports.inspectCapitalCallClaim = exports.prepareCapitalCall = exports.loadFund = exports.verifyFund = exports.defineFund = exports.exportFinalityPayload = exports.exportBondEvidence = exports.verifyClosing = exports.executeClosing = exports.inspectClosing = exports.prepareClosing = exports.verifySettlement = exports.buildSettlement = exports.verifyRedemption = exports.executeRedemption = exports.inspectRedemption = exports.prepareRedemption = exports.issueBond = exports.loadBond = exports.verifyIssuanceHistory = exports.verifyBond = exports.defineBond = exports.verifyStateDescriptorAgainstArtifact = exports.verifyStateAgainstArtifact = exports.buildArtifactStateMetadata = exports.loadStateInput = exports.verifyDefinitionDescriptorAgainstArtifact = exports.verifyDefinitionAgainstArtifact = exports.buildArtifactDefinitionMetadata = exports.loadDefinitionInput = exports.evaluateOutputBindingSupport = exports.describeOutputBindingSupport = exports.normalizeArtifact = exports.saveArtifact = exports.loadArtifact = exports.getPresetOrThrow = exports.listPresets = exports.ElementsRpcClient = exports.RelayerClient = exports.DeployedContract = exports.CompiledContract = exports.SimplicityClient = exports.createSimplicityClient = void 0;
|
|
18
|
+
exports.validateFundDefinition = exports.summarizeFundFinalityPayload = exports.summarizeFundClosingDescriptor = exports.summarizeDistributionDescriptor = exports.summarizeLPPositionReceipt = exports.summarizeCapitalCallState = exports.summarizeFundDefinition = exports.validateBondSettlementMatchesExpected = exports.validateBondSettlementDescriptor = exports.summarizeBondSettlementDescriptor = exports.verifyBondIssuanceHistory = exports.summarizeBondIssuanceState = exports.buildRedeemedBondIssuanceState = exports.validateBondStateTransition = exports.validateBondCrossChecks = exports.validateBondIssuanceState = exports.validateBondDefinition = exports.exportReceivableFinalityPayload = exports.exportReceivableEvidence = exports.verifyReceivableStateHistory = exports.verifyReceivableClosing = exports.prepareReceivableClosing = exports.verifyReceivableWriteOff = exports.prepareReceivableWriteOff = exports.verifyReceivableRepaymentClaim = exports.executeReceivableRepaymentClaim = exports.inspectReceivableRepaymentClaim = exports.prepareReceivableRepaymentClaim = exports.verifyReceivableRepayment = exports.prepareReceivableRepayment = exports.verifyReceivableFundingClaim = exports.executeReceivableFundingClaim = exports.inspectReceivableFundingClaim = exports.prepareReceivableFundingClaim = exports.verifyReceivableFunding = exports.prepareReceivableFunding = exports.loadReceivable = exports.verifyReceivable = exports.defineReceivable = exports.exportFundFinalityPayload = exports.exportFundEvidence = exports.verifyFundClosing = exports.prepareFundClosing = exports.verifyDistribution = exports.executeDistributionClaim = exports.inspectDistributionClaim = exports.reconcilePosition = exports.prepareDistribution = exports.verifyPositionReceiptChain = exports.verifyPositionReceipt = void 0;
|
|
19
|
+
exports.verifyState = exports.inspectTransfer = exports.executeTransfer = exports.prepareTransfer = exports.issue = exports.validatePolicyTemplateParams = exports.describePolicyTemplate = exports.validatePolicyTemplateManifest = exports.loadPolicyTemplateManifest = exports.listPolicyTemplates = exports.buildPolicyOutputDescriptor = exports.compilePolicyStateContract = exports.verifyReceivableStateHistoryValidation = exports.validateReceivableWriteOffTransition = exports.validateReceivableRepaymentTransition = exports.validateReceivableFundingTransition = exports.validateReceivableCrossChecks = exports.validateReceivableState = exports.validateReceivableRepaymentClaimDescriptor = exports.validateReceivableRepaymentClaimAgainstState = exports.validateReceivableFundingClaimDescriptor = exports.validateReceivableFundingClaimAgainstState = exports.validateReceivableDefinition = exports.validateReceivableClosingDescriptor = exports.validateReceivableClosingAgainstState = exports.summarizeReceivableState = exports.summarizeReceivableRepaymentClaimDescriptor = exports.summarizeReceivableFundingClaimDescriptor = exports.summarizeReceivableDefinition = exports.summarizeReceivableClosingDescriptor = exports.buildReceivableRepaymentClaimDescriptor = exports.buildReceivableFundingClaimDescriptor = exports.buildReceivableClosingDescriptor = exports.buildFundedReceivableState = exports.buildDefaultedReceivableState = exports.applyReceivableRepayment = exports.buildFundClosingDescriptor = exports.buildDistributionDescriptor = exports.applyDistributionsToReceipt = exports.applyDistributionToReceipt = exports.buildLPPositionReceipt = exports.buildRefundedCapitalCallState = exports.buildClaimedCapitalCallState = exports.validateClosingAgainstReceipt = exports.validateDistributionAgainstReceipt = exports.validateFundCrossChecks = exports.validateFundClosingDescriptor = exports.validateDistributionDescriptor = exports.validateLPPositionReceipt = exports.validateCapitalCallState = void 0;
|
|
20
|
+
exports.validatePolicyTransferDescriptor = exports.validatePolicyOutputDescriptor = exports.validatePolicyState = exports.summarizePolicyTransferDescriptor = exports.summarizePolicyOutputDescriptor = exports.summarizePolicyState = exports.exportPolicyEvidence = exports.verifyTransfer = void 0;
|
|
21
21
|
var SimplicityClient_1 = require("./client/SimplicityClient");
|
|
22
22
|
Object.defineProperty(exports, "createSimplicityClient", { enumerable: true, get: function () { return SimplicityClient_1.createSimplicityClient; } });
|
|
23
23
|
Object.defineProperty(exports, "SimplicityClient", { enumerable: true, get: function () { return SimplicityClient_1.SimplicityClient; } });
|
|
@@ -27,6 +27,9 @@ var DeployedContract_1 = require("./client/DeployedContract");
|
|
|
27
27
|
Object.defineProperty(exports, "DeployedContract", { enumerable: true, get: function () { return DeployedContract_1.DeployedContract; } });
|
|
28
28
|
var RelayerClient_1 = require("./gasless/RelayerClient");
|
|
29
29
|
Object.defineProperty(exports, "RelayerClient", { enumerable: true, get: function () { return RelayerClient_1.RelayerClient; } });
|
|
30
|
+
var rpc_1 = require("./core/rpc");
|
|
31
|
+
Object.defineProperty(exports, "ElementsRpcClient", { enumerable: true, get: function () { return rpc_1.ElementsRpcClient; } });
|
|
32
|
+
__exportStar(require("./x402"), exports);
|
|
30
33
|
__exportStar(require("./core/types"), exports);
|
|
31
34
|
__exportStar(require("./core/errors"), exports);
|
|
32
35
|
var presets_1 = require("./core/presets");
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { ElementsRpcClient } from "../core/rpc";
|
|
2
|
+
export declare const LIQUID_X402_SCHEME: "exact-liquid-pset";
|
|
3
|
+
export declare const LIQUID_X402_VERSION: 1;
|
|
4
|
+
export declare const LIQUID_X402_DEFAULT_NETWORK: "liquidtestnet";
|
|
5
|
+
export declare const LIQUID_X402_DEFAULT_TIMEOUT_SECONDS = 60;
|
|
6
|
+
export type LiquidX402Network = "liquidtestnet" | "liquidv1";
|
|
7
|
+
export type LiquidX402AssetKey = "lbtc" | "usdt";
|
|
8
|
+
export interface LiquidX402Asset {
|
|
9
|
+
key: LiquidX402AssetKey;
|
|
10
|
+
label: string;
|
|
11
|
+
network: LiquidX402Network;
|
|
12
|
+
assetId: string;
|
|
13
|
+
decimals: number;
|
|
14
|
+
isNative: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface LiquidX402RequirementsInput {
|
|
17
|
+
paymentRequestId: string;
|
|
18
|
+
resource: string;
|
|
19
|
+
payTo: string;
|
|
20
|
+
amountAtomic: string | number | bigint;
|
|
21
|
+
network?: LiquidX402Network;
|
|
22
|
+
asset?: LiquidX402AssetKey | string;
|
|
23
|
+
description?: string;
|
|
24
|
+
mimeType?: string;
|
|
25
|
+
maxTimeoutSeconds?: number;
|
|
26
|
+
expiresAt?: string | Date;
|
|
27
|
+
maxFeeSat?: string | number | bigint;
|
|
28
|
+
metadata?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
export interface LiquidX402PaymentRequirements {
|
|
31
|
+
scheme: typeof LIQUID_X402_SCHEME;
|
|
32
|
+
network: LiquidX402Network;
|
|
33
|
+
maxAmountRequired: string;
|
|
34
|
+
resource: string;
|
|
35
|
+
description: string;
|
|
36
|
+
mimeType: string;
|
|
37
|
+
payTo: string;
|
|
38
|
+
maxTimeoutSeconds: number;
|
|
39
|
+
asset: string;
|
|
40
|
+
extra: {
|
|
41
|
+
paymentRequestId: string;
|
|
42
|
+
asset: LiquidX402AssetKey;
|
|
43
|
+
assetId: string;
|
|
44
|
+
decimals: number;
|
|
45
|
+
expiresAt: string;
|
|
46
|
+
feeAsset: "lbtc";
|
|
47
|
+
feeAssetId?: string;
|
|
48
|
+
maxFeeSat?: string;
|
|
49
|
+
metadata?: Record<string, unknown>;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export interface LiquidX402PaymentPayload {
|
|
53
|
+
scheme: typeof LIQUID_X402_SCHEME;
|
|
54
|
+
network: LiquidX402Network;
|
|
55
|
+
paymentRequestId: string;
|
|
56
|
+
asset: LiquidX402AssetKey;
|
|
57
|
+
assetId: string;
|
|
58
|
+
amountAtomic: string;
|
|
59
|
+
payTo: string;
|
|
60
|
+
psetBase64: string;
|
|
61
|
+
summaryHash: string;
|
|
62
|
+
payer?: string;
|
|
63
|
+
expiresAt: string;
|
|
64
|
+
}
|
|
65
|
+
export interface LiquidX402PreparePsetPaymentInput {
|
|
66
|
+
requirements: LiquidX402PaymentRequirements;
|
|
67
|
+
wallet: string;
|
|
68
|
+
payer?: string;
|
|
69
|
+
locktime?: number;
|
|
70
|
+
sign?: boolean;
|
|
71
|
+
}
|
|
72
|
+
export interface LiquidX402PreparePsetPaymentResult {
|
|
73
|
+
paymentPayload: LiquidX402PaymentPayload;
|
|
74
|
+
xPayment: string;
|
|
75
|
+
psetBase64: string;
|
|
76
|
+
summaryHash: string;
|
|
77
|
+
}
|
|
78
|
+
export interface LiquidX402VerifyPaymentInput {
|
|
79
|
+
requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
|
|
80
|
+
paymentPayload: LiquidX402PaymentPayload | Record<string, unknown>;
|
|
81
|
+
now?: Date;
|
|
82
|
+
}
|
|
83
|
+
export interface LiquidX402VerifyPaymentResult {
|
|
84
|
+
isValid: boolean;
|
|
85
|
+
invalidReason?: string;
|
|
86
|
+
payer?: string | null;
|
|
87
|
+
network?: LiquidX402Network;
|
|
88
|
+
paymentRequestId?: string;
|
|
89
|
+
summaryHash?: string;
|
|
90
|
+
}
|
|
91
|
+
export interface LiquidX402SettlePaymentInput extends LiquidX402VerifyPaymentInput {
|
|
92
|
+
wallet?: string;
|
|
93
|
+
broadcast?: boolean;
|
|
94
|
+
}
|
|
95
|
+
export interface LiquidX402SettlePaymentResult {
|
|
96
|
+
success: boolean;
|
|
97
|
+
errorCode?: string;
|
|
98
|
+
txId?: string;
|
|
99
|
+
transactionHash?: string;
|
|
100
|
+
rawTxHex?: string;
|
|
101
|
+
summaryHash?: string;
|
|
102
|
+
}
|
|
103
|
+
type DecodePsbtResult = {
|
|
104
|
+
fees?: Record<string, number>;
|
|
105
|
+
outputs?: Array<{
|
|
106
|
+
amount?: number;
|
|
107
|
+
asset?: string;
|
|
108
|
+
script?: {
|
|
109
|
+
address?: string;
|
|
110
|
+
hex?: string;
|
|
111
|
+
};
|
|
112
|
+
}>;
|
|
113
|
+
};
|
|
114
|
+
export declare const LIQUID_X402_ASSETS: Record<LiquidX402Network, Record<LiquidX402AssetKey, LiquidX402Asset>>;
|
|
115
|
+
export declare function listLiquidX402Assets(network?: LiquidX402Network): LiquidX402Asset[];
|
|
116
|
+
export declare function resolveLiquidX402Asset(asset: LiquidX402AssetKey | string, network?: LiquidX402Network): LiquidX402Asset;
|
|
117
|
+
export declare function buildLiquidX402Requirements(input: LiquidX402RequirementsInput): LiquidX402PaymentRequirements;
|
|
118
|
+
export declare function encodeLiquidXPayment(payload: LiquidX402PaymentPayload): string;
|
|
119
|
+
export declare function decodeLiquidXPayment(raw: string): LiquidX402PaymentPayload | null;
|
|
120
|
+
export declare function prepareLiquidX402PsetPayment(rpc: ElementsRpcClient, input: LiquidX402PreparePsetPaymentInput): Promise<LiquidX402PreparePsetPaymentResult>;
|
|
121
|
+
export declare function verifyLiquidX402Payment(rpc: ElementsRpcClient | null, input: LiquidX402VerifyPaymentInput): Promise<LiquidX402VerifyPaymentResult>;
|
|
122
|
+
export declare function settleLiquidX402Payment(rpc: ElementsRpcClient, input: LiquidX402SettlePaymentInput): Promise<LiquidX402SettlePaymentResult>;
|
|
123
|
+
export declare function verifyLiquidX402PayloadFields(requirements: LiquidX402PaymentRequirements | Record<string, unknown>, payload: LiquidX402PaymentPayload | Record<string, unknown>, now?: Date): LiquidX402VerifyPaymentResult;
|
|
124
|
+
export declare function buildLiquidPsetSummaryHash(decoded: DecodePsbtResult, requirements: LiquidX402PaymentRequirements | Record<string, unknown>): string;
|
|
125
|
+
export {};
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
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
|
+
exports.listLiquidX402Assets = listLiquidX402Assets;
|
|
5
|
+
exports.resolveLiquidX402Asset = resolveLiquidX402Asset;
|
|
6
|
+
exports.buildLiquidX402Requirements = buildLiquidX402Requirements;
|
|
7
|
+
exports.encodeLiquidXPayment = encodeLiquidXPayment;
|
|
8
|
+
exports.decodeLiquidXPayment = decodeLiquidXPayment;
|
|
9
|
+
exports.prepareLiquidX402PsetPayment = prepareLiquidX402PsetPayment;
|
|
10
|
+
exports.verifyLiquidX402Payment = verifyLiquidX402Payment;
|
|
11
|
+
exports.settleLiquidX402Payment = settleLiquidX402Payment;
|
|
12
|
+
exports.verifyLiquidX402PayloadFields = verifyLiquidX402PayloadFields;
|
|
13
|
+
exports.buildLiquidPsetSummaryHash = buildLiquidPsetSummaryHash;
|
|
14
|
+
const node_crypto_1 = require("node:crypto");
|
|
15
|
+
exports.LIQUID_X402_SCHEME = "exact-liquid-pset";
|
|
16
|
+
exports.LIQUID_X402_VERSION = 1;
|
|
17
|
+
exports.LIQUID_X402_DEFAULT_NETWORK = "liquidtestnet";
|
|
18
|
+
exports.LIQUID_X402_DEFAULT_TIMEOUT_SECONDS = 60;
|
|
19
|
+
const LIQUID_TESTNET_USDT_ASSET_ID = "b612eb46313a2cd6ebabd8b7a8eed5696e29898b87a43bff41c94f51acef9d73";
|
|
20
|
+
const LIQUID_MAINNET_USDT_ASSET_ID = "ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2";
|
|
21
|
+
exports.LIQUID_X402_ASSETS = {
|
|
22
|
+
liquidtestnet: {
|
|
23
|
+
lbtc: {
|
|
24
|
+
key: "lbtc",
|
|
25
|
+
label: "Liquid Bitcoin",
|
|
26
|
+
network: "liquidtestnet",
|
|
27
|
+
assetId: "bitcoin",
|
|
28
|
+
decimals: 8,
|
|
29
|
+
isNative: true,
|
|
30
|
+
},
|
|
31
|
+
usdt: {
|
|
32
|
+
key: "usdt",
|
|
33
|
+
label: "Tether USDt",
|
|
34
|
+
network: "liquidtestnet",
|
|
35
|
+
assetId: LIQUID_TESTNET_USDT_ASSET_ID,
|
|
36
|
+
decimals: 8,
|
|
37
|
+
isNative: false,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
liquidv1: {
|
|
41
|
+
lbtc: {
|
|
42
|
+
key: "lbtc",
|
|
43
|
+
label: "Liquid Bitcoin",
|
|
44
|
+
network: "liquidv1",
|
|
45
|
+
assetId: "bitcoin",
|
|
46
|
+
decimals: 8,
|
|
47
|
+
isNative: true,
|
|
48
|
+
},
|
|
49
|
+
usdt: {
|
|
50
|
+
key: "usdt",
|
|
51
|
+
label: "Tether USDt",
|
|
52
|
+
network: "liquidv1",
|
|
53
|
+
assetId: LIQUID_MAINNET_USDT_ASSET_ID,
|
|
54
|
+
decimals: 8,
|
|
55
|
+
isNative: false,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
function listLiquidX402Assets(network) {
|
|
60
|
+
if (network)
|
|
61
|
+
return Object.values(exports.LIQUID_X402_ASSETS[network]);
|
|
62
|
+
return Object.values(exports.LIQUID_X402_ASSETS).flatMap((assets) => Object.values(assets));
|
|
63
|
+
}
|
|
64
|
+
function resolveLiquidX402Asset(asset, network = exports.LIQUID_X402_DEFAULT_NETWORK) {
|
|
65
|
+
const key = normalizeAssetKey(asset);
|
|
66
|
+
const resolved = exports.LIQUID_X402_ASSETS[network]?.[key];
|
|
67
|
+
if (!resolved) {
|
|
68
|
+
throw new Error(`unsupported Liquid x402 asset: ${asset} on ${network}`);
|
|
69
|
+
}
|
|
70
|
+
return resolved;
|
|
71
|
+
}
|
|
72
|
+
function buildLiquidX402Requirements(input) {
|
|
73
|
+
const network = normalizeNetwork(input.network ?? exports.LIQUID_X402_DEFAULT_NETWORK);
|
|
74
|
+
const asset = resolveLiquidX402Asset(input.asset ?? "usdt", network);
|
|
75
|
+
const amountAtomic = normalizeAmountAtomic(input.amountAtomic);
|
|
76
|
+
const expiresAt = normalizeExpiresAt(input.expiresAt, input.maxTimeoutSeconds);
|
|
77
|
+
const description = String(input.description ?? "").trim() || "Unlock resource";
|
|
78
|
+
const mimeType = String(input.mimeType ?? "").trim() || "application/octet-stream";
|
|
79
|
+
const maxTimeoutSeconds = normalizeTimeout(input.maxTimeoutSeconds);
|
|
80
|
+
const paymentRequestId = String(input.paymentRequestId ?? "").trim();
|
|
81
|
+
if (!paymentRequestId)
|
|
82
|
+
throw new Error("paymentRequestId is required");
|
|
83
|
+
const resource = String(input.resource ?? "").trim();
|
|
84
|
+
if (!resource)
|
|
85
|
+
throw new Error("resource is required");
|
|
86
|
+
const payTo = String(input.payTo ?? "").trim();
|
|
87
|
+
if (!payTo)
|
|
88
|
+
throw new Error("payTo is required");
|
|
89
|
+
return {
|
|
90
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
91
|
+
network,
|
|
92
|
+
maxAmountRequired: amountAtomic,
|
|
93
|
+
resource,
|
|
94
|
+
description,
|
|
95
|
+
mimeType,
|
|
96
|
+
payTo,
|
|
97
|
+
maxTimeoutSeconds,
|
|
98
|
+
asset: asset.assetId,
|
|
99
|
+
extra: {
|
|
100
|
+
paymentRequestId,
|
|
101
|
+
asset: asset.key,
|
|
102
|
+
assetId: asset.assetId,
|
|
103
|
+
decimals: asset.decimals,
|
|
104
|
+
expiresAt,
|
|
105
|
+
feeAsset: "lbtc",
|
|
106
|
+
feeAssetId: exports.LIQUID_X402_ASSETS[network].lbtc.assetId,
|
|
107
|
+
...(input.maxFeeSat !== undefined ? { maxFeeSat: normalizeAmountAtomic(input.maxFeeSat) } : {}),
|
|
108
|
+
...(input.metadata ? { metadata: input.metadata } : {}),
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function encodeLiquidXPayment(payload) {
|
|
113
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
114
|
+
}
|
|
115
|
+
function decodeLiquidXPayment(raw) {
|
|
116
|
+
const value = String(raw ?? "").trim();
|
|
117
|
+
if (!value)
|
|
118
|
+
return null;
|
|
119
|
+
try {
|
|
120
|
+
const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
121
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
122
|
+
return null;
|
|
123
|
+
if (String(parsed.scheme ?? "") !== exports.LIQUID_X402_SCHEME)
|
|
124
|
+
return null;
|
|
125
|
+
return parsed;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function prepareLiquidX402PsetPayment(rpc, input) {
|
|
132
|
+
const requirements = coerceRequirements(input.requirements);
|
|
133
|
+
const amount = atomicToDecimalNumber(requirements.maxAmountRequired, requirements.extra.decimals);
|
|
134
|
+
const outputs = [
|
|
135
|
+
{ [requirements.payTo]: amount, asset: requirements.asset },
|
|
136
|
+
];
|
|
137
|
+
const pset = await rpc.call("createpsbt", [[], outputs, input.locktime ?? 0, true], input.wallet);
|
|
138
|
+
const updatedPset = await rpc.call("utxoupdatepsbt", [pset], input.wallet);
|
|
139
|
+
const signed = input.sign === false
|
|
140
|
+
? updatedPset
|
|
141
|
+
: (await rpc.call("walletprocesspsbt", [updatedPset, true, "ALL", true], input.wallet)).psbt ?? updatedPset;
|
|
142
|
+
const decoded = await rpc.call("decodepsbt", [signed], input.wallet);
|
|
143
|
+
const summaryHash = buildLiquidPsetSummaryHash(decoded, requirements);
|
|
144
|
+
const paymentPayload = {
|
|
145
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
146
|
+
network: requirements.network,
|
|
147
|
+
paymentRequestId: requirements.extra.paymentRequestId,
|
|
148
|
+
asset: requirements.extra.asset,
|
|
149
|
+
assetId: requirements.asset,
|
|
150
|
+
amountAtomic: requirements.maxAmountRequired,
|
|
151
|
+
payTo: requirements.payTo,
|
|
152
|
+
psetBase64: signed,
|
|
153
|
+
summaryHash,
|
|
154
|
+
...(input.payer ? { payer: input.payer } : {}),
|
|
155
|
+
expiresAt: requirements.extra.expiresAt,
|
|
156
|
+
};
|
|
157
|
+
return {
|
|
158
|
+
paymentPayload,
|
|
159
|
+
xPayment: encodeLiquidXPayment(paymentPayload),
|
|
160
|
+
psetBase64: signed,
|
|
161
|
+
summaryHash,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async function verifyLiquidX402Payment(rpc, input) {
|
|
165
|
+
const requirements = coerceRequirements(input.requirements);
|
|
166
|
+
const payload = coercePaymentPayload(input.paymentPayload);
|
|
167
|
+
const basic = verifyLiquidX402PayloadFields(requirements, payload, input.now);
|
|
168
|
+
if (!basic.isValid)
|
|
169
|
+
return basic;
|
|
170
|
+
if (!rpc) {
|
|
171
|
+
return basic;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const decoded = await rpc.call("decodepsbt", [payload.psetBase64]);
|
|
175
|
+
const actualSummaryHash = buildLiquidPsetSummaryHash(decoded, requirements);
|
|
176
|
+
if (actualSummaryHash !== payload.summaryHash) {
|
|
177
|
+
return { ...basic, isValid: false, invalidReason: "pset_summary_mismatch" };
|
|
178
|
+
}
|
|
179
|
+
if (!decodedContainsExpectedOutput(decoded, requirements)) {
|
|
180
|
+
return { ...basic, isValid: false, invalidReason: "pset_expected_output_missing" };
|
|
181
|
+
}
|
|
182
|
+
if (!decodedFeeWithinCap(decoded, requirements)) {
|
|
183
|
+
return { ...basic, isValid: false, invalidReason: "fee_too_high" };
|
|
184
|
+
}
|
|
185
|
+
return basic;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return { ...basic, isValid: false, invalidReason: "pset_decode_failed" };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function settleLiquidX402Payment(rpc, input) {
|
|
192
|
+
const verification = await verifyLiquidX402Payment(rpc, input);
|
|
193
|
+
if (!verification.isValid) {
|
|
194
|
+
return { success: false, errorCode: verification.invalidReason ?? "payment_verification_failed" };
|
|
195
|
+
}
|
|
196
|
+
const payload = coercePaymentPayload(input.paymentPayload);
|
|
197
|
+
try {
|
|
198
|
+
const finalized = await rpc.call("finalizepsbt", [payload.psetBase64, true], input.wallet);
|
|
199
|
+
if (!finalized.complete || !finalized.hex) {
|
|
200
|
+
return { success: false, errorCode: "pset_not_complete", summaryHash: payload.summaryHash };
|
|
201
|
+
}
|
|
202
|
+
const mempool = await rpc.call("testmempoolaccept", [[finalized.hex]], input.wallet);
|
|
203
|
+
if (mempool[0]?.allowed !== true) {
|
|
204
|
+
return {
|
|
205
|
+
success: false,
|
|
206
|
+
errorCode: mempool[0]?.reject_reason || "mempool_rejected",
|
|
207
|
+
rawTxHex: finalized.hex,
|
|
208
|
+
summaryHash: payload.summaryHash,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (input.broadcast === false) {
|
|
212
|
+
return { success: true, rawTxHex: finalized.hex, summaryHash: payload.summaryHash };
|
|
213
|
+
}
|
|
214
|
+
const txId = await rpc.call("sendrawtransaction", [finalized.hex], input.wallet);
|
|
215
|
+
return {
|
|
216
|
+
success: true,
|
|
217
|
+
txId,
|
|
218
|
+
transactionHash: txId,
|
|
219
|
+
rawTxHex: finalized.hex,
|
|
220
|
+
summaryHash: payload.summaryHash,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return { success: false, errorCode: "settlement_rpc_failed", summaryHash: payload.summaryHash };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function verifyLiquidX402PayloadFields(requirements, payload, now = new Date()) {
|
|
228
|
+
const req = coerceRequirements(requirements);
|
|
229
|
+
const parsed = coercePaymentPayload(payload);
|
|
230
|
+
const base = {
|
|
231
|
+
payer: parsed.payer ?? null,
|
|
232
|
+
network: parsed.network,
|
|
233
|
+
paymentRequestId: parsed.paymentRequestId,
|
|
234
|
+
summaryHash: parsed.summaryHash,
|
|
235
|
+
};
|
|
236
|
+
if (parsed.scheme !== exports.LIQUID_X402_SCHEME)
|
|
237
|
+
return { ...base, isValid: false, invalidReason: "unsupported_scheme" };
|
|
238
|
+
if (parsed.network !== req.network)
|
|
239
|
+
return { ...base, isValid: false, invalidReason: "network_mismatch" };
|
|
240
|
+
if (parsed.paymentRequestId !== req.extra.paymentRequestId) {
|
|
241
|
+
return { ...base, isValid: false, invalidReason: "payment_request_mismatch" };
|
|
242
|
+
}
|
|
243
|
+
if (parsed.asset !== req.extra.asset || parsed.assetId !== req.asset) {
|
|
244
|
+
return { ...base, isValid: false, invalidReason: "asset_mismatch" };
|
|
245
|
+
}
|
|
246
|
+
if (parsed.amountAtomic !== req.maxAmountRequired) {
|
|
247
|
+
return { ...base, isValid: false, invalidReason: "amount_mismatch" };
|
|
248
|
+
}
|
|
249
|
+
if (parsed.payTo !== req.payTo)
|
|
250
|
+
return { ...base, isValid: false, invalidReason: "recipient_mismatch" };
|
|
251
|
+
if (!parsed.psetBase64)
|
|
252
|
+
return { ...base, isValid: false, invalidReason: "pset_missing" };
|
|
253
|
+
if (!parsed.summaryHash)
|
|
254
|
+
return { ...base, isValid: false, invalidReason: "summary_hash_missing" };
|
|
255
|
+
if (new Date(parsed.expiresAt).getTime() <= now.getTime()) {
|
|
256
|
+
return { ...base, isValid: false, invalidReason: "payment_expired" };
|
|
257
|
+
}
|
|
258
|
+
return { ...base, isValid: true };
|
|
259
|
+
}
|
|
260
|
+
function buildLiquidPsetSummaryHash(decoded, requirements) {
|
|
261
|
+
const req = coerceRequirements(requirements);
|
|
262
|
+
return sha256Hex(stableStringify({
|
|
263
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
264
|
+
network: req.network,
|
|
265
|
+
paymentRequestId: req.extra.paymentRequestId,
|
|
266
|
+
asset: req.extra.asset,
|
|
267
|
+
assetId: req.asset,
|
|
268
|
+
amountAtomic: req.maxAmountRequired,
|
|
269
|
+
payTo: req.payTo,
|
|
270
|
+
outputs: normalizeDecodedOutputs(decoded),
|
|
271
|
+
fees: decoded.fees ?? {},
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
function decodedContainsExpectedOutput(decoded, requirements) {
|
|
275
|
+
const expectedAmount = atomicToDecimalNumber(requirements.maxAmountRequired, requirements.extra.decimals);
|
|
276
|
+
return (decoded.outputs ?? []).some((output) => (String(output.asset ?? "").toLowerCase() === requirements.asset.toLowerCase() &&
|
|
277
|
+
Math.round(Number(output.amount ?? 0) * 10 ** requirements.extra.decimals) ===
|
|
278
|
+
Math.round(expectedAmount * 10 ** requirements.extra.decimals) &&
|
|
279
|
+
String(output.script?.address ?? "") === requirements.payTo));
|
|
280
|
+
}
|
|
281
|
+
function normalizeDecodedOutputs(decoded) {
|
|
282
|
+
return (decoded.outputs ?? []).map((output) => ({
|
|
283
|
+
amount: output.amount ?? null,
|
|
284
|
+
asset: output.asset ?? null,
|
|
285
|
+
address: output.script?.address ?? null,
|
|
286
|
+
scriptPubKey: output.script?.hex ?? null,
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
function coerceRequirements(value) {
|
|
290
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
291
|
+
throw new Error("requirements must be an object");
|
|
292
|
+
const raw = value;
|
|
293
|
+
const extra = raw.extra && typeof raw.extra === "object" && !Array.isArray(raw.extra) ? raw.extra : {};
|
|
294
|
+
const network = normalizeNetwork(raw.network);
|
|
295
|
+
const asset = resolveLiquidX402Asset(extra.asset ?? raw.asset, network);
|
|
296
|
+
return {
|
|
297
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
298
|
+
network,
|
|
299
|
+
maxAmountRequired: normalizeAmountAtomic(raw.maxAmountRequired),
|
|
300
|
+
resource: String(raw.resource ?? ""),
|
|
301
|
+
description: String(raw.description ?? ""),
|
|
302
|
+
mimeType: String(raw.mimeType ?? ""),
|
|
303
|
+
payTo: String(raw.payTo ?? ""),
|
|
304
|
+
maxTimeoutSeconds: normalizeTimeout(raw.maxTimeoutSeconds),
|
|
305
|
+
asset: String(raw.asset ?? extra.assetId ?? asset.assetId),
|
|
306
|
+
extra: {
|
|
307
|
+
paymentRequestId: String(extra.paymentRequestId ?? raw.paymentRequestId ?? ""),
|
|
308
|
+
asset: asset.key,
|
|
309
|
+
assetId: String(extra.assetId ?? raw.asset ?? asset.assetId),
|
|
310
|
+
decimals: Number(extra.decimals ?? asset.decimals),
|
|
311
|
+
expiresAt: String(extra.expiresAt ?? ""),
|
|
312
|
+
feeAsset: "lbtc",
|
|
313
|
+
feeAssetId: String(extra.feeAssetId ?? exports.LIQUID_X402_ASSETS[network].lbtc.assetId),
|
|
314
|
+
...(extra.maxFeeSat !== undefined ? { maxFeeSat: normalizeAmountAtomic(extra.maxFeeSat) } : {}),
|
|
315
|
+
...(extra.metadata ? { metadata: extra.metadata } : {}),
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function decodedFeeWithinCap(decoded, requirements) {
|
|
320
|
+
if (requirements.extra.maxFeeSat === undefined)
|
|
321
|
+
return true;
|
|
322
|
+
const maxFeeSat = BigInt(requirements.extra.maxFeeSat);
|
|
323
|
+
const feeSat = Object.values(decoded.fees ?? {}).reduce((sum, amount) => {
|
|
324
|
+
const sat = Math.round(Number(amount) * 100_000_000);
|
|
325
|
+
return sum + BigInt(Number.isFinite(sat) ? sat : 0);
|
|
326
|
+
}, 0n);
|
|
327
|
+
return feeSat <= maxFeeSat;
|
|
328
|
+
}
|
|
329
|
+
function coercePaymentPayload(value) {
|
|
330
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
331
|
+
throw new Error("payment payload must be an object");
|
|
332
|
+
const raw = value;
|
|
333
|
+
return {
|
|
334
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
335
|
+
network: normalizeNetwork(raw.network),
|
|
336
|
+
paymentRequestId: String(raw.paymentRequestId ?? ""),
|
|
337
|
+
asset: normalizeAssetKey(String(raw.asset ?? "")),
|
|
338
|
+
assetId: String(raw.assetId ?? ""),
|
|
339
|
+
amountAtomic: normalizeAmountAtomic(raw.amountAtomic),
|
|
340
|
+
payTo: String(raw.payTo ?? ""),
|
|
341
|
+
psetBase64: String(raw.psetBase64 ?? ""),
|
|
342
|
+
summaryHash: String(raw.summaryHash ?? ""),
|
|
343
|
+
...(raw.payer ? { payer: String(raw.payer) } : {}),
|
|
344
|
+
expiresAt: String(raw.expiresAt ?? ""),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function normalizeNetwork(input) {
|
|
348
|
+
const value = String(input ?? "").trim().toLowerCase();
|
|
349
|
+
if (value === "liquidtestnet" || value === "liquidv1")
|
|
350
|
+
return value;
|
|
351
|
+
throw new Error(`unsupported Liquid x402 network: ${String(input)}`);
|
|
352
|
+
}
|
|
353
|
+
function normalizeAssetKey(input) {
|
|
354
|
+
const value = String(input ?? "").trim().toLowerCase();
|
|
355
|
+
if (value === "lbtc" || value === "bitcoin")
|
|
356
|
+
return "lbtc";
|
|
357
|
+
if (value === "usdt" || value === "usdt-liquid" || value === "tether")
|
|
358
|
+
return "usdt";
|
|
359
|
+
throw new Error(`unsupported Liquid x402 asset: ${String(input)}`);
|
|
360
|
+
}
|
|
361
|
+
function normalizeAmountAtomic(input) {
|
|
362
|
+
const raw = typeof input === "bigint" ? input.toString() : String(input ?? "").trim();
|
|
363
|
+
if (!/^\d+$/u.test(raw))
|
|
364
|
+
throw new Error("amountAtomic must be an integer string");
|
|
365
|
+
return BigInt(raw).toString();
|
|
366
|
+
}
|
|
367
|
+
function normalizeTimeout(input) {
|
|
368
|
+
const parsed = Number(input ?? exports.LIQUID_X402_DEFAULT_TIMEOUT_SECONDS);
|
|
369
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : exports.LIQUID_X402_DEFAULT_TIMEOUT_SECONDS;
|
|
370
|
+
}
|
|
371
|
+
function normalizeExpiresAt(expiresAt, timeoutSeconds) {
|
|
372
|
+
if (expiresAt instanceof Date)
|
|
373
|
+
return expiresAt.toISOString();
|
|
374
|
+
const raw = String(expiresAt ?? "").trim();
|
|
375
|
+
if (raw)
|
|
376
|
+
return new Date(raw).toISOString();
|
|
377
|
+
return new Date(Date.now() + normalizeTimeout(timeoutSeconds) * 1000).toISOString();
|
|
378
|
+
}
|
|
379
|
+
function atomicToDecimalNumber(amountAtomic, decimals) {
|
|
380
|
+
const amount = BigInt(amountAtomic);
|
|
381
|
+
const scale = 10n ** BigInt(decimals);
|
|
382
|
+
const whole = amount / scale;
|
|
383
|
+
const frac = amount % scale;
|
|
384
|
+
return Number(`${whole}.${frac.toString().padStart(decimals, "0")}`);
|
|
385
|
+
}
|
|
386
|
+
function sha256Hex(value) {
|
|
387
|
+
return (0, node_crypto_1.createHash)("sha256").update(value, "utf8").digest("hex");
|
|
388
|
+
}
|
|
389
|
+
function stableStringify(value) {
|
|
390
|
+
if (value === null || typeof value !== "object")
|
|
391
|
+
return JSON.stringify(value);
|
|
392
|
+
if (Array.isArray(value))
|
|
393
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
394
|
+
const record = value;
|
|
395
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
|
|
396
|
+
}
|