@hazbase/simplicity 0.2.1 → 0.3.1
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 +16 -0
- package/dist/client/SimplicityClient.d.ts +14 -0
- package/dist/client/SimplicityClient.js +47 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -4
- package/dist/x402/index.d.ts +188 -0
- package/dist/x402/index.js +559 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -193,6 +193,22 @@ RECEIVABLE_OUTPUT_BINDING_MODE=script-bound npm run e2e:receivable-testnet
|
|
|
193
193
|
RECEIVABLE_OUTPUT_BINDING_MODE=descriptor-bound npm run e2e:receivable-testnet
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
+
Liquid x402 buyer smoke test:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
npm install --no-save lwk_node@^0.17.1
|
|
200
|
+
npm run e2e:x402-lwk-address -- --generate
|
|
201
|
+
npm run e2e:x402-lwk-buyer -- https://share.example/v/slug --dry-run
|
|
202
|
+
LIQUID_X402_E2E_MNEMONIC="..." npm run e2e:x402-lwk-buyer -- https://share.example/v/slug --no-submit
|
|
203
|
+
LIQUID_X402_E2E_MNEMONIC="..." npm run e2e:x402-lwk-buyer -- https://share.example/v/slug --output unlocked.bin
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The address script derives a confidential Liquid receive address for funding a
|
|
207
|
+
test buyer wallet. The buyer script then uses LWK locally to scan, build, sign,
|
|
208
|
+
and finalize a Liquid PSET, then sends the resulting `X-PAYMENT` header back to
|
|
209
|
+
the protected URL. `--dry-run` only inspects the 402 requirements, while
|
|
210
|
+
`--no-submit` signs a payment without broadcasting through the seller backend.
|
|
211
|
+
|
|
196
212
|
These commands are useful both as reproducible checks and as examples of the SDK's current validated surface.
|
|
197
213
|
|
|
198
214
|
## Where To Go Next
|
|
@@ -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,19 @@ 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
|
+
buildPaymentFromPset: typeof liquidX402.buildLiquidX402PaymentFromPset;
|
|
26
|
+
prepareLwkWasmPayment: typeof liquidX402.prepareLiquidX402LwkWasmPayment;
|
|
27
|
+
deriveLwkWasmAddress: typeof liquidX402.deriveLiquidX402LwkWasmAddress;
|
|
28
|
+
verify: (input: liquidX402.LiquidX402VerifyPaymentInput) => Promise<liquidX402.LiquidX402VerifyPaymentResult>;
|
|
29
|
+
settle: (input: liquidX402.LiquidX402SettlePaymentInput) => Promise<liquidX402.LiquidX402SettlePaymentResult>;
|
|
30
|
+
};
|
|
17
31
|
};
|
|
18
32
|
readonly outputBinding: {
|
|
19
33
|
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,19 @@ 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
|
+
buildPaymentFromPset: liquidX402.buildLiquidX402PaymentFromPset,
|
|
75
|
+
prepareLwkWasmPayment: liquidX402.prepareLiquidX402LwkWasmPayment,
|
|
76
|
+
deriveLwkWasmAddress: liquidX402.deriveLiquidX402LwkWasmAddress,
|
|
77
|
+
verify: async (input) => liquidX402.verifyLiquidX402Payment(this.rpc, input),
|
|
78
|
+
settle: async (input) => liquidX402.settleLiquidX402Payment(this.rpc, input),
|
|
79
|
+
},
|
|
33
80
|
};
|
|
34
81
|
this.outputBinding = {
|
|
35
82
|
describeSupport: () => (0, outputBinding_1.describeOutputBindingSupport)(),
|
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,188 @@
|
|
|
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 LiquidX402BuildPaymentFromPsetInput {
|
|
79
|
+
requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
|
|
80
|
+
psetBase64: string;
|
|
81
|
+
decoded?: DecodePsbtResult;
|
|
82
|
+
payer?: string;
|
|
83
|
+
summaryHash?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface LiquidX402PrepareLwkWasmPaymentInput {
|
|
86
|
+
requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
|
|
87
|
+
mnemonic: string;
|
|
88
|
+
payer?: string;
|
|
89
|
+
esploraUrl?: string;
|
|
90
|
+
waterfalls?: boolean;
|
|
91
|
+
concurrency?: number;
|
|
92
|
+
utxoOnly?: boolean;
|
|
93
|
+
scan?: boolean;
|
|
94
|
+
scanToIndex?: number;
|
|
95
|
+
feeRate?: number;
|
|
96
|
+
finalize?: boolean;
|
|
97
|
+
lwk?: LiquidX402LwkWasmModule;
|
|
98
|
+
importLwk?: () => Promise<LiquidX402LwkWasmModule>;
|
|
99
|
+
}
|
|
100
|
+
export type LiquidX402PrepareLwkWasmPaymentResult = LiquidX402PreparePsetPaymentResult & {
|
|
101
|
+
descriptor: string;
|
|
102
|
+
dwid?: string;
|
|
103
|
+
};
|
|
104
|
+
export interface LiquidX402DeriveLwkWasmAddressInput {
|
|
105
|
+
mnemonic: string;
|
|
106
|
+
network?: LiquidX402Network;
|
|
107
|
+
index?: number;
|
|
108
|
+
lwk?: LiquidX402LwkWasmModule;
|
|
109
|
+
importLwk?: () => Promise<LiquidX402LwkWasmModule>;
|
|
110
|
+
}
|
|
111
|
+
export interface LiquidX402DeriveLwkWasmAddressResult {
|
|
112
|
+
network: LiquidX402Network;
|
|
113
|
+
address: string;
|
|
114
|
+
unconfidentialAddress?: string;
|
|
115
|
+
index: number;
|
|
116
|
+
isBlinded?: boolean;
|
|
117
|
+
descriptor: string;
|
|
118
|
+
dwid?: string;
|
|
119
|
+
policyAsset?: string;
|
|
120
|
+
}
|
|
121
|
+
export interface LiquidX402VerifyPaymentInput {
|
|
122
|
+
requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
|
|
123
|
+
paymentPayload: LiquidX402PaymentPayload | Record<string, unknown>;
|
|
124
|
+
now?: Date;
|
|
125
|
+
}
|
|
126
|
+
export interface LiquidX402VerifyPaymentResult {
|
|
127
|
+
isValid: boolean;
|
|
128
|
+
invalidReason?: string;
|
|
129
|
+
payer?: string | null;
|
|
130
|
+
network?: LiquidX402Network;
|
|
131
|
+
paymentRequestId?: string;
|
|
132
|
+
summaryHash?: string;
|
|
133
|
+
}
|
|
134
|
+
export interface LiquidX402SettlePaymentInput extends LiquidX402VerifyPaymentInput {
|
|
135
|
+
wallet?: string;
|
|
136
|
+
broadcast?: boolean;
|
|
137
|
+
}
|
|
138
|
+
export interface LiquidX402SettlePaymentResult {
|
|
139
|
+
success: boolean;
|
|
140
|
+
errorCode?: string;
|
|
141
|
+
txId?: string;
|
|
142
|
+
transactionHash?: string;
|
|
143
|
+
rawTxHex?: string;
|
|
144
|
+
summaryHash?: string;
|
|
145
|
+
}
|
|
146
|
+
export type DecodePsbtResult = {
|
|
147
|
+
fees?: Record<string, number>;
|
|
148
|
+
outputs?: Array<{
|
|
149
|
+
amount?: number;
|
|
150
|
+
asset?: string;
|
|
151
|
+
script?: {
|
|
152
|
+
address?: string;
|
|
153
|
+
hex?: string;
|
|
154
|
+
};
|
|
155
|
+
}>;
|
|
156
|
+
};
|
|
157
|
+
export type LiquidX402LwkWasmModule = {
|
|
158
|
+
Address: {
|
|
159
|
+
new (value: string): any;
|
|
160
|
+
parse?: (value: string, network: any) => any;
|
|
161
|
+
};
|
|
162
|
+
AssetId: {
|
|
163
|
+
new (value: string): any;
|
|
164
|
+
fromString?: (value: string) => any;
|
|
165
|
+
};
|
|
166
|
+
EsploraClient: new (network: any, url: string, waterfalls: boolean, concurrency: number, utxoOnly: boolean) => any;
|
|
167
|
+
Mnemonic: new (value: string) => any;
|
|
168
|
+
Network: {
|
|
169
|
+
mainnet: () => any;
|
|
170
|
+
testnet: () => any;
|
|
171
|
+
};
|
|
172
|
+
Signer: new (mnemonic: any, network: any) => any;
|
|
173
|
+
Wollet: new (network: any, descriptor: any) => any;
|
|
174
|
+
};
|
|
175
|
+
export declare const LIQUID_X402_ASSETS: Record<LiquidX402Network, Record<LiquidX402AssetKey, LiquidX402Asset>>;
|
|
176
|
+
export declare function listLiquidX402Assets(network?: LiquidX402Network): LiquidX402Asset[];
|
|
177
|
+
export declare function resolveLiquidX402Asset(asset: LiquidX402AssetKey | string, network?: LiquidX402Network): LiquidX402Asset;
|
|
178
|
+
export declare function buildLiquidX402Requirements(input: LiquidX402RequirementsInput): LiquidX402PaymentRequirements;
|
|
179
|
+
export declare function encodeLiquidXPayment(payload: LiquidX402PaymentPayload): string;
|
|
180
|
+
export declare function decodeLiquidXPayment(raw: string): LiquidX402PaymentPayload | null;
|
|
181
|
+
export declare function prepareLiquidX402PsetPayment(rpc: ElementsRpcClient, input: LiquidX402PreparePsetPaymentInput): Promise<LiquidX402PreparePsetPaymentResult>;
|
|
182
|
+
export declare function buildLiquidX402PaymentFromPset(input: LiquidX402BuildPaymentFromPsetInput): LiquidX402PreparePsetPaymentResult;
|
|
183
|
+
export declare function prepareLiquidX402LwkWasmPayment(input: LiquidX402PrepareLwkWasmPaymentInput): Promise<LiquidX402PrepareLwkWasmPaymentResult>;
|
|
184
|
+
export declare function deriveLiquidX402LwkWasmAddress(input: LiquidX402DeriveLwkWasmAddressInput): Promise<LiquidX402DeriveLwkWasmAddressResult>;
|
|
185
|
+
export declare function verifyLiquidX402Payment(rpc: ElementsRpcClient | null, input: LiquidX402VerifyPaymentInput): Promise<LiquidX402VerifyPaymentResult>;
|
|
186
|
+
export declare function settleLiquidX402Payment(rpc: ElementsRpcClient, input: LiquidX402SettlePaymentInput): Promise<LiquidX402SettlePaymentResult>;
|
|
187
|
+
export declare function verifyLiquidX402PayloadFields(requirements: LiquidX402PaymentRequirements | Record<string, unknown>, payload: LiquidX402PaymentPayload | Record<string, unknown>, now?: Date): LiquidX402VerifyPaymentResult;
|
|
188
|
+
export declare function buildLiquidPsetSummaryHash(_decoded: DecodePsbtResult, requirements: LiquidX402PaymentRequirements | Record<string, unknown>): string;
|
|
@@ -0,0 +1,559 @@
|
|
|
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.buildLiquidX402PaymentFromPset = buildLiquidX402PaymentFromPset;
|
|
11
|
+
exports.prepareLiquidX402LwkWasmPayment = prepareLiquidX402LwkWasmPayment;
|
|
12
|
+
exports.deriveLiquidX402LwkWasmAddress = deriveLiquidX402LwkWasmAddress;
|
|
13
|
+
exports.verifyLiquidX402Payment = verifyLiquidX402Payment;
|
|
14
|
+
exports.settleLiquidX402Payment = settleLiquidX402Payment;
|
|
15
|
+
exports.verifyLiquidX402PayloadFields = verifyLiquidX402PayloadFields;
|
|
16
|
+
exports.buildLiquidPsetSummaryHash = buildLiquidPsetSummaryHash;
|
|
17
|
+
const node_crypto_1 = require("node:crypto");
|
|
18
|
+
exports.LIQUID_X402_SCHEME = "exact-liquid-pset";
|
|
19
|
+
exports.LIQUID_X402_VERSION = 1;
|
|
20
|
+
exports.LIQUID_X402_DEFAULT_NETWORK = "liquidtestnet";
|
|
21
|
+
exports.LIQUID_X402_DEFAULT_TIMEOUT_SECONDS = 60;
|
|
22
|
+
const LIQUID_TESTNET_USDT_ASSET_ID = "b612eb46313a2cd6ebabd8b7a8eed5696e29898b87a43bff41c94f51acef9d73";
|
|
23
|
+
const LIQUID_MAINNET_USDT_ASSET_ID = "ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2";
|
|
24
|
+
const LIQUID_TESTNET_LBTC_ASSET_ID = "144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49";
|
|
25
|
+
const LIQUID_MAINNET_LBTC_ASSET_ID = "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d";
|
|
26
|
+
exports.LIQUID_X402_ASSETS = {
|
|
27
|
+
liquidtestnet: {
|
|
28
|
+
lbtc: {
|
|
29
|
+
key: "lbtc",
|
|
30
|
+
label: "Liquid Bitcoin",
|
|
31
|
+
network: "liquidtestnet",
|
|
32
|
+
assetId: LIQUID_TESTNET_LBTC_ASSET_ID,
|
|
33
|
+
decimals: 8,
|
|
34
|
+
isNative: true,
|
|
35
|
+
},
|
|
36
|
+
usdt: {
|
|
37
|
+
key: "usdt",
|
|
38
|
+
label: "Tether USDt",
|
|
39
|
+
network: "liquidtestnet",
|
|
40
|
+
assetId: LIQUID_TESTNET_USDT_ASSET_ID,
|
|
41
|
+
decimals: 8,
|
|
42
|
+
isNative: false,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
liquidv1: {
|
|
46
|
+
lbtc: {
|
|
47
|
+
key: "lbtc",
|
|
48
|
+
label: "Liquid Bitcoin",
|
|
49
|
+
network: "liquidv1",
|
|
50
|
+
assetId: LIQUID_MAINNET_LBTC_ASSET_ID,
|
|
51
|
+
decimals: 8,
|
|
52
|
+
isNative: true,
|
|
53
|
+
},
|
|
54
|
+
usdt: {
|
|
55
|
+
key: "usdt",
|
|
56
|
+
label: "Tether USDt",
|
|
57
|
+
network: "liquidv1",
|
|
58
|
+
assetId: LIQUID_MAINNET_USDT_ASSET_ID,
|
|
59
|
+
decimals: 8,
|
|
60
|
+
isNative: false,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
function listLiquidX402Assets(network) {
|
|
65
|
+
if (network)
|
|
66
|
+
return Object.values(exports.LIQUID_X402_ASSETS[network]);
|
|
67
|
+
return Object.values(exports.LIQUID_X402_ASSETS).flatMap((assets) => Object.values(assets));
|
|
68
|
+
}
|
|
69
|
+
function resolveLiquidX402Asset(asset, network = exports.LIQUID_X402_DEFAULT_NETWORK) {
|
|
70
|
+
const key = normalizeAssetKey(asset);
|
|
71
|
+
const resolved = exports.LIQUID_X402_ASSETS[network]?.[key];
|
|
72
|
+
if (!resolved) {
|
|
73
|
+
throw new Error(`unsupported Liquid x402 asset: ${asset} on ${network}`);
|
|
74
|
+
}
|
|
75
|
+
return resolved;
|
|
76
|
+
}
|
|
77
|
+
function buildLiquidX402Requirements(input) {
|
|
78
|
+
const network = normalizeNetwork(input.network ?? exports.LIQUID_X402_DEFAULT_NETWORK);
|
|
79
|
+
const asset = resolveLiquidX402Asset(input.asset ?? "usdt", network);
|
|
80
|
+
const amountAtomic = normalizeAmountAtomic(input.amountAtomic);
|
|
81
|
+
const expiresAt = normalizeExpiresAt(input.expiresAt, input.maxTimeoutSeconds);
|
|
82
|
+
const description = String(input.description ?? "").trim() || "Unlock resource";
|
|
83
|
+
const mimeType = String(input.mimeType ?? "").trim() || "application/octet-stream";
|
|
84
|
+
const maxTimeoutSeconds = normalizeTimeout(input.maxTimeoutSeconds);
|
|
85
|
+
const paymentRequestId = String(input.paymentRequestId ?? "").trim();
|
|
86
|
+
if (!paymentRequestId)
|
|
87
|
+
throw new Error("paymentRequestId is required");
|
|
88
|
+
const resource = String(input.resource ?? "").trim();
|
|
89
|
+
if (!resource)
|
|
90
|
+
throw new Error("resource is required");
|
|
91
|
+
const payTo = String(input.payTo ?? "").trim();
|
|
92
|
+
if (!payTo)
|
|
93
|
+
throw new Error("payTo is required");
|
|
94
|
+
return {
|
|
95
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
96
|
+
network,
|
|
97
|
+
maxAmountRequired: amountAtomic,
|
|
98
|
+
resource,
|
|
99
|
+
description,
|
|
100
|
+
mimeType,
|
|
101
|
+
payTo,
|
|
102
|
+
maxTimeoutSeconds,
|
|
103
|
+
asset: asset.assetId,
|
|
104
|
+
extra: {
|
|
105
|
+
paymentRequestId,
|
|
106
|
+
asset: asset.key,
|
|
107
|
+
assetId: asset.assetId,
|
|
108
|
+
decimals: asset.decimals,
|
|
109
|
+
expiresAt,
|
|
110
|
+
feeAsset: "lbtc",
|
|
111
|
+
feeAssetId: exports.LIQUID_X402_ASSETS[network].lbtc.assetId,
|
|
112
|
+
...(input.maxFeeSat !== undefined ? { maxFeeSat: normalizeAmountAtomic(input.maxFeeSat) } : {}),
|
|
113
|
+
...(input.metadata ? { metadata: input.metadata } : {}),
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function encodeLiquidXPayment(payload) {
|
|
118
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
119
|
+
}
|
|
120
|
+
function decodeLiquidXPayment(raw) {
|
|
121
|
+
const value = String(raw ?? "").trim();
|
|
122
|
+
if (!value)
|
|
123
|
+
return null;
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
126
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
127
|
+
return null;
|
|
128
|
+
if (String(parsed.scheme ?? "") !== exports.LIQUID_X402_SCHEME)
|
|
129
|
+
return null;
|
|
130
|
+
return parsed;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function prepareLiquidX402PsetPayment(rpc, input) {
|
|
137
|
+
const requirements = coerceRequirements(input.requirements);
|
|
138
|
+
const amount = atomicToDecimalNumber(requirements.maxAmountRequired, requirements.extra.decimals);
|
|
139
|
+
const outputs = [
|
|
140
|
+
{ [requirements.payTo]: amount, asset: requirements.asset },
|
|
141
|
+
];
|
|
142
|
+
const pset = await rpc.call("createpsbt", [[], outputs, input.locktime ?? 0, true], input.wallet);
|
|
143
|
+
const updatedPset = await rpc.call("utxoupdatepsbt", [pset], input.wallet);
|
|
144
|
+
const signed = input.sign === false
|
|
145
|
+
? updatedPset
|
|
146
|
+
: (await rpc.call("walletprocesspsbt", [updatedPset, true, "ALL", true], input.wallet)).psbt ?? updatedPset;
|
|
147
|
+
const decoded = await rpc.call("decodepsbt", [signed], input.wallet);
|
|
148
|
+
const summaryHash = buildLiquidPsetSummaryHash(decoded, requirements);
|
|
149
|
+
const paymentPayload = {
|
|
150
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
151
|
+
network: requirements.network,
|
|
152
|
+
paymentRequestId: requirements.extra.paymentRequestId,
|
|
153
|
+
asset: requirements.extra.asset,
|
|
154
|
+
assetId: requirements.asset,
|
|
155
|
+
amountAtomic: requirements.maxAmountRequired,
|
|
156
|
+
payTo: requirements.payTo,
|
|
157
|
+
psetBase64: signed,
|
|
158
|
+
summaryHash,
|
|
159
|
+
...(input.payer ? { payer: input.payer } : {}),
|
|
160
|
+
expiresAt: requirements.extra.expiresAt,
|
|
161
|
+
};
|
|
162
|
+
return {
|
|
163
|
+
paymentPayload,
|
|
164
|
+
xPayment: encodeLiquidXPayment(paymentPayload),
|
|
165
|
+
psetBase64: signed,
|
|
166
|
+
summaryHash,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function buildLiquidX402PaymentFromPset(input) {
|
|
170
|
+
const requirements = coerceRequirements(input.requirements);
|
|
171
|
+
const psetBase64 = String(input.psetBase64 ?? "").trim();
|
|
172
|
+
if (!psetBase64)
|
|
173
|
+
throw new Error("psetBase64 is required");
|
|
174
|
+
const summaryHash = input.summaryHash ?? buildLiquidPsetSummaryHash(input.decoded ?? {}, requirements);
|
|
175
|
+
const paymentPayload = {
|
|
176
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
177
|
+
network: requirements.network,
|
|
178
|
+
paymentRequestId: requirements.extra.paymentRequestId,
|
|
179
|
+
asset: requirements.extra.asset,
|
|
180
|
+
assetId: requirements.asset,
|
|
181
|
+
amountAtomic: requirements.maxAmountRequired,
|
|
182
|
+
payTo: requirements.payTo,
|
|
183
|
+
psetBase64,
|
|
184
|
+
summaryHash,
|
|
185
|
+
...(input.payer ? { payer: input.payer } : {}),
|
|
186
|
+
expiresAt: requirements.extra.expiresAt,
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
paymentPayload,
|
|
190
|
+
xPayment: encodeLiquidXPayment(paymentPayload),
|
|
191
|
+
psetBase64,
|
|
192
|
+
summaryHash,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async function prepareLiquidX402LwkWasmPayment(input) {
|
|
196
|
+
const requirements = coerceRequirements(input.requirements);
|
|
197
|
+
ensureLwkWasmNodeTimerCompat();
|
|
198
|
+
const lwk = input.lwk ?? await loadLwkWasm(input.importLwk);
|
|
199
|
+
const network = requirements.network === "liquidv1" ? lwk.Network.mainnet() : lwk.Network.testnet();
|
|
200
|
+
const mnemonic = new lwk.Mnemonic(input.mnemonic);
|
|
201
|
+
const signer = new lwk.Signer(mnemonic, network);
|
|
202
|
+
const descriptor = signer.wpkhSlip77Descriptor();
|
|
203
|
+
const wollet = new lwk.Wollet(network, descriptor);
|
|
204
|
+
const client = input.esploraUrl
|
|
205
|
+
? new lwk.EsploraClient(network, input.esploraUrl, input.waterfalls ?? false, input.concurrency ?? 4, input.utxoOnly ?? false)
|
|
206
|
+
: network.defaultEsploraClient();
|
|
207
|
+
if (input.scan !== false) {
|
|
208
|
+
const update = input.scanToIndex !== undefined
|
|
209
|
+
? await client.fullScanToIndex(wollet, input.scanToIndex)
|
|
210
|
+
: await client.fullScan(wollet);
|
|
211
|
+
if (update)
|
|
212
|
+
wollet.applyUpdate(update);
|
|
213
|
+
}
|
|
214
|
+
let builder = network.txBuilder();
|
|
215
|
+
if (input.feeRate !== undefined)
|
|
216
|
+
builder = builder.feeRate(input.feeRate);
|
|
217
|
+
const recipient = parseLwkAddress(lwk, requirements.payTo, network);
|
|
218
|
+
const amount = BigInt(requirements.maxAmountRequired);
|
|
219
|
+
if (requirements.extra.asset === "lbtc") {
|
|
220
|
+
builder = builder.addLbtcRecipient(recipient, amount);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
builder = builder.addRecipient(recipient, amount, parseLwkAssetId(lwk, requirements.asset));
|
|
224
|
+
}
|
|
225
|
+
const unsigned = builder.finish(wollet);
|
|
226
|
+
const signed = signer.sign(unsigned);
|
|
227
|
+
const pset = input.finalize === false ? signed : wollet.finalize(signed);
|
|
228
|
+
const decoded = decodeLwkPsetForX402(pset, wollet);
|
|
229
|
+
const payment = buildLiquidX402PaymentFromPset({
|
|
230
|
+
requirements,
|
|
231
|
+
psetBase64: pset.toString(),
|
|
232
|
+
decoded,
|
|
233
|
+
payer: input.payer,
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
...payment,
|
|
237
|
+
descriptor: descriptor.toString(),
|
|
238
|
+
...(typeof wollet.dwid === "function" ? { dwid: wollet.dwid() } : {}),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
async function deriveLiquidX402LwkWasmAddress(input) {
|
|
242
|
+
const networkKey = normalizeNetwork(input.network ?? exports.LIQUID_X402_DEFAULT_NETWORK);
|
|
243
|
+
ensureLwkWasmNodeTimerCompat();
|
|
244
|
+
const lwk = input.lwk ?? await loadLwkWasm(input.importLwk);
|
|
245
|
+
const network = networkKey === "liquidv1" ? lwk.Network.mainnet() : lwk.Network.testnet();
|
|
246
|
+
const mnemonic = new lwk.Mnemonic(input.mnemonic);
|
|
247
|
+
const signer = new lwk.Signer(mnemonic, network);
|
|
248
|
+
const descriptor = signer.wpkhSlip77Descriptor();
|
|
249
|
+
const wollet = new lwk.Wollet(network, descriptor);
|
|
250
|
+
const addressResult = wollet.address(input.index ?? null);
|
|
251
|
+
const address = addressResult.address();
|
|
252
|
+
return {
|
|
253
|
+
network: networkKey,
|
|
254
|
+
address: address.toString(),
|
|
255
|
+
...(typeof address.toUnconfidential === "function" ? { unconfidentialAddress: address.toUnconfidential().toString() } : {}),
|
|
256
|
+
index: typeof addressResult.index === "function" ? addressResult.index() : input.index ?? 0,
|
|
257
|
+
...(typeof address.isBlinded === "function" ? { isBlinded: address.isBlinded() } : {}),
|
|
258
|
+
descriptor: descriptor.toString(),
|
|
259
|
+
...(typeof wollet.dwid === "function" ? { dwid: wollet.dwid() } : {}),
|
|
260
|
+
...(typeof network.policyAsset === "function" ? { policyAsset: network.policyAsset().toString() } : {}),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function verifyLiquidX402Payment(rpc, input) {
|
|
264
|
+
const requirements = coerceRequirements(input.requirements);
|
|
265
|
+
const payload = coercePaymentPayload(input.paymentPayload);
|
|
266
|
+
const basic = verifyLiquidX402PayloadFields(requirements, payload, input.now);
|
|
267
|
+
if (!basic.isValid)
|
|
268
|
+
return basic;
|
|
269
|
+
if (!rpc) {
|
|
270
|
+
return basic;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
const decoded = await rpc.call("decodepsbt", [payload.psetBase64]);
|
|
274
|
+
const actualSummaryHash = buildLiquidPsetSummaryHash(decoded, requirements);
|
|
275
|
+
if (actualSummaryHash !== payload.summaryHash) {
|
|
276
|
+
return { ...basic, isValid: false, invalidReason: "pset_summary_mismatch" };
|
|
277
|
+
}
|
|
278
|
+
if (!decodedContainsExpectedOutput(decoded, requirements)) {
|
|
279
|
+
return { ...basic, isValid: false, invalidReason: "pset_expected_output_missing" };
|
|
280
|
+
}
|
|
281
|
+
if (!decodedFeeWithinCap(decoded, requirements)) {
|
|
282
|
+
return { ...basic, isValid: false, invalidReason: "fee_too_high" };
|
|
283
|
+
}
|
|
284
|
+
return basic;
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return { ...basic, isValid: false, invalidReason: "pset_decode_failed" };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function settleLiquidX402Payment(rpc, input) {
|
|
291
|
+
const verification = await verifyLiquidX402Payment(rpc, input);
|
|
292
|
+
if (!verification.isValid) {
|
|
293
|
+
return { success: false, errorCode: verification.invalidReason ?? "payment_verification_failed" };
|
|
294
|
+
}
|
|
295
|
+
const payload = coercePaymentPayload(input.paymentPayload);
|
|
296
|
+
try {
|
|
297
|
+
const finalized = await rpc.call("finalizepsbt", [payload.psetBase64, true], input.wallet);
|
|
298
|
+
if (!finalized.complete || !finalized.hex) {
|
|
299
|
+
return { success: false, errorCode: "pset_not_complete", summaryHash: payload.summaryHash };
|
|
300
|
+
}
|
|
301
|
+
const mempool = await rpc.call("testmempoolaccept", [[finalized.hex]], input.wallet);
|
|
302
|
+
if (mempool[0]?.allowed !== true) {
|
|
303
|
+
return {
|
|
304
|
+
success: false,
|
|
305
|
+
errorCode: mempool[0]?.reject_reason || "mempool_rejected",
|
|
306
|
+
rawTxHex: finalized.hex,
|
|
307
|
+
summaryHash: payload.summaryHash,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (input.broadcast === false) {
|
|
311
|
+
return { success: true, rawTxHex: finalized.hex, summaryHash: payload.summaryHash };
|
|
312
|
+
}
|
|
313
|
+
const txId = await rpc.call("sendrawtransaction", [finalized.hex], input.wallet);
|
|
314
|
+
return {
|
|
315
|
+
success: true,
|
|
316
|
+
txId,
|
|
317
|
+
transactionHash: txId,
|
|
318
|
+
rawTxHex: finalized.hex,
|
|
319
|
+
summaryHash: payload.summaryHash,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
return { success: false, errorCode: "settlement_rpc_failed", summaryHash: payload.summaryHash };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function verifyLiquidX402PayloadFields(requirements, payload, now = new Date()) {
|
|
327
|
+
const req = coerceRequirements(requirements);
|
|
328
|
+
const parsed = coercePaymentPayload(payload);
|
|
329
|
+
const base = {
|
|
330
|
+
payer: parsed.payer ?? null,
|
|
331
|
+
network: parsed.network,
|
|
332
|
+
paymentRequestId: parsed.paymentRequestId,
|
|
333
|
+
summaryHash: parsed.summaryHash,
|
|
334
|
+
};
|
|
335
|
+
if (parsed.scheme !== exports.LIQUID_X402_SCHEME)
|
|
336
|
+
return { ...base, isValid: false, invalidReason: "unsupported_scheme" };
|
|
337
|
+
if (parsed.network !== req.network)
|
|
338
|
+
return { ...base, isValid: false, invalidReason: "network_mismatch" };
|
|
339
|
+
if (parsed.paymentRequestId !== req.extra.paymentRequestId) {
|
|
340
|
+
return { ...base, isValid: false, invalidReason: "payment_request_mismatch" };
|
|
341
|
+
}
|
|
342
|
+
if (parsed.asset !== req.extra.asset || parsed.assetId !== req.asset) {
|
|
343
|
+
return { ...base, isValid: false, invalidReason: "asset_mismatch" };
|
|
344
|
+
}
|
|
345
|
+
if (parsed.amountAtomic !== req.maxAmountRequired) {
|
|
346
|
+
return { ...base, isValid: false, invalidReason: "amount_mismatch" };
|
|
347
|
+
}
|
|
348
|
+
if (parsed.payTo !== req.payTo)
|
|
349
|
+
return { ...base, isValid: false, invalidReason: "recipient_mismatch" };
|
|
350
|
+
if (!parsed.psetBase64)
|
|
351
|
+
return { ...base, isValid: false, invalidReason: "pset_missing" };
|
|
352
|
+
if (!parsed.summaryHash)
|
|
353
|
+
return { ...base, isValid: false, invalidReason: "summary_hash_missing" };
|
|
354
|
+
if (new Date(parsed.expiresAt).getTime() <= now.getTime()) {
|
|
355
|
+
return { ...base, isValid: false, invalidReason: "payment_expired" };
|
|
356
|
+
}
|
|
357
|
+
return { ...base, isValid: true };
|
|
358
|
+
}
|
|
359
|
+
function buildLiquidPsetSummaryHash(_decoded, requirements) {
|
|
360
|
+
const req = coerceRequirements(requirements);
|
|
361
|
+
return sha256Hex(stableStringify({
|
|
362
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
363
|
+
network: req.network,
|
|
364
|
+
paymentRequestId: req.extra.paymentRequestId,
|
|
365
|
+
asset: req.extra.asset,
|
|
366
|
+
assetId: req.asset,
|
|
367
|
+
amountAtomic: req.maxAmountRequired,
|
|
368
|
+
payTo: req.payTo,
|
|
369
|
+
expectedOutput: {
|
|
370
|
+
amountAtomic: req.maxAmountRequired,
|
|
371
|
+
assetId: req.asset,
|
|
372
|
+
payTo: req.payTo,
|
|
373
|
+
},
|
|
374
|
+
feeAsset: req.extra.feeAsset,
|
|
375
|
+
feeAssetId: req.extra.feeAssetId ?? exports.LIQUID_X402_ASSETS[req.network].lbtc.assetId,
|
|
376
|
+
maxFeeSat: req.extra.maxFeeSat ?? null,
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
function decodedContainsExpectedOutput(decoded, requirements) {
|
|
380
|
+
const expectedAmount = atomicToDecimalNumber(requirements.maxAmountRequired, requirements.extra.decimals);
|
|
381
|
+
return (decoded.outputs ?? []).some((output) => (String(output.asset ?? "").toLowerCase() === requirements.asset.toLowerCase() &&
|
|
382
|
+
Math.round(Number(output.amount ?? 0) * 10 ** requirements.extra.decimals) ===
|
|
383
|
+
Math.round(expectedAmount * 10 ** requirements.extra.decimals) &&
|
|
384
|
+
String(output.script?.address ?? "") === requirements.payTo));
|
|
385
|
+
}
|
|
386
|
+
function coerceRequirements(value) {
|
|
387
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
388
|
+
throw new Error("requirements must be an object");
|
|
389
|
+
const raw = value;
|
|
390
|
+
const extra = raw.extra && typeof raw.extra === "object" && !Array.isArray(raw.extra) ? raw.extra : {};
|
|
391
|
+
const network = normalizeNetwork(raw.network);
|
|
392
|
+
const asset = resolveLiquidX402Asset(extra.asset ?? raw.asset, network);
|
|
393
|
+
const rawAssetId = String(raw.asset ?? extra.assetId ?? asset.assetId);
|
|
394
|
+
const assetId = isLiquidAssetAlias(rawAssetId) ? asset.assetId : rawAssetId;
|
|
395
|
+
return {
|
|
396
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
397
|
+
network,
|
|
398
|
+
maxAmountRequired: normalizeAmountAtomic(raw.maxAmountRequired),
|
|
399
|
+
resource: String(raw.resource ?? ""),
|
|
400
|
+
description: String(raw.description ?? ""),
|
|
401
|
+
mimeType: String(raw.mimeType ?? ""),
|
|
402
|
+
payTo: String(raw.payTo ?? ""),
|
|
403
|
+
maxTimeoutSeconds: normalizeTimeout(raw.maxTimeoutSeconds),
|
|
404
|
+
asset: assetId,
|
|
405
|
+
extra: {
|
|
406
|
+
paymentRequestId: String(extra.paymentRequestId ?? raw.paymentRequestId ?? ""),
|
|
407
|
+
asset: asset.key,
|
|
408
|
+
assetId,
|
|
409
|
+
decimals: Number(extra.decimals ?? asset.decimals),
|
|
410
|
+
expiresAt: String(extra.expiresAt ?? ""),
|
|
411
|
+
feeAsset: "lbtc",
|
|
412
|
+
feeAssetId: String(extra.feeAssetId ?? exports.LIQUID_X402_ASSETS[network].lbtc.assetId),
|
|
413
|
+
...(extra.maxFeeSat !== undefined ? { maxFeeSat: normalizeAmountAtomic(extra.maxFeeSat) } : {}),
|
|
414
|
+
...(extra.metadata ? { metadata: extra.metadata } : {}),
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function decodedFeeWithinCap(decoded, requirements) {
|
|
419
|
+
if (requirements.extra.maxFeeSat === undefined)
|
|
420
|
+
return true;
|
|
421
|
+
const maxFeeSat = BigInt(requirements.extra.maxFeeSat);
|
|
422
|
+
const feeSat = Object.values(decoded.fees ?? {}).reduce((sum, amount) => {
|
|
423
|
+
const sat = Math.round(Number(amount) * 100_000_000);
|
|
424
|
+
return sum + BigInt(Number.isFinite(sat) ? sat : 0);
|
|
425
|
+
}, 0n);
|
|
426
|
+
return feeSat <= maxFeeSat;
|
|
427
|
+
}
|
|
428
|
+
async function loadLwkWasm(importer) {
|
|
429
|
+
ensureLwkWasmNodeTimerCompat();
|
|
430
|
+
if (importer)
|
|
431
|
+
return importer();
|
|
432
|
+
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
433
|
+
const failures = [];
|
|
434
|
+
for (const specifier of ["lwk_wasm", "lwk_node"]) {
|
|
435
|
+
try {
|
|
436
|
+
const imported = await dynamicImport(specifier);
|
|
437
|
+
return imported.default ?? imported;
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
failures.push(`${specifier}: ${error instanceof Error ? error.message : String(error)}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
throw new Error("lwk_wasm or lwk_node is required for lightweight Liquid x402 payments. " +
|
|
444
|
+
"Install one of them in the consuming app and bundle its WASM artifact when needed. " +
|
|
445
|
+
`Import failed: ${failures.join("; ")}`);
|
|
446
|
+
}
|
|
447
|
+
function ensureLwkWasmNodeTimerCompat() {
|
|
448
|
+
const scope = globalThis;
|
|
449
|
+
if (typeof scope.window === "undefined" && typeof scope.setTimeout === "function") {
|
|
450
|
+
scope.window = scope;
|
|
451
|
+
}
|
|
452
|
+
if (typeof scope.Window === "undefined" && typeof scope.window !== "undefined") {
|
|
453
|
+
scope.Window = Object;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function parseLwkAddress(lwk, address, network) {
|
|
457
|
+
return typeof lwk.Address.parse === "function" ? lwk.Address.parse(address, network) : new lwk.Address(address);
|
|
458
|
+
}
|
|
459
|
+
function parseLwkAssetId(lwk, assetId) {
|
|
460
|
+
return typeof lwk.AssetId.fromString === "function" ? lwk.AssetId.fromString(assetId) : new lwk.AssetId(assetId);
|
|
461
|
+
}
|
|
462
|
+
function decodeLwkPsetForX402(pset, wollet) {
|
|
463
|
+
const details = wollet.psetDetails(pset);
|
|
464
|
+
const balance = details.balance();
|
|
465
|
+
const recipients = typeof balance.recipients === "function" ? balance.recipients() : [];
|
|
466
|
+
const fee = typeof balance.fee === "function" ? balance.fee() : 0n;
|
|
467
|
+
return {
|
|
468
|
+
fees: { bitcoin: atomicToDecimalNumber(String(fee), 8) },
|
|
469
|
+
outputs: recipients.map((recipient) => ({
|
|
470
|
+
amount: recipient.value() === undefined ? undefined : atomicToDecimalNumber(String(recipient.value()), 8),
|
|
471
|
+
asset: recipient.asset()?.toString(),
|
|
472
|
+
script: {
|
|
473
|
+
address: recipient.address()?.toString(),
|
|
474
|
+
},
|
|
475
|
+
})),
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function coercePaymentPayload(value) {
|
|
479
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
480
|
+
throw new Error("payment payload must be an object");
|
|
481
|
+
const raw = value;
|
|
482
|
+
return {
|
|
483
|
+
scheme: exports.LIQUID_X402_SCHEME,
|
|
484
|
+
network: normalizeNetwork(raw.network),
|
|
485
|
+
paymentRequestId: String(raw.paymentRequestId ?? ""),
|
|
486
|
+
asset: normalizeAssetKey(String(raw.asset ?? "")),
|
|
487
|
+
assetId: String(raw.assetId ?? ""),
|
|
488
|
+
amountAtomic: normalizeAmountAtomic(raw.amountAtomic),
|
|
489
|
+
payTo: String(raw.payTo ?? ""),
|
|
490
|
+
psetBase64: String(raw.psetBase64 ?? ""),
|
|
491
|
+
summaryHash: String(raw.summaryHash ?? ""),
|
|
492
|
+
...(raw.payer ? { payer: String(raw.payer) } : {}),
|
|
493
|
+
expiresAt: String(raw.expiresAt ?? ""),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
function normalizeNetwork(input) {
|
|
497
|
+
const value = String(input ?? "").trim().toLowerCase();
|
|
498
|
+
if (value === "liquidtestnet" || value === "liquidv1")
|
|
499
|
+
return value;
|
|
500
|
+
throw new Error(`unsupported Liquid x402 network: ${String(input)}`);
|
|
501
|
+
}
|
|
502
|
+
function normalizeAssetKey(input) {
|
|
503
|
+
const value = String(input ?? "").trim().toLowerCase();
|
|
504
|
+
if (value === "lbtc" || value === "bitcoin" || value === LIQUID_TESTNET_LBTC_ASSET_ID || value === LIQUID_MAINNET_LBTC_ASSET_ID) {
|
|
505
|
+
return "lbtc";
|
|
506
|
+
}
|
|
507
|
+
if (value === "usdt" ||
|
|
508
|
+
value === "usdt-liquid" ||
|
|
509
|
+
value === "tether" ||
|
|
510
|
+
value === LIQUID_TESTNET_USDT_ASSET_ID ||
|
|
511
|
+
value === LIQUID_MAINNET_USDT_ASSET_ID) {
|
|
512
|
+
return "usdt";
|
|
513
|
+
}
|
|
514
|
+
throw new Error(`unsupported Liquid x402 asset: ${String(input)}`);
|
|
515
|
+
}
|
|
516
|
+
function isLiquidAssetAlias(input) {
|
|
517
|
+
const value = String(input ?? "").trim().toLowerCase();
|
|
518
|
+
return value === "lbtc" ||
|
|
519
|
+
value === "bitcoin" ||
|
|
520
|
+
value === "usdt" ||
|
|
521
|
+
value === "usdt-liquid" ||
|
|
522
|
+
value === "tether";
|
|
523
|
+
}
|
|
524
|
+
function normalizeAmountAtomic(input) {
|
|
525
|
+
const raw = typeof input === "bigint" ? input.toString() : String(input ?? "").trim();
|
|
526
|
+
if (!/^\d+$/u.test(raw))
|
|
527
|
+
throw new Error("amountAtomic must be an integer string");
|
|
528
|
+
return BigInt(raw).toString();
|
|
529
|
+
}
|
|
530
|
+
function normalizeTimeout(input) {
|
|
531
|
+
const parsed = Number(input ?? exports.LIQUID_X402_DEFAULT_TIMEOUT_SECONDS);
|
|
532
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : exports.LIQUID_X402_DEFAULT_TIMEOUT_SECONDS;
|
|
533
|
+
}
|
|
534
|
+
function normalizeExpiresAt(expiresAt, timeoutSeconds) {
|
|
535
|
+
if (expiresAt instanceof Date)
|
|
536
|
+
return expiresAt.toISOString();
|
|
537
|
+
const raw = String(expiresAt ?? "").trim();
|
|
538
|
+
if (raw)
|
|
539
|
+
return new Date(raw).toISOString();
|
|
540
|
+
return new Date(Date.now() + normalizeTimeout(timeoutSeconds) * 1000).toISOString();
|
|
541
|
+
}
|
|
542
|
+
function atomicToDecimalNumber(amountAtomic, decimals) {
|
|
543
|
+
const amount = BigInt(amountAtomic);
|
|
544
|
+
const scale = 10n ** BigInt(decimals);
|
|
545
|
+
const whole = amount / scale;
|
|
546
|
+
const frac = amount % scale;
|
|
547
|
+
return Number(`${whole}.${frac.toString().padStart(decimals, "0")}`);
|
|
548
|
+
}
|
|
549
|
+
function sha256Hex(value) {
|
|
550
|
+
return (0, node_crypto_1.createHash)("sha256").update(value, "utf8").digest("hex");
|
|
551
|
+
}
|
|
552
|
+
function stableStringify(value) {
|
|
553
|
+
if (value === null || typeof value !== "object")
|
|
554
|
+
return JSON.stringify(value);
|
|
555
|
+
if (Array.isArray(value))
|
|
556
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
557
|
+
const record = value;
|
|
558
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
|
|
559
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazbase/simplicity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "An SDK for Simplicity on Liquid",
|
|
5
5
|
"author": "IndieSquare Inc <info@hazbase.com>",
|
|
6
6
|
"keywords": [
|
|
@@ -50,6 +50,8 @@
|
|
|
50
50
|
"e2e:receivable-local": "npm run build && node scripts/e2e-receivable-local.mjs",
|
|
51
51
|
"e2e:receivable-testnet": "npm run build && node scripts/e2e-receivable-testnet.mjs",
|
|
52
52
|
"e2e:receivable-consumer": "npm run build && node scripts/e2e-receivable-consumer.mjs",
|
|
53
|
+
"e2e:x402-lwk-address": "npm run build && node scripts/e2e-x402-lwk-address.mjs",
|
|
54
|
+
"e2e:x402-lwk-buyer": "npm run build && node scripts/e2e-x402-lwk-buyer.mjs",
|
|
53
55
|
"e2e:simplicity-relayer": "npm run build && node scripts/e2e-simplicity-relayer.mjs",
|
|
54
56
|
"prepublishOnly": "npm run build"
|
|
55
57
|
},
|