@hazbase/simplicity 0.3.0 → 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 +3 -0
- package/dist/client/SimplicityClient.js +3 -0
- package/dist/x402/index.d.ts +66 -3
- package/dist/x402/index.js +180 -17
- 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
|
|
@@ -22,6 +22,9 @@ export declare class SimplicityClient {
|
|
|
22
22
|
encodePayment: typeof liquidX402.encodeLiquidXPayment;
|
|
23
23
|
decodePayment: typeof liquidX402.decodeLiquidXPayment;
|
|
24
24
|
preparePsetPayment: (input: liquidX402.LiquidX402PreparePsetPaymentInput) => Promise<liquidX402.LiquidX402PreparePsetPaymentResult>;
|
|
25
|
+
buildPaymentFromPset: typeof liquidX402.buildLiquidX402PaymentFromPset;
|
|
26
|
+
prepareLwkWasmPayment: typeof liquidX402.prepareLiquidX402LwkWasmPayment;
|
|
27
|
+
deriveLwkWasmAddress: typeof liquidX402.deriveLiquidX402LwkWasmAddress;
|
|
25
28
|
verify: (input: liquidX402.LiquidX402VerifyPaymentInput) => Promise<liquidX402.LiquidX402VerifyPaymentResult>;
|
|
26
29
|
settle: (input: liquidX402.LiquidX402SettlePaymentInput) => Promise<liquidX402.LiquidX402SettlePaymentResult>;
|
|
27
30
|
};
|
|
@@ -71,6 +71,9 @@ class SimplicityClient {
|
|
|
71
71
|
encodePayment: liquidX402.encodeLiquidXPayment,
|
|
72
72
|
decodePayment: liquidX402.decodeLiquidXPayment,
|
|
73
73
|
preparePsetPayment: async (input) => liquidX402.prepareLiquidX402PsetPayment(this.rpc, input),
|
|
74
|
+
buildPaymentFromPset: liquidX402.buildLiquidX402PaymentFromPset,
|
|
75
|
+
prepareLwkWasmPayment: liquidX402.prepareLiquidX402LwkWasmPayment,
|
|
76
|
+
deriveLwkWasmAddress: liquidX402.deriveLiquidX402LwkWasmAddress,
|
|
74
77
|
verify: async (input) => liquidX402.verifyLiquidX402Payment(this.rpc, input),
|
|
75
78
|
settle: async (input) => liquidX402.settleLiquidX402Payment(this.rpc, input),
|
|
76
79
|
},
|
package/dist/x402/index.d.ts
CHANGED
|
@@ -75,6 +75,49 @@ export interface LiquidX402PreparePsetPaymentResult {
|
|
|
75
75
|
psetBase64: string;
|
|
76
76
|
summaryHash: string;
|
|
77
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
|
+
}
|
|
78
121
|
export interface LiquidX402VerifyPaymentInput {
|
|
79
122
|
requirements: LiquidX402PaymentRequirements | Record<string, unknown>;
|
|
80
123
|
paymentPayload: LiquidX402PaymentPayload | Record<string, unknown>;
|
|
@@ -100,7 +143,7 @@ export interface LiquidX402SettlePaymentResult {
|
|
|
100
143
|
rawTxHex?: string;
|
|
101
144
|
summaryHash?: string;
|
|
102
145
|
}
|
|
103
|
-
type DecodePsbtResult = {
|
|
146
|
+
export type DecodePsbtResult = {
|
|
104
147
|
fees?: Record<string, number>;
|
|
105
148
|
outputs?: Array<{
|
|
106
149
|
amount?: number;
|
|
@@ -111,6 +154,24 @@ type DecodePsbtResult = {
|
|
|
111
154
|
};
|
|
112
155
|
}>;
|
|
113
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
|
+
};
|
|
114
175
|
export declare const LIQUID_X402_ASSETS: Record<LiquidX402Network, Record<LiquidX402AssetKey, LiquidX402Asset>>;
|
|
115
176
|
export declare function listLiquidX402Assets(network?: LiquidX402Network): LiquidX402Asset[];
|
|
116
177
|
export declare function resolveLiquidX402Asset(asset: LiquidX402AssetKey | string, network?: LiquidX402Network): LiquidX402Asset;
|
|
@@ -118,8 +179,10 @@ export declare function buildLiquidX402Requirements(input: LiquidX402Requirement
|
|
|
118
179
|
export declare function encodeLiquidXPayment(payload: LiquidX402PaymentPayload): string;
|
|
119
180
|
export declare function decodeLiquidXPayment(raw: string): LiquidX402PaymentPayload | null;
|
|
120
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>;
|
|
121
185
|
export declare function verifyLiquidX402Payment(rpc: ElementsRpcClient | null, input: LiquidX402VerifyPaymentInput): Promise<LiquidX402VerifyPaymentResult>;
|
|
122
186
|
export declare function settleLiquidX402Payment(rpc: ElementsRpcClient, input: LiquidX402SettlePaymentInput): Promise<LiquidX402SettlePaymentResult>;
|
|
123
187
|
export declare function verifyLiquidX402PayloadFields(requirements: LiquidX402PaymentRequirements | Record<string, unknown>, payload: LiquidX402PaymentPayload | Record<string, unknown>, now?: Date): LiquidX402VerifyPaymentResult;
|
|
124
|
-
export declare function buildLiquidPsetSummaryHash(
|
|
125
|
-
export {};
|
|
188
|
+
export declare function buildLiquidPsetSummaryHash(_decoded: DecodePsbtResult, requirements: LiquidX402PaymentRequirements | Record<string, unknown>): string;
|
package/dist/x402/index.js
CHANGED
|
@@ -7,6 +7,9 @@ exports.buildLiquidX402Requirements = buildLiquidX402Requirements;
|
|
|
7
7
|
exports.encodeLiquidXPayment = encodeLiquidXPayment;
|
|
8
8
|
exports.decodeLiquidXPayment = decodeLiquidXPayment;
|
|
9
9
|
exports.prepareLiquidX402PsetPayment = prepareLiquidX402PsetPayment;
|
|
10
|
+
exports.buildLiquidX402PaymentFromPset = buildLiquidX402PaymentFromPset;
|
|
11
|
+
exports.prepareLiquidX402LwkWasmPayment = prepareLiquidX402LwkWasmPayment;
|
|
12
|
+
exports.deriveLiquidX402LwkWasmAddress = deriveLiquidX402LwkWasmAddress;
|
|
10
13
|
exports.verifyLiquidX402Payment = verifyLiquidX402Payment;
|
|
11
14
|
exports.settleLiquidX402Payment = settleLiquidX402Payment;
|
|
12
15
|
exports.verifyLiquidX402PayloadFields = verifyLiquidX402PayloadFields;
|
|
@@ -18,13 +21,15 @@ exports.LIQUID_X402_DEFAULT_NETWORK = "liquidtestnet";
|
|
|
18
21
|
exports.LIQUID_X402_DEFAULT_TIMEOUT_SECONDS = 60;
|
|
19
22
|
const LIQUID_TESTNET_USDT_ASSET_ID = "b612eb46313a2cd6ebabd8b7a8eed5696e29898b87a43bff41c94f51acef9d73";
|
|
20
23
|
const LIQUID_MAINNET_USDT_ASSET_ID = "ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2";
|
|
24
|
+
const LIQUID_TESTNET_LBTC_ASSET_ID = "144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49";
|
|
25
|
+
const LIQUID_MAINNET_LBTC_ASSET_ID = "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d";
|
|
21
26
|
exports.LIQUID_X402_ASSETS = {
|
|
22
27
|
liquidtestnet: {
|
|
23
28
|
lbtc: {
|
|
24
29
|
key: "lbtc",
|
|
25
30
|
label: "Liquid Bitcoin",
|
|
26
31
|
network: "liquidtestnet",
|
|
27
|
-
assetId:
|
|
32
|
+
assetId: LIQUID_TESTNET_LBTC_ASSET_ID,
|
|
28
33
|
decimals: 8,
|
|
29
34
|
isNative: true,
|
|
30
35
|
},
|
|
@@ -42,7 +47,7 @@ exports.LIQUID_X402_ASSETS = {
|
|
|
42
47
|
key: "lbtc",
|
|
43
48
|
label: "Liquid Bitcoin",
|
|
44
49
|
network: "liquidv1",
|
|
45
|
-
assetId:
|
|
50
|
+
assetId: LIQUID_MAINNET_LBTC_ASSET_ID,
|
|
46
51
|
decimals: 8,
|
|
47
52
|
isNative: true,
|
|
48
53
|
},
|
|
@@ -161,6 +166,100 @@ async function prepareLiquidX402PsetPayment(rpc, input) {
|
|
|
161
166
|
summaryHash,
|
|
162
167
|
};
|
|
163
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
|
+
}
|
|
164
263
|
async function verifyLiquidX402Payment(rpc, input) {
|
|
165
264
|
const requirements = coerceRequirements(input.requirements);
|
|
166
265
|
const payload = coercePaymentPayload(input.paymentPayload);
|
|
@@ -257,7 +356,7 @@ function verifyLiquidX402PayloadFields(requirements, payload, now = new Date())
|
|
|
257
356
|
}
|
|
258
357
|
return { ...base, isValid: true };
|
|
259
358
|
}
|
|
260
|
-
function buildLiquidPsetSummaryHash(
|
|
359
|
+
function buildLiquidPsetSummaryHash(_decoded, requirements) {
|
|
261
360
|
const req = coerceRequirements(requirements);
|
|
262
361
|
return sha256Hex(stableStringify({
|
|
263
362
|
scheme: exports.LIQUID_X402_SCHEME,
|
|
@@ -267,8 +366,14 @@ function buildLiquidPsetSummaryHash(decoded, requirements) {
|
|
|
267
366
|
assetId: req.asset,
|
|
268
367
|
amountAtomic: req.maxAmountRequired,
|
|
269
368
|
payTo: req.payTo,
|
|
270
|
-
|
|
271
|
-
|
|
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,
|
|
272
377
|
}));
|
|
273
378
|
}
|
|
274
379
|
function decodedContainsExpectedOutput(decoded, requirements) {
|
|
@@ -278,14 +383,6 @@ function decodedContainsExpectedOutput(decoded, requirements) {
|
|
|
278
383
|
Math.round(expectedAmount * 10 ** requirements.extra.decimals) &&
|
|
279
384
|
String(output.script?.address ?? "") === requirements.payTo));
|
|
280
385
|
}
|
|
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
386
|
function coerceRequirements(value) {
|
|
290
387
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
291
388
|
throw new Error("requirements must be an object");
|
|
@@ -293,6 +390,8 @@ function coerceRequirements(value) {
|
|
|
293
390
|
const extra = raw.extra && typeof raw.extra === "object" && !Array.isArray(raw.extra) ? raw.extra : {};
|
|
294
391
|
const network = normalizeNetwork(raw.network);
|
|
295
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;
|
|
296
395
|
return {
|
|
297
396
|
scheme: exports.LIQUID_X402_SCHEME,
|
|
298
397
|
network,
|
|
@@ -302,11 +401,11 @@ function coerceRequirements(value) {
|
|
|
302
401
|
mimeType: String(raw.mimeType ?? ""),
|
|
303
402
|
payTo: String(raw.payTo ?? ""),
|
|
304
403
|
maxTimeoutSeconds: normalizeTimeout(raw.maxTimeoutSeconds),
|
|
305
|
-
asset:
|
|
404
|
+
asset: assetId,
|
|
306
405
|
extra: {
|
|
307
406
|
paymentRequestId: String(extra.paymentRequestId ?? raw.paymentRequestId ?? ""),
|
|
308
407
|
asset: asset.key,
|
|
309
|
-
assetId
|
|
408
|
+
assetId,
|
|
310
409
|
decimals: Number(extra.decimals ?? asset.decimals),
|
|
311
410
|
expiresAt: String(extra.expiresAt ?? ""),
|
|
312
411
|
feeAsset: "lbtc",
|
|
@@ -326,6 +425,56 @@ function decodedFeeWithinCap(decoded, requirements) {
|
|
|
326
425
|
}, 0n);
|
|
327
426
|
return feeSat <= maxFeeSat;
|
|
328
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
|
+
}
|
|
329
478
|
function coercePaymentPayload(value) {
|
|
330
479
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
331
480
|
throw new Error("payment payload must be an object");
|
|
@@ -352,12 +501,26 @@ function normalizeNetwork(input) {
|
|
|
352
501
|
}
|
|
353
502
|
function normalizeAssetKey(input) {
|
|
354
503
|
const value = String(input ?? "").trim().toLowerCase();
|
|
355
|
-
if (value === "lbtc" || value === "bitcoin")
|
|
504
|
+
if (value === "lbtc" || value === "bitcoin" || value === LIQUID_TESTNET_LBTC_ASSET_ID || value === LIQUID_MAINNET_LBTC_ASSET_ID) {
|
|
356
505
|
return "lbtc";
|
|
357
|
-
|
|
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) {
|
|
358
512
|
return "usdt";
|
|
513
|
+
}
|
|
359
514
|
throw new Error(`unsupported Liquid x402 asset: ${String(input)}`);
|
|
360
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
|
+
}
|
|
361
524
|
function normalizeAmountAtomic(input) {
|
|
362
525
|
const raw = typeof input === "bigint" ? input.toString() : String(input ?? "").trim();
|
|
363
526
|
if (!/^\d+$/u.test(raw))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazbase/simplicity",
|
|
3
|
-
"version": "0.3.
|
|
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
|
},
|