@hazbase/simplicity 0.4.0 → 0.4.2
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 +40 -0
- package/dist/client/SimplicityClient.d.ts +5 -0
- package/dist/client/SimplicityClient.js +5 -0
- package/dist/core/executor.d.ts +3 -1
- package/dist/core/executor.js +307 -8
- package/dist/core/rpc.js +3 -1
- package/dist/core/toolchain.d.ts +24 -0
- package/dist/core/toolchain.js +106 -2
- package/dist/core/types.d.ts +44 -0
- package/dist/docs/definitions/rwa-dvp-escrow.simf +128 -0
- package/dist/domain/rwaDvp.d.ts +74 -1
- package/dist/domain/rwaDvp.js +427 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -2
- package/dist/x402/index.d.ts +1 -0
- package/dist/x402/index.js +9 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,6 +152,46 @@ Main entrypoints:
|
|
|
152
152
|
Representative example:
|
|
153
153
|
- [show-receivable-business-flow.ts](./examples/show-receivable-business-flow.ts)
|
|
154
154
|
|
|
155
|
+
### RWA DvP and Liquid x402
|
|
156
|
+
|
|
157
|
+
Use `sdk.rwaDvp` when a purchase flow needs a Liquid PSET payment request,
|
|
158
|
+
delivery/refund claim descriptors, and an evidence bundle that ties the Liquid
|
|
159
|
+
payment and delivery to an external lock or allocation record.
|
|
160
|
+
|
|
161
|
+
For standard Liquid assets, `payment.asset` can be `"lbtc"` or `"usdt"`. If a
|
|
162
|
+
testnet issuer or deployment uses a different USDt asset id, set
|
|
163
|
+
`payment.asset: "usdt"` and pass the explicit `payment.assetId`; the generated
|
|
164
|
+
payment requirements will preserve that asset id instead of replacing it with
|
|
165
|
+
the SDK's default registry id.
|
|
166
|
+
|
|
167
|
+
Typical entrypoints:
|
|
168
|
+
- `sdk.rwaDvp.definePurchase(...)`
|
|
169
|
+
- `sdk.rwaDvp.buildPaymentRequirements(...)`
|
|
170
|
+
- `sdk.rwaDvp.verifyPaymentPset(...)`
|
|
171
|
+
- `sdk.rwaDvp.compileEscrowContract(...)`
|
|
172
|
+
- `sdk.rwaDvp.prepareDeliveryClaim(...)`
|
|
173
|
+
- `sdk.rwaDvp.inspectDeliveryClaim(...)`
|
|
174
|
+
- `sdk.rwaDvp.executeDeliveryClaim(...)`
|
|
175
|
+
- `sdk.rwaDvp.prepareRefundClaim(...)`
|
|
176
|
+
- `sdk.rwaDvp.inspectRefundClaim(...)`
|
|
177
|
+
- `sdk.rwaDvp.executeRefundClaim(...)`
|
|
178
|
+
- `sdk.rwaDvp.exportEvidence(...)`
|
|
179
|
+
|
|
180
|
+
For a delivery claim, the operator spends the funded escrow output and sends
|
|
181
|
+
the payment asset to the treasury while delivering the RWA asset to the buyer.
|
|
182
|
+
For a refund claim, the operator spends the same escrow output after the
|
|
183
|
+
configured timeout and returns the payment asset to the buyer. The inspect
|
|
184
|
+
methods build and validate the candidate spend without broadcasting it; the
|
|
185
|
+
execute methods finalize the Simplicity input, optionally test mempool
|
|
186
|
+
acceptance, and broadcast when `broadcast: true`.
|
|
187
|
+
|
|
188
|
+
The claim execution helpers assume the Elements wallet can provide any extra
|
|
189
|
+
inputs needed for RWA delivery and L-BTC fees. You can also pass explicit
|
|
190
|
+
`extraInputs` when the service wants deterministic coin selection. Script-bound
|
|
191
|
+
delivery/refund output checks are the practical default; descriptor-bound checks
|
|
192
|
+
should be used only when the caller can provide the exact output data required
|
|
193
|
+
by the descriptor.
|
|
194
|
+
|
|
155
195
|
## CLI and Confidence Commands
|
|
156
196
|
|
|
157
197
|
### Validation Surface
|
|
@@ -38,10 +38,15 @@ export declare class SimplicityClient {
|
|
|
38
38
|
definePurchase: (input: Parameters<typeof rwaDvp.definePurchase>[1]) => ReturnType<typeof rwaDvp.definePurchase>;
|
|
39
39
|
buildPaymentRequirements: (input: Parameters<typeof rwaDvp.buildPaymentRequirements>[1]) => ReturnType<typeof rwaDvp.buildPaymentRequirements>;
|
|
40
40
|
verifyPaymentPset: (input: Parameters<typeof rwaDvp.verifyPaymentPset>[1]) => ReturnType<typeof rwaDvp.verifyPaymentPset>;
|
|
41
|
+
compileEscrowContract: (input: Parameters<typeof rwaDvp.compileEscrowContract>[1]) => ReturnType<typeof rwaDvp.compileEscrowContract>;
|
|
41
42
|
prepareDeliveryClaim: (input: Parameters<typeof rwaDvp.prepareDeliveryClaim>[1]) => ReturnType<typeof rwaDvp.prepareDeliveryClaim>;
|
|
42
43
|
verifyDeliveryClaim: (input: Parameters<typeof rwaDvp.verifyDeliveryClaim>[1]) => ReturnType<typeof rwaDvp.verifyDeliveryClaim>;
|
|
44
|
+
inspectDeliveryClaim: (input: Parameters<typeof rwaDvp.inspectDeliveryClaim>[1]) => ReturnType<typeof rwaDvp.inspectDeliveryClaim>;
|
|
45
|
+
executeDeliveryClaim: (input: Parameters<typeof rwaDvp.executeDeliveryClaim>[1]) => ReturnType<typeof rwaDvp.executeDeliveryClaim>;
|
|
43
46
|
prepareRefundClaim: (input: Parameters<typeof rwaDvp.prepareRefundClaim>[1]) => ReturnType<typeof rwaDvp.prepareRefundClaim>;
|
|
44
47
|
verifyRefundClaim: (input: Parameters<typeof rwaDvp.verifyRefundClaim>[1]) => ReturnType<typeof rwaDvp.verifyRefundClaim>;
|
|
48
|
+
inspectRefundClaim: (input: Parameters<typeof rwaDvp.inspectRefundClaim>[1]) => ReturnType<typeof rwaDvp.inspectRefundClaim>;
|
|
49
|
+
executeRefundClaim: (input: Parameters<typeof rwaDvp.executeRefundClaim>[1]) => ReturnType<typeof rwaDvp.executeRefundClaim>;
|
|
45
50
|
exportEvidence: (input: Parameters<typeof rwaDvp.exportEvidence>[1]) => ReturnType<typeof rwaDvp.exportEvidence>;
|
|
46
51
|
};
|
|
47
52
|
readonly policies: {
|
|
@@ -88,10 +88,15 @@ class SimplicityClient {
|
|
|
88
88
|
definePurchase: (input) => rwaDvp.definePurchase(this, input),
|
|
89
89
|
buildPaymentRequirements: (input) => rwaDvp.buildPaymentRequirements(this, input),
|
|
90
90
|
verifyPaymentPset: (input) => rwaDvp.verifyPaymentPset(this, input),
|
|
91
|
+
compileEscrowContract: (input) => rwaDvp.compileEscrowContract(this, input),
|
|
91
92
|
prepareDeliveryClaim: (input) => rwaDvp.prepareDeliveryClaim(this, input),
|
|
92
93
|
verifyDeliveryClaim: (input) => rwaDvp.verifyDeliveryClaim(this, input),
|
|
94
|
+
inspectDeliveryClaim: (input) => rwaDvp.inspectDeliveryClaim(this, input),
|
|
95
|
+
executeDeliveryClaim: (input) => rwaDvp.executeDeliveryClaim(this, input),
|
|
93
96
|
prepareRefundClaim: (input) => rwaDvp.prepareRefundClaim(this, input),
|
|
94
97
|
verifyRefundClaim: (input) => rwaDvp.verifyRefundClaim(this, input),
|
|
98
|
+
inspectRefundClaim: (input) => rwaDvp.inspectRefundClaim(this, input),
|
|
99
|
+
executeRefundClaim: (input) => rwaDvp.executeRefundClaim(this, input),
|
|
95
100
|
exportEvidence: (input) => rwaDvp.exportEvidence(this, input),
|
|
96
101
|
};
|
|
97
102
|
this.policies = {
|
package/dist/core/executor.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { ContractUtxo, ExecuteCallInput, ExecuteResult, GaslessExecuteInput, GaslessExecuteResult, InspectCallInput, InspectResult, SimplicityArtifact, SimplicityClientConfig } from "./types";
|
|
1
|
+
import { ContractUtxo, ExecuteCallInput, ExecuteResult, GaslessExecuteInput, GaslessExecuteResult, InspectCallInput, InspectResult, MultiAssetContractCallInput, MultiAssetExecuteResult, MultiAssetInspectResult, SimplicityArtifact, SimplicityClientConfig } from "./types";
|
|
2
2
|
export declare function inspectContractCall(config: SimplicityClientConfig, artifact: SimplicityArtifact, input: InspectCallInput): Promise<InspectResult>;
|
|
3
3
|
export declare function executeContractCall(config: SimplicityClientConfig, artifact: SimplicityArtifact, input: ExecuteCallInput): Promise<ExecuteResult>;
|
|
4
4
|
export declare function findContractUtxos(config: SimplicityClientConfig, artifact: SimplicityArtifact): Promise<ContractUtxo[]>;
|
|
5
|
+
export declare function inspectMultiAssetContractCall(config: SimplicityClientConfig, artifact: SimplicityArtifact, input: MultiAssetContractCallInput): Promise<MultiAssetInspectResult>;
|
|
6
|
+
export declare function executeMultiAssetContractCall(config: SimplicityClientConfig, artifact: SimplicityArtifact, input: MultiAssetContractCallInput): Promise<MultiAssetExecuteResult>;
|
|
5
7
|
export declare function executeGaslessContractCall(config: SimplicityClientConfig, artifact: SimplicityArtifact, input: GaslessExecuteInput): Promise<GaslessExecuteResult>;
|
package/dist/core/executor.js
CHANGED
|
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.inspectContractCall = inspectContractCall;
|
|
7
7
|
exports.executeContractCall = executeContractCall;
|
|
8
8
|
exports.findContractUtxos = findContractUtxos;
|
|
9
|
+
exports.inspectMultiAssetContractCall = inspectMultiAssetContractCall;
|
|
10
|
+
exports.executeMultiAssetContractCall = executeMultiAssetContractCall;
|
|
9
11
|
exports.executeGaslessContractCall = executeGaslessContractCall;
|
|
10
12
|
const promises_1 = require("node:fs/promises");
|
|
11
13
|
const node_os_1 = require("node:os");
|
|
@@ -25,6 +27,11 @@ function btcStringToSatNumber(btcStr) {
|
|
|
25
27
|
throw new errors_1.ValidationError(`Invalid BTC amount: ${btcStr}`);
|
|
26
28
|
return Math.round(x * 1e8);
|
|
27
29
|
}
|
|
30
|
+
function optionalBtcStringToSatNumber(value) {
|
|
31
|
+
if (value === undefined || value === null || value === "")
|
|
32
|
+
return undefined;
|
|
33
|
+
return btcStringToSatNumber(String(value));
|
|
34
|
+
}
|
|
28
35
|
function satToBtcNumber(sat) {
|
|
29
36
|
return Number((sat / 1e8).toFixed(8));
|
|
30
37
|
}
|
|
@@ -34,6 +41,24 @@ function satToBtcStringFromNumber(sat) {
|
|
|
34
41
|
const frac = value % 100000000n;
|
|
35
42
|
return `${whole}.${frac.toString().padStart(8, "0")}`;
|
|
36
43
|
}
|
|
44
|
+
function normalizeAssetId(asset) {
|
|
45
|
+
return asset.trim().toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
function addAssetAmount(target, asset, amountSat) {
|
|
48
|
+
if (!Number.isSafeInteger(amountSat) || amountSat < 0) {
|
|
49
|
+
throw new errors_1.ValidationError("amountSat must be a non-negative safe integer");
|
|
50
|
+
}
|
|
51
|
+
const key = normalizeAssetId(asset);
|
|
52
|
+
target.set(key, (target.get(key) ?? 0) + amountSat);
|
|
53
|
+
}
|
|
54
|
+
async function resolveLbtcAssetId(config) {
|
|
55
|
+
const sidechain = await callRpc(config, "getsidechaininfo", []);
|
|
56
|
+
const asset = sidechain.pegged_asset;
|
|
57
|
+
if (asset && /^[0-9a-f]{64}$/i.test(asset)) {
|
|
58
|
+
return asset.toLowerCase();
|
|
59
|
+
}
|
|
60
|
+
return "bitcoin";
|
|
61
|
+
}
|
|
37
62
|
function getArtifactLocktime(artifact) {
|
|
38
63
|
const legacyMinHeight = artifact.legacy?.params?.minHeight;
|
|
39
64
|
if (typeof legacyMinHeight === "number" && Number.isFinite(legacyMinHeight) && legacyMinHeight >= 0) {
|
|
@@ -59,21 +84,49 @@ function getEffectiveLocktime(artifact, input) {
|
|
|
59
84
|
}
|
|
60
85
|
return getArtifactLocktime(artifact);
|
|
61
86
|
}
|
|
62
|
-
function
|
|
87
|
+
function parseSimcWitnessOutput(output) {
|
|
63
88
|
const lines = output
|
|
64
89
|
.split("\n")
|
|
65
90
|
.map((line) => line.trim())
|
|
66
91
|
.filter(Boolean);
|
|
92
|
+
let program;
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
if (line.startsWith("{")) {
|
|
95
|
+
try {
|
|
96
|
+
const parsed = JSON.parse(line);
|
|
97
|
+
if (typeof parsed.witness === "string" && parsed.witness.trim()) {
|
|
98
|
+
return {
|
|
99
|
+
...(typeof parsed.program === "string" && parsed.program.trim() ? { program: parsed.program.trim() } : {}),
|
|
100
|
+
witness: parsed.witness.trim(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Continue with the human-readable simc output format.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const line of lines) {
|
|
110
|
+
if (line.startsWith("Program:")) {
|
|
111
|
+
const rest = line.slice("Program:".length).trim();
|
|
112
|
+
if (rest)
|
|
113
|
+
program = rest;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const programLineIndex = lines.findIndex((line) => line === "Program:");
|
|
117
|
+
if (!program && programLineIndex >= 0 && lines[programLineIndex + 1]) {
|
|
118
|
+
program = lines[programLineIndex + 1];
|
|
119
|
+
}
|
|
67
120
|
for (const line of lines) {
|
|
68
121
|
if (line.startsWith("Witness:")) {
|
|
69
122
|
const rest = line.slice("Witness:".length).trim();
|
|
70
123
|
if (rest)
|
|
71
|
-
return rest;
|
|
124
|
+
return { ...(program ? { program } : {}), witness: rest };
|
|
72
125
|
}
|
|
73
126
|
}
|
|
74
127
|
const nextLineIndex = lines.findIndex((line) => line === "Witness:");
|
|
75
128
|
if (nextLineIndex >= 0 && lines[nextLineIndex + 1]) {
|
|
76
|
-
return lines[nextLineIndex + 1];
|
|
129
|
+
return { ...(program ? { program } : {}), witness: lines[nextLineIndex + 1] };
|
|
77
130
|
}
|
|
78
131
|
throw new errors_1.ExecutionError("Could not parse Witness from simc output", { output });
|
|
79
132
|
}
|
|
@@ -139,7 +192,7 @@ async function scanUtxosByAddress(config, contractAddress) {
|
|
|
139
192
|
vout: utxo.vout,
|
|
140
193
|
scriptPubKey: utxo.scriptPubKey,
|
|
141
194
|
asset: utxo.asset,
|
|
142
|
-
sat:
|
|
195
|
+
sat: optionalBtcStringToSatNumber(utxo.amount) ?? 0,
|
|
143
196
|
height: utxo.height,
|
|
144
197
|
confirmed: true,
|
|
145
198
|
}));
|
|
@@ -160,7 +213,7 @@ async function allocateWalletAddress(config, wallet) {
|
|
|
160
213
|
const address = await callRpc(config, "getnewaddress", [], wallet);
|
|
161
214
|
const info = await getAddressInfo(config, wallet, address);
|
|
162
215
|
return {
|
|
163
|
-
address
|
|
216
|
+
address,
|
|
164
217
|
scriptPubKey: info.scriptPubKey,
|
|
165
218
|
};
|
|
166
219
|
}
|
|
@@ -375,6 +428,252 @@ async function executeContractCall(config, artifact, input) {
|
|
|
375
428
|
async function findContractUtxos(config, artifact) {
|
|
376
429
|
return scanUtxosByAddress(config, artifact.compiled.contractAddress);
|
|
377
430
|
}
|
|
431
|
+
function findRequestedContractUtxo(utxos, input) {
|
|
432
|
+
if (!input)
|
|
433
|
+
return null;
|
|
434
|
+
const requested = utxos.find((utxo) => (utxo.txid === input.txid
|
|
435
|
+
&& (input.vout === undefined || utxo.vout === input.vout))) ?? null;
|
|
436
|
+
if (!requested)
|
|
437
|
+
return null;
|
|
438
|
+
return {
|
|
439
|
+
...requested,
|
|
440
|
+
asset: input.asset ?? requested.asset,
|
|
441
|
+
sat: input.amountSat ?? requested.sat,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function buildMultiAssetBlindingSecrets(input) {
|
|
445
|
+
const secrets = [];
|
|
446
|
+
if (input.contractInput?.rawTxHex && input.contractInput.blindingPrivateKey) {
|
|
447
|
+
secrets.push({
|
|
448
|
+
index: 0,
|
|
449
|
+
rawTxHex: input.contractInput.rawTxHex,
|
|
450
|
+
vout: input.contractInput.vout ?? input.contractUtxo.vout,
|
|
451
|
+
blindingPrivateKey: input.contractInput.blindingPrivateKey,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
input.extraInputs.forEach((extra, index) => {
|
|
455
|
+
if (extra.amountBlinder && extra.assetBlinder) {
|
|
456
|
+
secrets.push({
|
|
457
|
+
index: index + 1,
|
|
458
|
+
asset: extra.asset,
|
|
459
|
+
amountSat: extra.amountSat,
|
|
460
|
+
amountBlinder: extra.amountBlinder,
|
|
461
|
+
assetBlinder: extra.assetBlinder,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
return secrets;
|
|
466
|
+
}
|
|
467
|
+
async function buildMultiAssetExecutionState(config, artifact, input) {
|
|
468
|
+
await callRpc(config, "getwalletinfo", [], input.wallet);
|
|
469
|
+
if (input.outputs.length === 0) {
|
|
470
|
+
throw new errors_1.ValidationError("outputs must contain at least one recipient");
|
|
471
|
+
}
|
|
472
|
+
const contractUtxos = await scanUtxosByAddress(config, artifact.compiled.contractAddress);
|
|
473
|
+
const feeSat = input.feeSat ?? config.defaults?.feeSat ?? DEFAULT_FEE_SAT;
|
|
474
|
+
const requested = findRequestedContractUtxo(contractUtxos, input.contractInput);
|
|
475
|
+
const contractUtxo = requested ?? chooseUtxo(contractUtxos, 1, config.defaults?.utxoPolicy ?? "smallest_over");
|
|
476
|
+
if (!contractUtxo) {
|
|
477
|
+
throw new errors_1.UtxoNotFoundError(`No contract UTXO found for address=${artifact.compiled.contractAddress}`);
|
|
478
|
+
}
|
|
479
|
+
const extraInputs = input.extraInputs ?? [];
|
|
480
|
+
const inputTotals = new Map();
|
|
481
|
+
addAssetAmount(inputTotals, contractUtxo.asset, contractUtxo.sat);
|
|
482
|
+
for (const extra of extraInputs) {
|
|
483
|
+
addAssetAmount(inputTotals, extra.asset, extra.amountSat);
|
|
484
|
+
}
|
|
485
|
+
const lbtcAssetId = await resolveLbtcAssetId(config);
|
|
486
|
+
const outputTotals = new Map();
|
|
487
|
+
for (const output of input.outputs) {
|
|
488
|
+
addAssetAmount(outputTotals, output.asset, output.amountSat);
|
|
489
|
+
}
|
|
490
|
+
addAssetAmount(outputTotals, lbtcAssetId, feeSat);
|
|
491
|
+
const changeOutputs = [];
|
|
492
|
+
let changeAddress;
|
|
493
|
+
if (input.changeAddress) {
|
|
494
|
+
await getAddressInfo(config, input.wallet, input.changeAddress);
|
|
495
|
+
changeAddress = input.changeAddress;
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
changeAddress = (await allocateWalletAddress(config, input.wallet)).address;
|
|
499
|
+
}
|
|
500
|
+
const assetKeys = Array.from(new Set([...inputTotals.keys(), ...outputTotals.keys()])).sort();
|
|
501
|
+
for (const asset of assetKeys) {
|
|
502
|
+
const available = inputTotals.get(asset) ?? 0;
|
|
503
|
+
const required = outputTotals.get(asset) ?? 0;
|
|
504
|
+
if (available < required) {
|
|
505
|
+
throw new errors_1.ValidationError("Insufficient inputs for multi-asset contract call", {
|
|
506
|
+
asset,
|
|
507
|
+
available,
|
|
508
|
+
required,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
const changeSat = available - required;
|
|
512
|
+
if (changeSat > 0) {
|
|
513
|
+
changeOutputs.push({
|
|
514
|
+
address: changeAddress,
|
|
515
|
+
asset,
|
|
516
|
+
amountSat: changeSat,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
const inputsJson = [
|
|
521
|
+
{
|
|
522
|
+
txid: contractUtxo.txid,
|
|
523
|
+
vout: contractUtxo.vout,
|
|
524
|
+
sequence: input.locktimeHeight ? DEFAULT_SEQUENCE : DEFAULT_SEQUENCE,
|
|
525
|
+
},
|
|
526
|
+
...extraInputs.map((extra) => ({
|
|
527
|
+
txid: extra.txid,
|
|
528
|
+
vout: extra.vout,
|
|
529
|
+
sequence: extra.sequence ?? DEFAULT_SEQUENCE,
|
|
530
|
+
})),
|
|
531
|
+
];
|
|
532
|
+
const inputAssets = [contractUtxo.asset, ...extraInputs.map((extra) => extra.asset)];
|
|
533
|
+
const blinderIndexFor = (asset) => {
|
|
534
|
+
const normalized = normalizeAssetId(asset);
|
|
535
|
+
const extraIndex = extraInputs.findIndex((input) => normalizeAssetId(input.asset) === normalized);
|
|
536
|
+
if (extraIndex >= 0)
|
|
537
|
+
return extraIndex + 1;
|
|
538
|
+
const index = inputAssets.findIndex((inputAsset) => normalizeAssetId(inputAsset) === normalized);
|
|
539
|
+
return index >= 0 ? index : undefined;
|
|
540
|
+
};
|
|
541
|
+
const psetOutputs = await Promise.all([...input.outputs, ...changeOutputs].map(async (output) => {
|
|
542
|
+
await getAddressInfo(config, input.wallet, output.address);
|
|
543
|
+
return output;
|
|
544
|
+
}));
|
|
545
|
+
const outputsJson = [
|
|
546
|
+
...psetOutputs.map((output) => ({
|
|
547
|
+
[output.address]: satToBtcNumber(output.amountSat),
|
|
548
|
+
asset: output.asset,
|
|
549
|
+
})),
|
|
550
|
+
{ fee: satToBtcNumber(feeSat) },
|
|
551
|
+
];
|
|
552
|
+
const halOutputsJson = [
|
|
553
|
+
...psetOutputs.map((output) => ({
|
|
554
|
+
address: output.address,
|
|
555
|
+
asset: output.asset,
|
|
556
|
+
amount: satToBtcNumber(output.amountSat),
|
|
557
|
+
blinderIndex: blinderIndexFor(output.asset),
|
|
558
|
+
})),
|
|
559
|
+
{
|
|
560
|
+
address: "fee",
|
|
561
|
+
asset: lbtcAssetId,
|
|
562
|
+
amount: satToBtcNumber(feeSat),
|
|
563
|
+
},
|
|
564
|
+
];
|
|
565
|
+
const locktime = getEffectiveLocktime(artifact, input);
|
|
566
|
+
const createResult = locktime
|
|
567
|
+
? {
|
|
568
|
+
pset: await callRpc(config, "createpsbt", [inputsJson, outputsJson, locktime, true], input.wallet),
|
|
569
|
+
}
|
|
570
|
+
: (await (0, toolchain_1.runSimplicityCreatePset)(config.toolchain.halSimplicityPath, inputsJson, halOutputsJson));
|
|
571
|
+
const pset1 = createResult.pset;
|
|
572
|
+
if (!pset1) {
|
|
573
|
+
throw new errors_1.ExecutionError("pset create did not return a pset", createResult);
|
|
574
|
+
}
|
|
575
|
+
const contractSpec = `${contractUtxo.scriptPubKey}:${contractUtxo.asset}:${satToBtcStringFromNumber(contractUtxo.sat)}`;
|
|
576
|
+
const psetWithUtxos = await callRpc(config, "utxoupdatepsbt", [pset1], input.wallet);
|
|
577
|
+
const contractUpdated = (await (0, toolchain_1.runSimplicityUpdateInput)(config.toolchain.halSimplicityPath, psetWithUtxos, 0, contractSpec, artifact.compiled.cmr, artifact.compiled.internalKey));
|
|
578
|
+
if (!contractUpdated.pset) {
|
|
579
|
+
throw new errors_1.ExecutionError("update-input did not return a pset", contractUpdated);
|
|
580
|
+
}
|
|
581
|
+
const psetUpdated = await callRpc(config, "utxoupdatepsbt", [contractUpdated.pset], input.wallet);
|
|
582
|
+
const decoded = await decodePsbt(config, psetUpdated, input.wallet);
|
|
583
|
+
const summary = buildPsetSummary(decoded, {
|
|
584
|
+
network: artifact.network,
|
|
585
|
+
purpose: input.purpose ?? "sdk_multi_asset_contract_call",
|
|
586
|
+
bondDefinitionId: null,
|
|
587
|
+
definitionType: artifact.definition?.definitionType,
|
|
588
|
+
definitionId: artifact.definition?.definitionId,
|
|
589
|
+
definitionHash: artifact.definition?.hash,
|
|
590
|
+
definitionTrustMode: artifact.definition?.trustMode,
|
|
591
|
+
definitionAnchorMode: artifact.definition?.anchorMode,
|
|
592
|
+
stateType: artifact.state?.stateType,
|
|
593
|
+
stateId: artifact.state?.stateId,
|
|
594
|
+
stateHash: artifact.state?.hash,
|
|
595
|
+
stateTrustMode: artifact.state?.trustMode,
|
|
596
|
+
stateAnchorMode: artifact.state?.anchorMode,
|
|
597
|
+
expectedLiquidReceiver: input.outputs[0]?.address,
|
|
598
|
+
contractAddress: artifact.compiled.contractAddress,
|
|
599
|
+
cmr: artifact.compiled.cmr,
|
|
600
|
+
internalKey: artifact.compiled.internalKey,
|
|
601
|
+
program: artifact.compiled.program,
|
|
602
|
+
minHeight: locktime || undefined,
|
|
603
|
+
});
|
|
604
|
+
const { canonicalJson: summaryCanonicalJson, hash: summaryHash } = (0, summary_1.summarize)(summary);
|
|
605
|
+
return {
|
|
606
|
+
psetBase64: psetUpdated,
|
|
607
|
+
summary,
|
|
608
|
+
summaryHash,
|
|
609
|
+
summaryCanonicalJson,
|
|
610
|
+
contractUtxo,
|
|
611
|
+
extraInputs,
|
|
612
|
+
requestedOutputs: psetOutputs,
|
|
613
|
+
changeOutputs,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
async function inspectMultiAssetContractCall(config, artifact, input) {
|
|
617
|
+
const state = await buildMultiAssetExecutionState(config, artifact, input);
|
|
618
|
+
return {
|
|
619
|
+
mode: "inspect",
|
|
620
|
+
summary: state.summary,
|
|
621
|
+
summaryHash: state.summaryHash,
|
|
622
|
+
summaryCanonicalJson: state.summaryCanonicalJson,
|
|
623
|
+
psetBase64: state.psetBase64,
|
|
624
|
+
contractUtxo: state.contractUtxo,
|
|
625
|
+
warnings: [],
|
|
626
|
+
extraInputs: state.extraInputs,
|
|
627
|
+
requestedOutputs: state.requestedOutputs,
|
|
628
|
+
changeOutputs: state.changeOutputs,
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
async function executeMultiAssetContractCall(config, artifact, input) {
|
|
632
|
+
const state = await buildMultiAssetExecutionState(config, artifact, input);
|
|
633
|
+
const blindingSecrets = buildMultiAssetBlindingSecrets({
|
|
634
|
+
contractInput: input.contractInput,
|
|
635
|
+
contractUtxo: state.contractUtxo,
|
|
636
|
+
extraInputs: state.extraInputs,
|
|
637
|
+
});
|
|
638
|
+
const blindedPset = blindingSecrets.length > 0
|
|
639
|
+
? (await (0, toolchain_1.runSimplicityBlindPset)(state.psetBase64, blindingSecrets))
|
|
640
|
+
: { pset: state.psetBase64 };
|
|
641
|
+
if (!blindedPset.pset) {
|
|
642
|
+
throw new errors_1.ExecutionError(`blind PSET did not return a pset${blindedPset.error ? `: ${blindedPset.error}` : ""}`, blindedPset);
|
|
643
|
+
}
|
|
644
|
+
const contractSignedPset = await signContractInput(config, artifact, blindedPset.pset, input.signer.privkeyHex, input.witness);
|
|
645
|
+
const walletSignedParsed = await callRpc(config, "walletprocesspsbt", [contractSignedPset, true, "ALL", true], input.wallet);
|
|
646
|
+
if (!walletSignedParsed.psbt) {
|
|
647
|
+
throw new errors_1.ExecutionError("walletprocesspsbt did not return a signed pset", walletSignedParsed);
|
|
648
|
+
}
|
|
649
|
+
const finalizedParsed = await callRpc(config, "finalizepsbt", [walletSignedParsed.psbt, true]);
|
|
650
|
+
if (!finalizedParsed.complete || !finalizedParsed.hex) {
|
|
651
|
+
throw new errors_1.ExecutionError("PSET was not complete after wallet signing", finalizedParsed);
|
|
652
|
+
}
|
|
653
|
+
const rawTxHex = normalizeRawTx(finalizedParsed.hex);
|
|
654
|
+
let txId;
|
|
655
|
+
if (input.broadcast) {
|
|
656
|
+
const mempoolParsed = await callRpc(config, "testmempoolaccept", [[rawTxHex]], input.wallet);
|
|
657
|
+
if (!Array.isArray(mempoolParsed) || mempoolParsed[0]?.allowed !== true) {
|
|
658
|
+
throw new errors_1.ExecutionError("testmempoolaccept rejected transaction", mempoolParsed);
|
|
659
|
+
}
|
|
660
|
+
txId = await callRpc(config, "sendrawtransaction", [rawTxHex], input.wallet);
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
mode: "execute",
|
|
664
|
+
summary: state.summary,
|
|
665
|
+
summaryHash: state.summaryHash,
|
|
666
|
+
summaryCanonicalJson: state.summaryCanonicalJson,
|
|
667
|
+
psetBase64: walletSignedParsed.psbt,
|
|
668
|
+
rawTxHex,
|
|
669
|
+
txId,
|
|
670
|
+
broadcasted: Boolean(input.broadcast),
|
|
671
|
+
contractUtxo: state.contractUtxo,
|
|
672
|
+
extraInputs: state.extraInputs,
|
|
673
|
+
requestedOutputs: state.requestedOutputs,
|
|
674
|
+
changeOutputs: state.changeOutputs,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
378
677
|
async function executeGaslessContractCall(config, artifact, input) {
|
|
379
678
|
if (input.relayer) {
|
|
380
679
|
return executeRelayedGaslessContractCall(config, artifact, input, input.relayer);
|
|
@@ -520,10 +819,10 @@ async function signContractInput(config, artifact, psetBase64, privkeyHex, witne
|
|
|
520
819
|
const simfRenderedPath = node_path_1.default.join(workDir, "program.simf");
|
|
521
820
|
await (0, promises_1.writeFile)(simfRenderedPath, simfRendered, "utf8");
|
|
522
821
|
const witnessOutput = await (0, toolchain_1.runSimcWithWitness)(config.toolchain.simcPath, simfRenderedPath, witnessPath);
|
|
523
|
-
const
|
|
524
|
-
const contractFinalized = (await (0, toolchain_1.
|
|
822
|
+
const simc = parseSimcWitnessOutput(witnessOutput);
|
|
823
|
+
const contractFinalized = (await (0, toolchain_1.runSimplicityFinalize)(config.toolchain.halSimplicityPath, psetBase64, 0, artifact.compiled.program, simc.witness, { ...(simc.program ? { redeemProgram: simc.program } : {}) }));
|
|
525
824
|
if (!contractFinalized.pset) {
|
|
526
|
-
throw new errors_1.ExecutionError(
|
|
825
|
+
throw new errors_1.ExecutionError(`finalize did not return a pset${contractFinalized.error ? `: ${contractFinalized.error}` : ""}`, contractFinalized);
|
|
527
826
|
}
|
|
528
827
|
return contractFinalized.pset;
|
|
529
828
|
}
|
package/dist/core/rpc.js
CHANGED
|
@@ -26,7 +26,9 @@ class ElementsRpcClient {
|
|
|
26
26
|
});
|
|
27
27
|
const payload = (await response.json());
|
|
28
28
|
if (!response.ok || payload.error) {
|
|
29
|
-
|
|
29
|
+
const rpcMessage = payload.error?.message ? `: ${payload.error.message}` : "";
|
|
30
|
+
const rpcCode = payload.error?.code === undefined ? "" : ` (code ${payload.error.code})`;
|
|
31
|
+
throw new errors_1.ExecutionError(`RPC ${method} failed${rpcCode}${rpcMessage}`, {
|
|
30
32
|
status: response.status,
|
|
31
33
|
error: payload.error,
|
|
32
34
|
});
|
package/dist/core/toolchain.d.ts
CHANGED
|
@@ -9,6 +9,30 @@ export declare function runSimcCompile(simcPath: string, simfPath: string): Prom
|
|
|
9
9
|
export declare function runSimcWithWitness(simcPath: string, simfPath: string, witnessPath: string): Promise<string>;
|
|
10
10
|
export declare function runHalInfo(halPath: string, program: string): Promise<unknown>;
|
|
11
11
|
export declare function runHalUpdateInput(halPath: string, pset: string, inputIndex: number, utxoSpec: string, cmr: string, internalKey: string): Promise<unknown>;
|
|
12
|
+
export declare function runHalCreatePset(halPath: string, inputs: Array<{
|
|
13
|
+
txid: string;
|
|
14
|
+
vout: number;
|
|
15
|
+
sequence?: number;
|
|
16
|
+
}>, outputs: Array<{
|
|
17
|
+
address: string;
|
|
18
|
+
asset: string;
|
|
19
|
+
amount: number;
|
|
20
|
+
}>): Promise<unknown>;
|
|
21
|
+
export declare function runSimplicityCreatePset(halPath: string, inputs: Array<{
|
|
22
|
+
txid: string;
|
|
23
|
+
vout: number;
|
|
24
|
+
sequence?: number;
|
|
25
|
+
}>, outputs: Array<{
|
|
26
|
+
address: string;
|
|
27
|
+
asset: string;
|
|
28
|
+
amount: number;
|
|
29
|
+
blinderIndex?: number;
|
|
30
|
+
}>): Promise<unknown>;
|
|
31
|
+
export declare function runSimplicityUpdateInput(halPath: string, pset: string, inputIndex: number, inputUtxo: string, cmr: string, internalKey: string): Promise<unknown>;
|
|
32
|
+
export declare function runSimplicityBlindPset(pset: string, inputSecrets: Array<Record<string, unknown>>): Promise<unknown>;
|
|
12
33
|
export declare function runHalSighash(halPath: string, pset: string, inputIndex: number, cmr: string, privkeyHex: string): Promise<unknown>;
|
|
13
34
|
export declare function runHalFinalize(halPath: string, pset: string, inputIndex: number, program: string, witness: string): Promise<unknown>;
|
|
35
|
+
export declare function runSimplicityFinalize(halPath: string, pset: string, inputIndex: number, program: string, witness: string, options?: {
|
|
36
|
+
redeemProgram?: string;
|
|
37
|
+
}): Promise<unknown>;
|
|
14
38
|
export declare function runHalExtract(halPath: string, pset: string): Promise<string>;
|
package/dist/core/toolchain.js
CHANGED
|
@@ -5,8 +5,13 @@ exports.runSimcCompile = runSimcCompile;
|
|
|
5
5
|
exports.runSimcWithWitness = runSimcWithWitness;
|
|
6
6
|
exports.runHalInfo = runHalInfo;
|
|
7
7
|
exports.runHalUpdateInput = runHalUpdateInput;
|
|
8
|
+
exports.runHalCreatePset = runHalCreatePset;
|
|
9
|
+
exports.runSimplicityCreatePset = runSimplicityCreatePset;
|
|
10
|
+
exports.runSimplicityUpdateInput = runSimplicityUpdateInput;
|
|
11
|
+
exports.runSimplicityBlindPset = runSimplicityBlindPset;
|
|
8
12
|
exports.runHalSighash = runHalSighash;
|
|
9
13
|
exports.runHalFinalize = runHalFinalize;
|
|
14
|
+
exports.runSimplicityFinalize = runSimplicityFinalize;
|
|
10
15
|
exports.runHalExtract = runHalExtract;
|
|
11
16
|
const node_child_process_1 = require("node:child_process");
|
|
12
17
|
const node_util_1 = require("node:util");
|
|
@@ -21,7 +26,13 @@ async function runCommand(command, args, options = {}) {
|
|
|
21
26
|
return { stdout: stdout.trim(), stderr: stderr.trim() };
|
|
22
27
|
}
|
|
23
28
|
catch (error) {
|
|
24
|
-
|
|
29
|
+
const cause = error;
|
|
30
|
+
const details = [
|
|
31
|
+
cause.message ? `message=${cause.message}` : "",
|
|
32
|
+
cause.stderr ? `stderr=${cause.stderr.trim()}` : "",
|
|
33
|
+
cause.stdout ? `stdout=${cause.stdout.trim()}` : "",
|
|
34
|
+
].filter(Boolean);
|
|
35
|
+
throw new errors_1.ToolchainError(`Command failed: ${command} ${args.slice(0, 3).join(" ")}${details.length ? ` | ${details.join(" | ")}` : ""}`, error);
|
|
25
36
|
}
|
|
26
37
|
}
|
|
27
38
|
async function runSimcCompile(simcPath, simfPath) {
|
|
@@ -41,6 +52,7 @@ async function runHalUpdateInput(halPath, pset, inputIndex, utxoSpec, cmr, inter
|
|
|
41
52
|
"simplicity",
|
|
42
53
|
"pset",
|
|
43
54
|
"update-input",
|
|
55
|
+
"--liquid",
|
|
44
56
|
pset,
|
|
45
57
|
String(inputIndex),
|
|
46
58
|
"-i",
|
|
@@ -52,10 +64,73 @@ async function runHalUpdateInput(halPath, pset, inputIndex, utxoSpec, cmr, inter
|
|
|
52
64
|
]);
|
|
53
65
|
return JSON.parse(result.stdout);
|
|
54
66
|
}
|
|
67
|
+
async function runHalCreatePset(halPath, inputs, outputs) {
|
|
68
|
+
const result = await runCommand(halPath, [
|
|
69
|
+
"simplicity",
|
|
70
|
+
"pset",
|
|
71
|
+
"create",
|
|
72
|
+
"--liquid",
|
|
73
|
+
JSON.stringify(inputs),
|
|
74
|
+
JSON.stringify(outputs),
|
|
75
|
+
]);
|
|
76
|
+
return JSON.parse(result.stdout);
|
|
77
|
+
}
|
|
78
|
+
async function runSimplicityCreatePset(halPath, inputs, outputs) {
|
|
79
|
+
const helperPath = process.env.HAZBASE_SIMPLICITY_CREATE_PATH ?? "hazbase-simplicity-create";
|
|
80
|
+
try {
|
|
81
|
+
const helper = await runCommand(helperPath, [
|
|
82
|
+
"--liquid",
|
|
83
|
+
JSON.stringify(inputs),
|
|
84
|
+
JSON.stringify(outputs),
|
|
85
|
+
]);
|
|
86
|
+
return JSON.parse(helper.stdout);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
if (!message.includes("ENOENT")) {
|
|
91
|
+
return {
|
|
92
|
+
error: message,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return runHalCreatePset(halPath, inputs, outputs);
|
|
97
|
+
}
|
|
98
|
+
async function runSimplicityUpdateInput(halPath, pset, inputIndex, inputUtxo, cmr, internalKey) {
|
|
99
|
+
const helperPath = process.env.HAZBASE_SIMPLICITY_UPDATE_INPUT_PATH ?? "hazbase-simplicity-update-input";
|
|
100
|
+
try {
|
|
101
|
+
const helper = await runCommand(helperPath, [
|
|
102
|
+
"--liquid",
|
|
103
|
+
pset,
|
|
104
|
+
String(inputIndex),
|
|
105
|
+
internalKey,
|
|
106
|
+
cmr,
|
|
107
|
+
]);
|
|
108
|
+
return JSON.parse(helper.stdout);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
+
if (!message.includes("ENOENT")) {
|
|
113
|
+
return {
|
|
114
|
+
error: message,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return runHalUpdateInput(halPath, pset, inputIndex, inputUtxo, cmr, internalKey);
|
|
119
|
+
}
|
|
120
|
+
async function runSimplicityBlindPset(pset, inputSecrets) {
|
|
121
|
+
const helperPath = process.env.HAZBASE_SIMPLICITY_BLIND_PSET_PATH ?? "hazbase-simplicity-blind-pset";
|
|
122
|
+
const helper = await runCommand(helperPath, [
|
|
123
|
+
"--liquid",
|
|
124
|
+
pset,
|
|
125
|
+
JSON.stringify(inputSecrets),
|
|
126
|
+
]);
|
|
127
|
+
return JSON.parse(helper.stdout);
|
|
128
|
+
}
|
|
55
129
|
async function runHalSighash(halPath, pset, inputIndex, cmr, privkeyHex) {
|
|
56
130
|
const result = await runCommand(halPath, [
|
|
57
131
|
"simplicity",
|
|
58
132
|
"sighash",
|
|
133
|
+
"--liquid",
|
|
59
134
|
pset,
|
|
60
135
|
String(inputIndex),
|
|
61
136
|
cmr,
|
|
@@ -69,6 +144,7 @@ async function runHalFinalize(halPath, pset, inputIndex, program, witness) {
|
|
|
69
144
|
"simplicity",
|
|
70
145
|
"pset",
|
|
71
146
|
"finalize",
|
|
147
|
+
"--liquid",
|
|
72
148
|
pset,
|
|
73
149
|
String(inputIndex),
|
|
74
150
|
program,
|
|
@@ -76,7 +152,35 @@ async function runHalFinalize(halPath, pset, inputIndex, program, witness) {
|
|
|
76
152
|
]);
|
|
77
153
|
return JSON.parse(result.stdout);
|
|
78
154
|
}
|
|
155
|
+
async function runSimplicityFinalize(halPath, pset, inputIndex, program, witness, options = {}) {
|
|
156
|
+
const helperPath = process.env.HAZBASE_SIMPLICITY_FINALIZE_PATH ?? "hazbase-simplicity-finalize";
|
|
157
|
+
if (options.redeemProgram) {
|
|
158
|
+
try {
|
|
159
|
+
const helper = await runCommand(helperPath, [
|
|
160
|
+
"--liquid",
|
|
161
|
+
pset,
|
|
162
|
+
String(inputIndex),
|
|
163
|
+
options.redeemProgram,
|
|
164
|
+
witness,
|
|
165
|
+
]);
|
|
166
|
+
return JSON.parse(helper.stdout);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
170
|
+
if (!message.includes("ENOENT")) {
|
|
171
|
+
const halFallback = await runHalFinalize(halPath, pset, inputIndex, program, witness);
|
|
172
|
+
if (halFallback.pset)
|
|
173
|
+
return halFallback;
|
|
174
|
+
return {
|
|
175
|
+
...halFallback,
|
|
176
|
+
helperError: message,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return runHalFinalize(halPath, pset, inputIndex, program, witness);
|
|
182
|
+
}
|
|
79
183
|
async function runHalExtract(halPath, pset) {
|
|
80
|
-
const result = await runCommand(halPath, ["simplicity", "pset", "extract", pset]);
|
|
184
|
+
const result = await runCommand(halPath, ["simplicity", "pset", "extract", "--liquid", pset]);
|
|
81
185
|
return result.stdout;
|
|
82
186
|
}
|