@hazbase/simplicity 0.0.1 → 0.0.3
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 +114 -0
- package/dist/cli.js +72 -1
- package/dist/client/ContractFactory.d.ts +2 -1
- package/dist/client/ContractFactory.js +3 -0
- package/dist/client/DeployedContract.d.ts +15 -1
- package/dist/client/DeployedContract.js +22 -0
- package/dist/client/SimplicityClient.d.ts +14 -1
- package/dist/client/SimplicityClient.js +26 -0
- package/dist/core/artifact.js +12 -1
- package/dist/core/compiler.js +67 -3
- package/dist/core/definition.d.ts +18 -0
- package/dist/core/definition.js +279 -0
- package/dist/core/errors.d.ts +6 -0
- package/dist/core/errors.js +13 -1
- package/dist/core/executor.js +29 -2
- package/dist/core/types.d.ts +53 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# @hazbase/simplicity
|
|
2
|
+
[](https://badge.fury.io/js/@hazbase%2Fsimplicity)
|
|
3
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
2
4
|
|
|
3
5
|
`@hazbase/simplicity` is a Node.js / TypeScript SDK for working with Simplicity contracts on Liquid with an EVM-like developer workflow. It lets you compile SimplicityHL (`.simf`) contracts, derive the contract address, fund that address, inspect the spend you are about to make, execute the contract, and optionally run fee-sponsored flows through a sponsor wallet or relayer. It also ships with built-in presets so you can start from known-good contract templates before moving to custom `.simf` code.
|
|
4
6
|
|
|
@@ -7,6 +9,22 @@ This SDK is designed to help Node developers get productive quickly, but it is s
|
|
|
7
9
|
- This SDK currently optimizes for explicit / unblinded success paths first.
|
|
8
10
|
- Gasless support exists, but it comes in multiple modes with different tradeoffs.
|
|
9
11
|
|
|
12
|
+
Consumer validation note:
|
|
13
|
+
- The published npm package has been validated from a fresh external Node.js project using `npm install @hazbase/simplicity`.
|
|
14
|
+
- Verified flows include preset-based contract execution, custom `.simf` execution, and relayer-backed gasless execution on `liquidtestnet`.
|
|
15
|
+
|
|
16
|
+
## Validated Scenarios
|
|
17
|
+
|
|
18
|
+
The published package has been exercised from a blank external consumer project. For the full reproducible fixture, see [docs/consumer-validation/README.md](./docs/consumer-validation/README.md).
|
|
19
|
+
|
|
20
|
+
| Scenario | Status | Notes |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| Fresh install + JS import | Success | `npm install @hazbase/simplicity` and `import { createSimplicityClient } from "@hazbase/simplicity"` both worked |
|
|
23
|
+
| CLI smoke | Success | `npx simplicity-cli presets list` worked from the external project |
|
|
24
|
+
| Preset flow (`p2pkLockHeight`) | Success | compile -> fund -> inspect -> execute(`broadcast=true`) |
|
|
25
|
+
| Custom `.simf` flow | Success | `compileFromFile(...)` -> fund -> inspect -> execute(`broadcast=true`) |
|
|
26
|
+
| Relayer-backed gasless flow | Success | `executeGasless(...)` succeeded from the external project |
|
|
27
|
+
|
|
10
28
|
## Who This Is For
|
|
11
29
|
|
|
12
30
|
This README is for you if:
|
|
@@ -45,6 +63,77 @@ With the current SDK you can build and test flows such as:
|
|
|
45
63
|
|
|
46
64
|
You can also design more advanced systems such as ERC20-like token behavior, but the model is different from Ethereum. On Liquid/Simplicity, you usually represent state transitions as UTXO transitions instead of account storage updates. So the SDK can support that kind of application, but it does not mean you port Solidity account logic 1:1.
|
|
47
65
|
|
|
66
|
+
## Trusted Definition JSON
|
|
67
|
+
|
|
68
|
+
When your contract depends on off-chain business metadata such as a bond definition, coupon schedule, note terms, or asset terms, you usually do not want to trust a plain JSON file by itself. This SDK now supports a **hash-anchor** model for definition JSON.
|
|
69
|
+
|
|
70
|
+
What that means:
|
|
71
|
+
- the SDK canonicalizes the JSON using stable key ordering,
|
|
72
|
+
- computes `sha256(canonicalJson)`,
|
|
73
|
+
- stores that hash in the artifact as a definition anchor,
|
|
74
|
+
- injects `DEFINITION_HASH` and `DEFINITION_ID` into compile-time template vars when a definition is provided,
|
|
75
|
+
- lets you verify later that the JSON you are reading still matches the contract/artifact it was compiled against.
|
|
76
|
+
|
|
77
|
+
There are now two anchor modes:
|
|
78
|
+
- `artifact-hash-anchor`: the JSON hash is anchored in the artifact and verified later against that artifact.
|
|
79
|
+
- `on-chain-constant-committed`: the JSON hash is anchored in the artifact and also committed into executed contract logic, so it materially affects the compiled program, CMR, and contract address.
|
|
80
|
+
|
|
81
|
+
Today, `on-chain-constant-committed` is guaranteed for custom `.simf` contracts that include the blessed `require_definition_anchor()` helper pattern. Built-in presets still default to artifact-only anchors for now.
|
|
82
|
+
The SDK does **not** trust artifact JSON alone for this verdict. `trust.onChainAnchorVerified` only becomes `true` when the SDK can read the source file again and re-detect the blessed helper pattern. If the source file is unavailable, the claimed mode may still be `on-chain-constant-committed`, but `onChainAnchorVerified` will remain `false`.
|
|
83
|
+
|
|
84
|
+
Minimal TypeScript flow:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const definition = await sdk.loadDefinition({
|
|
88
|
+
type: "bond",
|
|
89
|
+
id: "BOND-2026-001",
|
|
90
|
+
jsonPath: "./docs/definitions/bond-definition.json",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const compiled = await sdk.compileFromFile({
|
|
94
|
+
simfPath: "./docs/definitions/bond-anchor.simf",
|
|
95
|
+
templateVars: {
|
|
96
|
+
MIN_HEIGHT: 2344430,
|
|
97
|
+
SIGNER_XONLY: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
|
98
|
+
},
|
|
99
|
+
definition: {
|
|
100
|
+
type: definition.definitionType,
|
|
101
|
+
id: definition.definitionId,
|
|
102
|
+
schemaVersion: definition.schemaVersion,
|
|
103
|
+
jsonPath: definition.sourcePath,
|
|
104
|
+
anchorMode: "on-chain-constant-committed",
|
|
105
|
+
},
|
|
106
|
+
artifactPath: "./bond.artifact.json",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const verification = await sdk.verifyDefinitionAgainstArtifact({
|
|
110
|
+
artifactPath: "./bond.artifact.json",
|
|
111
|
+
jsonPath: "./docs/definitions/bond-definition.json",
|
|
112
|
+
type: "bond",
|
|
113
|
+
id: "BOND-2026-001",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
console.log(verification.ok);
|
|
117
|
+
console.log(verification.trust.effectiveMode);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
CLI equivalents:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
simplicity-cli definition show \
|
|
124
|
+
--type bond \
|
|
125
|
+
--id BOND-2026-001 \
|
|
126
|
+
--json-path ./docs/definitions/bond-definition.json
|
|
127
|
+
|
|
128
|
+
simplicity-cli definition verify \
|
|
129
|
+
--artifact ./bond.artifact.json \
|
|
130
|
+
--type bond \
|
|
131
|
+
--id BOND-2026-001 \
|
|
132
|
+
--json-path ./docs/definitions/bond-definition.json
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
For a bond-oriented walkthrough, see [docs/definitions/README.md](./docs/definitions/README.md).
|
|
136
|
+
|
|
48
137
|
## Install
|
|
49
138
|
|
|
50
139
|
You need three things:
|
|
@@ -467,6 +556,12 @@ Use custom `.simf` when:
|
|
|
467
556
|
- you need your own parameterization,
|
|
468
557
|
- you want to build app-specific wrappers on top of the generic SDK.
|
|
469
558
|
|
|
559
|
+
A real external-consumer validation of this path has been completed with:
|
|
560
|
+
- a fresh project created outside this repo,
|
|
561
|
+
- a local `contract.simf` file owned by that project,
|
|
562
|
+
- `compileFromFile(...)`,
|
|
563
|
+
- funding + inspect + `broadcast: true` execution.
|
|
564
|
+
|
|
470
565
|
Recommended path:
|
|
471
566
|
- learn the lifecycle with a preset first,
|
|
472
567
|
- then move to `compileFromFile(...)` once the model is clear.
|
|
@@ -714,6 +809,15 @@ These examples are included to help you jump to the right workflow quickly.
|
|
|
714
809
|
- [execute-htlc.ts](./examples/execute-htlc.ts): HTLC preset with custom witness values.
|
|
715
810
|
- [execute-transfer-with-timeout-cooperative.ts](./examples/execute-transfer-with-timeout-cooperative.ts): cooperative multi-witness timeout flow.
|
|
716
811
|
- [gasless-transfer.ts](./examples/gasless-transfer.ts): standard relayer-backed gasless L-BTC transfer.
|
|
812
|
+
- [define-bond.ts](./examples/define-bond.ts): compile a bond example with a trusted definition hash anchor.
|
|
813
|
+
- [show-bond-definition.ts](./examples/show-bond-definition.ts): verify and retrieve a trusted bond definition from JSON + artifact.
|
|
814
|
+
|
|
815
|
+
In addition to the in-repo examples, the package has also been validated from a blank external consumer project with:
|
|
816
|
+
- `npm install @hazbase/simplicity`
|
|
817
|
+
- JS/TS import of `createSimplicityClient`
|
|
818
|
+
- preset compile -> fund -> inspect -> execute
|
|
819
|
+
- custom `.simf` compile -> fund -> inspect -> execute
|
|
820
|
+
- relayer-backed gasless execution
|
|
717
821
|
|
|
718
822
|
## FAQ / Practical Notes
|
|
719
823
|
|
|
@@ -729,6 +833,16 @@ It means: compile the contract, derive its address, then fund that address with
|
|
|
729
833
|
|
|
730
834
|
An artifact is the contract's compile output plus the metadata needed to reload, inspect, and execute it later. Think of it as the bridge between compilation time and on-chain execution time.
|
|
731
835
|
|
|
836
|
+
When you compile with `definition: { ... }`, the artifact also carries:
|
|
837
|
+
- `definitionType`
|
|
838
|
+
- `definitionId`
|
|
839
|
+
- `schemaVersion`
|
|
840
|
+
- `hash`
|
|
841
|
+
- `trustMode`
|
|
842
|
+
- `anchorMode`
|
|
843
|
+
|
|
844
|
+
That is what allows the SDK and CLI to verify that an off-chain JSON definition still matches the contract you compiled.
|
|
845
|
+
|
|
732
846
|
### When should I use a preset instead of a custom `.simf` file?
|
|
733
847
|
|
|
734
848
|
Use a preset first when you are learning the lifecycle or your use case already matches a built-in contract. Move to custom `.simf` when your business rules are app-specific.
|
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
const promises_1 = require("node:fs/promises");
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const artifact_1 = require("./core/artifact");
|
|
10
|
+
const definition_1 = require("./core/definition");
|
|
10
11
|
const presets_1 = require("./core/presets");
|
|
11
12
|
const errors_1 = require("./core/errors");
|
|
12
13
|
const SimplicityClient_1 = require("./client/SimplicityClient");
|
|
@@ -65,6 +66,25 @@ function parseWitnessSigners(values) {
|
|
|
65
66
|
return [name, { type: "schnorrPrivkeyHex", privkeyHex }];
|
|
66
67
|
}));
|
|
67
68
|
}
|
|
69
|
+
function parseDefinitionInput() {
|
|
70
|
+
const type = getArg("definition-type");
|
|
71
|
+
const id = getArg("definition-id");
|
|
72
|
+
const jsonPath = getArg("definition-json");
|
|
73
|
+
const valueJson = getArg("definition-value");
|
|
74
|
+
const schemaVersion = getArg("definition-schema-version");
|
|
75
|
+
const anchorMode = getArg("definition-anchor-mode");
|
|
76
|
+
if (!type && !id && !jsonPath && !valueJson && !schemaVersion && !anchorMode) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
type: type ?? "",
|
|
81
|
+
id: id ?? "",
|
|
82
|
+
schemaVersion: schemaVersion ?? undefined,
|
|
83
|
+
jsonPath,
|
|
84
|
+
value: valueJson ? JSON.parse(valueJson) : undefined,
|
|
85
|
+
anchorMode,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
68
88
|
function resolveConfig() {
|
|
69
89
|
return {
|
|
70
90
|
network: getArg("network", "liquidtestnet"),
|
|
@@ -357,6 +377,18 @@ function formatArtifactHelp(artifact, preset, utxos) {
|
|
|
357
377
|
` status: ${status}`,
|
|
358
378
|
` ready: ${ready ? "yes" : "no"}`,
|
|
359
379
|
].join("\n");
|
|
380
|
+
const definition = artifact.definition
|
|
381
|
+
? [
|
|
382
|
+
` type: ${artifact.definition.definitionType}`,
|
|
383
|
+
` id: ${artifact.definition.definitionId}`,
|
|
384
|
+
` schema version: ${artifact.definition.schemaVersion}`,
|
|
385
|
+
` hash: ${artifact.definition.hash}`,
|
|
386
|
+
` trust mode: ${artifact.definition.trustMode}`,
|
|
387
|
+
` anchor mode: ${artifact.definition.anchorMode}`,
|
|
388
|
+
` on-chain helper: ${artifact.definition.onChainAnchor?.helper ?? "(none)"}`,
|
|
389
|
+
` source verified: ${artifact.definition.onChainAnchor?.sourceVerified === true ? "yes" : "no"}`,
|
|
390
|
+
].join("\n")
|
|
391
|
+
: " (none)";
|
|
360
392
|
const compileSource = artifact.source.simfPath ?? artifact.legacy?.simfTemplatePath ?? "(unknown)";
|
|
361
393
|
const templateVars = artifact.source.templateVars ?? {};
|
|
362
394
|
const inspectCommand = `simplicity-cli contract inspect --artifact ./artifact.json --wallet simplicity-test --privkey <privkey-hex> --to-address tex1...`;
|
|
@@ -375,6 +407,9 @@ function formatArtifactHelp(artifact, preset, utxos) {
|
|
|
375
407
|
"Template Vars:",
|
|
376
408
|
indent(JSON.stringify(templateVars, null, 2)),
|
|
377
409
|
"",
|
|
410
|
+
"Definition Anchor:",
|
|
411
|
+
definition,
|
|
412
|
+
"",
|
|
378
413
|
"Suggested Commands:",
|
|
379
414
|
` ${inspectCommand}`,
|
|
380
415
|
` ${executeCommand}`,
|
|
@@ -416,17 +451,52 @@ async function main() {
|
|
|
416
451
|
const subcommand = process.argv[3];
|
|
417
452
|
const sdk = (0, SimplicityClient_1.createSimplicityClient)(resolveConfig());
|
|
418
453
|
if (!command) {
|
|
419
|
-
throw new Error("Usage: simplicity-cli <compile|presets|preset|contract|artifact|gasless> ...");
|
|
454
|
+
throw new Error("Usage: simplicity-cli <compile|presets|preset|contract|artifact|definition|gasless> ...");
|
|
420
455
|
}
|
|
421
456
|
if (command === "compile") {
|
|
422
457
|
const result = await sdk.compileFromFile({
|
|
423
458
|
simfPath: requireArg("simf"),
|
|
424
459
|
templateVars: parseAssignments(getMultiArgs("template-var")),
|
|
425
460
|
artifactPath: getArg("artifact"),
|
|
461
|
+
definition: parseDefinitionInput(),
|
|
426
462
|
});
|
|
427
463
|
printJson({ artifact: result.artifact, deployment: result.deployment() });
|
|
428
464
|
return;
|
|
429
465
|
}
|
|
466
|
+
if (command === "definition" && subcommand === "show") {
|
|
467
|
+
const definition = await (0, definition_1.loadDefinitionInput)({
|
|
468
|
+
type: requireArg("type"),
|
|
469
|
+
id: requireArg("id"),
|
|
470
|
+
jsonPath: getArg("json-path"),
|
|
471
|
+
value: getArg("value") ? JSON.parse(getArg("value")) : undefined,
|
|
472
|
+
schemaVersion: getArg("schema-version"),
|
|
473
|
+
});
|
|
474
|
+
printJson({
|
|
475
|
+
...definition,
|
|
476
|
+
anchorRecommendation: "Use --definition-anchor-mode on-chain-constant-committed with a blessed custom .simf helper for on-chain enforcement",
|
|
477
|
+
});
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (command === "definition" && subcommand === "verify") {
|
|
481
|
+
const verification = await sdk.verifyDefinitionAgainstArtifact({
|
|
482
|
+
artifactPath: requireArg("artifact"),
|
|
483
|
+
type: getArg("type"),
|
|
484
|
+
id: getArg("id"),
|
|
485
|
+
expectedType: getArg("expected-type"),
|
|
486
|
+
expectedId: getArg("expected-id"),
|
|
487
|
+
jsonPath: getArg("json-path"),
|
|
488
|
+
value: getArg("value") ? JSON.parse(getArg("value")) : undefined,
|
|
489
|
+
schemaVersion: getArg("schema-version"),
|
|
490
|
+
});
|
|
491
|
+
printJson({
|
|
492
|
+
verified: verification.ok,
|
|
493
|
+
reason: verification.reason,
|
|
494
|
+
definition: verification.definition,
|
|
495
|
+
artifactDefinition: verification.artifactDefinition ?? null,
|
|
496
|
+
trust: verification.trust,
|
|
497
|
+
});
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
430
500
|
if (command === "presets" && subcommand === "list") {
|
|
431
501
|
printJson((0, presets_1.listPresets)().map((preset) => (0, presets_1.describePreset)(preset)));
|
|
432
502
|
return;
|
|
@@ -472,6 +542,7 @@ async function main() {
|
|
|
472
542
|
preset: requireArg("preset"),
|
|
473
543
|
params,
|
|
474
544
|
artifactPath: getArg("artifact"),
|
|
545
|
+
definition: parseDefinitionInput(),
|
|
475
546
|
});
|
|
476
547
|
printJson({ artifact: result.artifact, deployment: result.deployment() });
|
|
477
548
|
return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DeploymentInfo, SimplicityArtifact, SimplicityClientConfig } from "../core/types";
|
|
1
|
+
import { ArtifactDefinitionMetadata, DeploymentInfo, SimplicityArtifact, SimplicityClientConfig } from "../core/types";
|
|
2
2
|
import { DeployedContract } from "./DeployedContract";
|
|
3
3
|
export declare class CompiledContract {
|
|
4
4
|
private readonly config;
|
|
@@ -7,6 +7,7 @@ export declare class CompiledContract {
|
|
|
7
7
|
get contractAddress(): string;
|
|
8
8
|
get cmr(): string;
|
|
9
9
|
get program(): string;
|
|
10
|
+
definition(): ArtifactDefinitionMetadata | null;
|
|
10
11
|
deployment(): DeploymentInfo;
|
|
11
12
|
saveArtifact(path: string): Promise<void>;
|
|
12
13
|
at(addressOverride?: string): DeployedContract;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { verifyDefinitionAgainstArtifact } from "../core/definition";
|
|
2
|
+
import { ArtifactDefinitionMetadata, ContractUtxo, ExecuteCallInput, ExecuteResult, GaslessExecuteInput, GaslessExecuteResult, InspectCallInput, InspectResult, SimplicityArtifact, SimplicityClientConfig, WaitForFundingInput } from "../core/types";
|
|
2
3
|
export declare class DeployedContract {
|
|
3
4
|
private readonly config;
|
|
4
5
|
readonly artifact: SimplicityArtifact;
|
|
@@ -9,4 +10,17 @@ export declare class DeployedContract {
|
|
|
9
10
|
inspectCall(input: InspectCallInput): Promise<InspectResult>;
|
|
10
11
|
execute(input: ExecuteCallInput): Promise<ExecuteResult>;
|
|
11
12
|
executeGasless(input: GaslessExecuteInput): Promise<GaslessExecuteResult>;
|
|
13
|
+
getTrustedDefinition(input: {
|
|
14
|
+
jsonPath?: string;
|
|
15
|
+
value?: unknown;
|
|
16
|
+
type?: string;
|
|
17
|
+
id?: string;
|
|
18
|
+
schemaVersion?: string;
|
|
19
|
+
}): Promise<{
|
|
20
|
+
verified: boolean;
|
|
21
|
+
definition: Awaited<ReturnType<typeof verifyDefinitionAgainstArtifact>>["definition"];
|
|
22
|
+
artifactDefinition: ArtifactDefinitionMetadata | null;
|
|
23
|
+
reason?: string;
|
|
24
|
+
trust: Awaited<ReturnType<typeof verifyDefinitionAgainstArtifact>>["trust"];
|
|
25
|
+
}>;
|
|
12
26
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DeployedContract = void 0;
|
|
4
4
|
const executor_1 = require("../core/executor");
|
|
5
|
+
const definition_1 = require("../core/definition");
|
|
5
6
|
const executor_2 = require("../core/executor");
|
|
6
7
|
class DeployedContract {
|
|
7
8
|
config;
|
|
@@ -39,5 +40,26 @@ class DeployedContract {
|
|
|
39
40
|
async executeGasless(input) {
|
|
40
41
|
return (0, executor_2.executeGaslessContractCall)(this.config, this.artifact, input);
|
|
41
42
|
}
|
|
43
|
+
async getTrustedDefinition(input) {
|
|
44
|
+
const verification = await (0, definition_1.verifyDefinitionAgainstArtifact)({
|
|
45
|
+
artifact: this.artifact,
|
|
46
|
+
definition: {
|
|
47
|
+
type: input.type ?? this.artifact.definition?.definitionType ?? "",
|
|
48
|
+
id: input.id ?? this.artifact.definition?.definitionId ?? "",
|
|
49
|
+
schemaVersion: input.schemaVersion,
|
|
50
|
+
jsonPath: input.jsonPath,
|
|
51
|
+
value: input.value,
|
|
52
|
+
},
|
|
53
|
+
expectedType: this.artifact.definition?.definitionType,
|
|
54
|
+
expectedId: this.artifact.definition?.definitionId,
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
verified: verification.ok,
|
|
58
|
+
definition: verification.definition,
|
|
59
|
+
artifactDefinition: verification.artifactDefinition ?? null,
|
|
60
|
+
reason: verification.reason,
|
|
61
|
+
trust: verification.trust,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
42
64
|
}
|
|
43
65
|
exports.DeployedContract = DeployedContract;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ElementsRpcClient } from "../core/rpc";
|
|
2
|
-
import { CompileFromFileInput, CompileFromPresetInput, SimplicityArtifact, SimplicityClientConfig } from "../core/types";
|
|
2
|
+
import { DefinitionInput, DefinitionVerificationResult, CompileFromFileInput, CompileFromPresetInput, DefinitionDescriptor, SimplicityArtifact, SimplicityClientConfig } from "../core/types";
|
|
3
3
|
import { RelayerClient } from "../gasless/RelayerClient";
|
|
4
4
|
import { GaslessTransferInput, GaslessTransferResult, RelayerClientConfig } from "../gasless/types";
|
|
5
5
|
import { CompiledContract } from "./ContractFactory";
|
|
@@ -14,6 +14,19 @@ export declare class SimplicityClient {
|
|
|
14
14
|
compileFromFile(input: CompileFromFileInput): Promise<CompiledContract>;
|
|
15
15
|
compileFromPreset(input: CompileFromPresetInput): Promise<CompiledContract>;
|
|
16
16
|
loadArtifact(path: string): Promise<CompiledContract>;
|
|
17
|
+
define(input: DefinitionInput): Promise<DefinitionDescriptor>;
|
|
18
|
+
loadDefinition(input: DefinitionInput): Promise<DefinitionDescriptor>;
|
|
19
|
+
verifyDefinitionAgainstArtifact(input: {
|
|
20
|
+
artifactPath?: string;
|
|
21
|
+
artifact?: SimplicityArtifact;
|
|
22
|
+
jsonPath?: string;
|
|
23
|
+
value?: unknown;
|
|
24
|
+
expectedType?: string;
|
|
25
|
+
expectedId?: string;
|
|
26
|
+
type?: string;
|
|
27
|
+
id?: string;
|
|
28
|
+
schemaVersion?: string;
|
|
29
|
+
}): Promise<DefinitionVerificationResult>;
|
|
17
30
|
fromArtifact(artifact: SimplicityArtifact): DeployedContract;
|
|
18
31
|
relayer(config: RelayerClientConfig): RelayerClient;
|
|
19
32
|
private gaslessTransfer;
|
|
@@ -4,6 +4,8 @@ exports.SimplicityClient = void 0;
|
|
|
4
4
|
exports.createSimplicityClient = createSimplicityClient;
|
|
5
5
|
const artifact_1 = require("../core/artifact");
|
|
6
6
|
const compiler_1 = require("../core/compiler");
|
|
7
|
+
const definition_1 = require("../core/definition");
|
|
8
|
+
const errors_1 = require("../core/errors");
|
|
7
9
|
const rpc_1 = require("../core/rpc");
|
|
8
10
|
const RelayerClient_1 = require("../gasless/RelayerClient");
|
|
9
11
|
const ContractFactory_1 = require("./ContractFactory");
|
|
@@ -31,6 +33,30 @@ class SimplicityClient {
|
|
|
31
33
|
const artifact = await (0, artifact_1.loadArtifact)(path, this.config.network);
|
|
32
34
|
return new ContractFactory_1.CompiledContract(this.config, artifact);
|
|
33
35
|
}
|
|
36
|
+
async define(input) {
|
|
37
|
+
return (0, definition_1.loadDefinitionInput)(input);
|
|
38
|
+
}
|
|
39
|
+
async loadDefinition(input) {
|
|
40
|
+
return (0, definition_1.loadDefinitionInput)(input);
|
|
41
|
+
}
|
|
42
|
+
async verifyDefinitionAgainstArtifact(input) {
|
|
43
|
+
const artifact = input.artifact ?? (input.artifactPath ? await (0, artifact_1.loadArtifact)(input.artifactPath, this.config.network) : undefined);
|
|
44
|
+
if (!artifact) {
|
|
45
|
+
throw new errors_1.ValidationError("artifactPath or artifact is required");
|
|
46
|
+
}
|
|
47
|
+
return (0, definition_1.verifyDefinitionAgainstArtifact)({
|
|
48
|
+
artifact,
|
|
49
|
+
definition: {
|
|
50
|
+
type: input.type ?? input.expectedType ?? artifact.definition?.definitionType ?? "",
|
|
51
|
+
id: input.id ?? input.expectedId ?? artifact.definition?.definitionId ?? "",
|
|
52
|
+
schemaVersion: input.schemaVersion,
|
|
53
|
+
jsonPath: input.jsonPath,
|
|
54
|
+
value: input.value,
|
|
55
|
+
},
|
|
56
|
+
expectedType: input.expectedType,
|
|
57
|
+
expectedId: input.expectedId,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
34
60
|
fromArtifact(artifact) {
|
|
35
61
|
return new DeployedContract_1.DeployedContract(this.config, artifact);
|
|
36
62
|
}
|
package/dist/core/artifact.js
CHANGED
|
@@ -17,7 +17,16 @@ function isArtifactV5(value) {
|
|
|
17
17
|
}
|
|
18
18
|
function normalizeArtifact(artifact, networkDefault = "liquidtestnet") {
|
|
19
19
|
if (artifact.version === exports.SDK_ARTIFACT_VERSION) {
|
|
20
|
-
|
|
20
|
+
const current = artifact;
|
|
21
|
+
return {
|
|
22
|
+
...current,
|
|
23
|
+
definition: current.definition
|
|
24
|
+
? {
|
|
25
|
+
...current.definition,
|
|
26
|
+
anchorMode: current.definition.anchorMode ?? "artifact-hash-anchor",
|
|
27
|
+
}
|
|
28
|
+
: undefined,
|
|
29
|
+
};
|
|
21
30
|
}
|
|
22
31
|
if (!isArtifactV5(artifact)) {
|
|
23
32
|
throw new errors_1.ArtifactError("Unsupported artifact version", artifact);
|
|
@@ -55,6 +64,7 @@ function normalizeArtifact(artifact, networkDefault = "liquidtestnet") {
|
|
|
55
64
|
sdkVersion: exports.SDK_PACKAGE_VERSION,
|
|
56
65
|
notes: null,
|
|
57
66
|
},
|
|
67
|
+
definition: undefined,
|
|
58
68
|
legacy: {
|
|
59
69
|
simfTemplatePath: artifact.simfTemplatePath,
|
|
60
70
|
params: artifact.params,
|
|
@@ -90,6 +100,7 @@ async function loadArtifact(artifactPath, networkDefault = "liquidtestnet") {
|
|
|
90
100
|
simfTemplatePath: legacySimfTemplatePath,
|
|
91
101
|
}
|
|
92
102
|
: undefined,
|
|
103
|
+
definition: normalized.definition,
|
|
93
104
|
};
|
|
94
105
|
}
|
|
95
106
|
async function saveArtifact(artifactPath, artifact) {
|
package/dist/core/compiler.js
CHANGED
|
@@ -9,6 +9,7 @@ const promises_1 = require("node:fs/promises");
|
|
|
9
9
|
const node_os_1 = require("node:os");
|
|
10
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
11
|
const artifact_1 = require("./artifact");
|
|
12
|
+
const definition_1 = require("./definition");
|
|
12
13
|
const errors_1 = require("./errors");
|
|
13
14
|
const presets_1 = require("./presets");
|
|
14
15
|
const templating_1 = require("./templating");
|
|
@@ -67,6 +68,12 @@ async function buildArtifact(input) {
|
|
|
67
68
|
sdkVersion: artifact_1.SDK_PACKAGE_VERSION,
|
|
68
69
|
notes: null,
|
|
69
70
|
},
|
|
71
|
+
definition: input.definition
|
|
72
|
+
? (0, definition_1.buildArtifactDefinitionMetadata)(input.definition, {
|
|
73
|
+
anchorMode: input.definition.anchorMode,
|
|
74
|
+
onChainAnchor: input.definition.onChainAnchor,
|
|
75
|
+
})
|
|
76
|
+
: undefined,
|
|
70
77
|
legacy: {
|
|
71
78
|
simfTemplatePath: input.sourceSimfPath,
|
|
72
79
|
params: {
|
|
@@ -77,8 +84,38 @@ async function buildArtifact(input) {
|
|
|
77
84
|
}, input.config.network);
|
|
78
85
|
}
|
|
79
86
|
async function compileFromFile(config, input) {
|
|
87
|
+
const definition = input.definition ? await (0, definition_1.loadDefinitionInput)(input.definition) : undefined;
|
|
88
|
+
if (definition && input.templateVars?.DEFINITION_HASH !== undefined) {
|
|
89
|
+
throw new errors_1.ValidationError("DEFINITION_HASH must not be provided explicitly when definition metadata is supplied", { code: "DEFINITION_HASH_OVERRIDE_FORBIDDEN" });
|
|
90
|
+
}
|
|
91
|
+
if (definition && input.templateVars?.DEFINITION_ID !== undefined) {
|
|
92
|
+
throw new errors_1.ValidationError("DEFINITION_ID must not be provided explicitly when definition metadata is supplied", { code: "DEFINITION_ID_OVERRIDE_FORBIDDEN" });
|
|
93
|
+
}
|
|
80
94
|
const rawSource = await (0, promises_1.readFile)(input.simfPath, "utf8");
|
|
81
|
-
|
|
95
|
+
let onChainAnchor;
|
|
96
|
+
if (input.definition?.anchorMode === "on-chain-constant-committed") {
|
|
97
|
+
const detection = (0, definition_1.detectOnChainDefinitionAnchor)(rawSource);
|
|
98
|
+
if (!rawSource.includes("{{DEFINITION_HASH}}")) {
|
|
99
|
+
throw new errors_1.ValidationError("Requested on-chain constant-committed definition anchor, but the .simf source does not contain {{DEFINITION_HASH}}", { code: "DEFINITION_HASH_PLACEHOLDER_MISSING" });
|
|
100
|
+
}
|
|
101
|
+
if (!detection.sourceVerified || !detection.helper) {
|
|
102
|
+
const code = detection.reason?.includes("called")
|
|
103
|
+
? "DEFINITION_ONCHAIN_HELPER_NOT_CALLED"
|
|
104
|
+
: "DEFINITION_ONCHAIN_HELPER_MISSING";
|
|
105
|
+
throw new errors_1.ValidationError(`Requested on-chain constant-committed definition anchor, but the .simf source does not contain the required anchor helper pattern: ${detection.reason ?? "unknown reason"}`, { code });
|
|
106
|
+
}
|
|
107
|
+
onChainAnchor = {
|
|
108
|
+
helper: detection.helper,
|
|
109
|
+
templateVar: "DEFINITION_HASH",
|
|
110
|
+
sourceVerified: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const templateVars = {
|
|
114
|
+
...(input.templateVars ?? {}),
|
|
115
|
+
...(definition && input.templateVars?.DEFINITION_HASH === undefined ? { DEFINITION_HASH: definition.hash } : {}),
|
|
116
|
+
...(definition && input.templateVars?.DEFINITION_ID === undefined ? { DEFINITION_ID: definition.definitionId } : {}),
|
|
117
|
+
};
|
|
118
|
+
const rendered = (0, templating_1.renderTemplate)(rawSource, templateVars);
|
|
82
119
|
const workDir = await (0, promises_1.mkdtemp)(node_path_1.default.join((0, node_os_1.tmpdir)(), "simplicity-sdk-compile-"));
|
|
83
120
|
const renderedPath = node_path_1.default.join(workDir, node_path_1.default.basename(input.simfPath));
|
|
84
121
|
await (0, promises_1.writeFile)(renderedPath, rendered, "utf8");
|
|
@@ -87,7 +124,14 @@ async function compileFromFile(config, input) {
|
|
|
87
124
|
renderedSimfPath: renderedPath,
|
|
88
125
|
sourceMode: "file",
|
|
89
126
|
sourceSimfPath: input.simfPath,
|
|
90
|
-
templateVars
|
|
127
|
+
templateVars,
|
|
128
|
+
definition: definition
|
|
129
|
+
? {
|
|
130
|
+
...definition,
|
|
131
|
+
anchorMode: input.definition?.anchorMode ?? "artifact-hash-anchor",
|
|
132
|
+
onChainAnchor,
|
|
133
|
+
}
|
|
134
|
+
: undefined,
|
|
91
135
|
});
|
|
92
136
|
if (input.artifactPath) {
|
|
93
137
|
await (0, artifact_1.saveArtifact)(input.artifactPath, artifact);
|
|
@@ -96,7 +140,21 @@ async function compileFromFile(config, input) {
|
|
|
96
140
|
}
|
|
97
141
|
async function compileFromPreset(config, input) {
|
|
98
142
|
const preset = (0, presets_1.getPresetOrThrow)(input.preset);
|
|
99
|
-
|
|
143
|
+
if (input.definition?.anchorMode === "on-chain-constant-committed") {
|
|
144
|
+
throw new errors_1.UnsupportedFeatureError(`Preset '${preset.id}' does not yet support on-chain constant-committed definition anchors`, { code: "DEFINITION_ANCHOR_MODE_UNSUPPORTED_FOR_PRESET", preset: preset.id });
|
|
145
|
+
}
|
|
146
|
+
const definition = input.definition ? await (0, definition_1.loadDefinitionInput)(input.definition) : undefined;
|
|
147
|
+
if (definition && input.params.DEFINITION_HASH !== undefined) {
|
|
148
|
+
throw new errors_1.ValidationError("DEFINITION_HASH must not be provided explicitly when definition metadata is supplied", { code: "DEFINITION_HASH_OVERRIDE_FORBIDDEN" });
|
|
149
|
+
}
|
|
150
|
+
if (definition && input.params.DEFINITION_ID !== undefined) {
|
|
151
|
+
throw new errors_1.ValidationError("DEFINITION_ID must not be provided explicitly when definition metadata is supplied", { code: "DEFINITION_ID_OVERRIDE_FORBIDDEN" });
|
|
152
|
+
}
|
|
153
|
+
const params = {
|
|
154
|
+
...(0, presets_1.validatePresetParams)(preset, input.params),
|
|
155
|
+
...(definition && input.params.DEFINITION_HASH === undefined ? { DEFINITION_HASH: definition.hash } : {}),
|
|
156
|
+
...(definition && input.params.DEFINITION_ID === undefined ? { DEFINITION_ID: definition.definitionId } : {}),
|
|
157
|
+
};
|
|
100
158
|
const rawSource = await (0, promises_1.readFile)(preset.simfTemplatePath, "utf8");
|
|
101
159
|
const rendered = (0, templating_1.renderTemplate)(rawSource, params);
|
|
102
160
|
const workDir = await (0, promises_1.mkdtemp)(node_path_1.default.join((0, node_os_1.tmpdir)(), "simplicity-sdk-preset-"));
|
|
@@ -109,6 +167,12 @@ async function compileFromPreset(config, input) {
|
|
|
109
167
|
sourceSimfPath: preset.simfTemplatePath,
|
|
110
168
|
preset: preset.id,
|
|
111
169
|
templateVars: params,
|
|
170
|
+
definition: definition
|
|
171
|
+
? {
|
|
172
|
+
...definition,
|
|
173
|
+
anchorMode: input.definition?.anchorMode ?? "artifact-hash-anchor",
|
|
174
|
+
}
|
|
175
|
+
: undefined,
|
|
112
176
|
});
|
|
113
177
|
if (input.artifactPath) {
|
|
114
178
|
await (0, artifact_1.saveArtifact)(input.artifactPath, artifact);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ArtifactDefinitionMetadata, DefinitionAnchorMode, DefinitionDescriptor, DefinitionInput, DefinitionVerificationResult, SimplicityArtifact } from "./types";
|
|
2
|
+
export declare function loadDefinitionInput(input: DefinitionInput): Promise<DefinitionDescriptor>;
|
|
3
|
+
export declare function detectOnChainDefinitionAnchor(simfSource: string): {
|
|
4
|
+
sourceVerified: boolean;
|
|
5
|
+
helper?: "nonzero-eq_256";
|
|
6
|
+
reason?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function buildArtifactDefinitionMetadata(definition: DefinitionDescriptor, options?: {
|
|
9
|
+
anchorMode?: DefinitionAnchorMode;
|
|
10
|
+
onChainAnchor?: ArtifactDefinitionMetadata["onChainAnchor"];
|
|
11
|
+
}): ArtifactDefinitionMetadata;
|
|
12
|
+
export declare function verifyDefinitionDescriptorAgainstArtifact(definition: DefinitionDescriptor, artifactDefinition?: ArtifactDefinitionMetadata, expectedType?: string, expectedId?: string): DefinitionVerificationResult;
|
|
13
|
+
export declare function verifyDefinitionAgainstArtifact(input: {
|
|
14
|
+
artifact: SimplicityArtifact;
|
|
15
|
+
definition: DefinitionInput;
|
|
16
|
+
expectedType?: string;
|
|
17
|
+
expectedId?: string;
|
|
18
|
+
}): Promise<DefinitionVerificationResult>;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadDefinitionInput = loadDefinitionInput;
|
|
7
|
+
exports.detectOnChainDefinitionAnchor = detectOnChainDefinitionAnchor;
|
|
8
|
+
exports.buildArtifactDefinitionMetadata = buildArtifactDefinitionMetadata;
|
|
9
|
+
exports.verifyDefinitionDescriptorAgainstArtifact = verifyDefinitionDescriptorAgainstArtifact;
|
|
10
|
+
exports.verifyDefinitionAgainstArtifact = verifyDefinitionAgainstArtifact;
|
|
11
|
+
const promises_1 = require("node:fs/promises");
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const errors_1 = require("./errors");
|
|
14
|
+
const summary_1 = require("./summary");
|
|
15
|
+
const DEFAULT_SCHEMA_VERSION = "1";
|
|
16
|
+
const DEFAULT_ANCHOR_MODE = "artifact-hash-anchor";
|
|
17
|
+
const ZERO_HASH_256 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
18
|
+
function stripComments(source) {
|
|
19
|
+
return source
|
|
20
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
21
|
+
.replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
22
|
+
}
|
|
23
|
+
function extractFunctionBody(source, functionName) {
|
|
24
|
+
const marker = `fn ${functionName}()`;
|
|
25
|
+
const start = source.indexOf(marker);
|
|
26
|
+
if (start === -1)
|
|
27
|
+
return null;
|
|
28
|
+
const braceStart = source.indexOf("{", start);
|
|
29
|
+
if (braceStart === -1)
|
|
30
|
+
return null;
|
|
31
|
+
let depth = 0;
|
|
32
|
+
for (let i = braceStart; i < source.length; i += 1) {
|
|
33
|
+
const char = source[i];
|
|
34
|
+
if (char === "{")
|
|
35
|
+
depth += 1;
|
|
36
|
+
if (char === "}") {
|
|
37
|
+
depth -= 1;
|
|
38
|
+
if (depth === 0) {
|
|
39
|
+
return source.slice(braceStart + 1, i);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function assertNonEmpty(value, fieldName) {
|
|
46
|
+
if (!value || value.trim().length === 0) {
|
|
47
|
+
throw new errors_1.DefinitionError(`${fieldName} must not be empty`);
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
function ensureSerializable(value, seen = new WeakSet()) {
|
|
52
|
+
if (value === undefined) {
|
|
53
|
+
throw new errors_1.DefinitionError("Definition JSON must not contain undefined values");
|
|
54
|
+
}
|
|
55
|
+
if (value === null)
|
|
56
|
+
return;
|
|
57
|
+
if (typeof value === "bigint") {
|
|
58
|
+
throw new errors_1.DefinitionError("Definition JSON must not contain bigint values");
|
|
59
|
+
}
|
|
60
|
+
if (value instanceof Date) {
|
|
61
|
+
throw new errors_1.DefinitionError("Definition JSON must not contain Date objects; normalize them first");
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
for (const entry of value)
|
|
65
|
+
ensureSerializable(entry, seen);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (typeof value === "object") {
|
|
69
|
+
const objectValue = value;
|
|
70
|
+
if (seen.has(objectValue)) {
|
|
71
|
+
throw new errors_1.DefinitionError("Definition JSON must not contain circular references");
|
|
72
|
+
}
|
|
73
|
+
seen.add(objectValue);
|
|
74
|
+
for (const entry of Object.values(objectValue))
|
|
75
|
+
ensureSerializable(entry, seen);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function resolveDefinitionValue(input) {
|
|
79
|
+
if ((input.jsonPath ? 1 : 0) + (input.value !== undefined ? 1 : 0) !== 1) {
|
|
80
|
+
throw new errors_1.DefinitionError("Exactly one of jsonPath or value must be provided");
|
|
81
|
+
}
|
|
82
|
+
if (input.jsonPath) {
|
|
83
|
+
const sourcePath = node_path_1.default.resolve(input.jsonPath);
|
|
84
|
+
const raw = await (0, promises_1.readFile)(sourcePath, "utf8");
|
|
85
|
+
try {
|
|
86
|
+
return { value: JSON.parse(raw), sourcePath };
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
throw new errors_1.DefinitionError(`Failed to parse definition JSON at ${sourcePath}`, error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { value: input.value };
|
|
93
|
+
}
|
|
94
|
+
async function loadDefinitionInput(input) {
|
|
95
|
+
const definitionType = assertNonEmpty(input.type, "definition.type");
|
|
96
|
+
const definitionId = assertNonEmpty(input.id, "definition.id");
|
|
97
|
+
const schemaVersion = assertNonEmpty(input.schemaVersion ?? DEFAULT_SCHEMA_VERSION, "definition.schemaVersion");
|
|
98
|
+
const { value, sourcePath } = await resolveDefinitionValue(input);
|
|
99
|
+
ensureSerializable(value);
|
|
100
|
+
const canonicalJson = (0, summary_1.stableStringify)(value);
|
|
101
|
+
return {
|
|
102
|
+
definitionType,
|
|
103
|
+
definitionId,
|
|
104
|
+
schemaVersion,
|
|
105
|
+
canonicalJson,
|
|
106
|
+
hash: (0, summary_1.sha256HexUtf8)(canonicalJson),
|
|
107
|
+
sourcePath,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function detectOnChainDefinitionAnchor(simfSource) {
|
|
111
|
+
const source = stripComments(simfSource.replace(/\r\n/g, "\n"));
|
|
112
|
+
if (!source.includes("{{DEFINITION_HASH}}")) {
|
|
113
|
+
return { sourceVerified: false, reason: "DEFINITION_HASH placeholder is missing" };
|
|
114
|
+
}
|
|
115
|
+
const helperBody = extractFunctionBody(source, "require_definition_anchor");
|
|
116
|
+
if (!helperBody) {
|
|
117
|
+
return { sourceVerified: false, reason: "Required anchor helper function is missing" };
|
|
118
|
+
}
|
|
119
|
+
if (!helperBody.includes("let anchored_definition_hash: u256 = 0x{{DEFINITION_HASH}};")) {
|
|
120
|
+
return { sourceVerified: false, reason: "Required anchored_definition_hash assignment is missing" };
|
|
121
|
+
}
|
|
122
|
+
if (!helperBody.includes(`let zero_hash: u256 = ${ZERO_HASH_256};`)) {
|
|
123
|
+
return { sourceVerified: false, reason: "Required zero_hash assignment is missing" };
|
|
124
|
+
}
|
|
125
|
+
if (!helperBody.includes("assert!(not(jet::eq_256(anchored_definition_hash, zero_hash)));")) {
|
|
126
|
+
return { sourceVerified: false, reason: "Required eq_256 assertion is missing" };
|
|
127
|
+
}
|
|
128
|
+
const mainBody = extractFunctionBody(source, "main");
|
|
129
|
+
if (!mainBody) {
|
|
130
|
+
return { sourceVerified: false, reason: "main function is missing" };
|
|
131
|
+
}
|
|
132
|
+
if (!mainBody.includes("require_definition_anchor();")) {
|
|
133
|
+
return { sourceVerified: false, reason: "require_definition_anchor() is not called from main" };
|
|
134
|
+
}
|
|
135
|
+
return { sourceVerified: true, helper: "nonzero-eq_256" };
|
|
136
|
+
}
|
|
137
|
+
function buildArtifactDefinitionMetadata(definition, options) {
|
|
138
|
+
return {
|
|
139
|
+
definitionType: definition.definitionType,
|
|
140
|
+
definitionId: definition.definitionId,
|
|
141
|
+
schemaVersion: definition.schemaVersion,
|
|
142
|
+
hash: definition.hash,
|
|
143
|
+
trustMode: "hash-anchor",
|
|
144
|
+
anchorMode: options?.anchorMode ?? DEFAULT_ANCHOR_MODE,
|
|
145
|
+
onChainAnchor: options?.onChainAnchor,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function verifyDefinitionDescriptorAgainstArtifact(definition, artifactDefinition, expectedType, expectedId) {
|
|
149
|
+
const noDefinitionTrust = {
|
|
150
|
+
artifactMatch: false,
|
|
151
|
+
onChainAnchorPresent: false,
|
|
152
|
+
onChainAnchorVerified: false,
|
|
153
|
+
effectiveMode: "none",
|
|
154
|
+
};
|
|
155
|
+
if (expectedType && expectedType !== definition.definitionType) {
|
|
156
|
+
return {
|
|
157
|
+
ok: false,
|
|
158
|
+
reason: `Definition type mismatch: expected=${expectedType} actual=${definition.definitionType}`,
|
|
159
|
+
definition,
|
|
160
|
+
artifactDefinition,
|
|
161
|
+
trust: noDefinitionTrust,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (expectedId && expectedId !== definition.definitionId) {
|
|
165
|
+
return {
|
|
166
|
+
ok: false,
|
|
167
|
+
reason: `Definition id mismatch: expected=${expectedId} actual=${definition.definitionId}`,
|
|
168
|
+
definition,
|
|
169
|
+
artifactDefinition,
|
|
170
|
+
trust: noDefinitionTrust,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (!artifactDefinition) {
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
reason: "Artifact does not contain definition metadata",
|
|
177
|
+
definition,
|
|
178
|
+
trust: noDefinitionTrust,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const trust = {
|
|
182
|
+
artifactMatch: false,
|
|
183
|
+
onChainAnchorPresent: artifactDefinition.anchorMode === "on-chain-constant-committed",
|
|
184
|
+
onChainAnchorVerified: false,
|
|
185
|
+
effectiveMode: artifactDefinition.anchorMode,
|
|
186
|
+
};
|
|
187
|
+
if (artifactDefinition.definitionType !== definition.definitionType) {
|
|
188
|
+
return {
|
|
189
|
+
ok: false,
|
|
190
|
+
reason: `Definition type mismatch: artifact=${artifactDefinition.definitionType} actual=${definition.definitionType}`,
|
|
191
|
+
definition,
|
|
192
|
+
artifactDefinition,
|
|
193
|
+
trust,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (artifactDefinition.definitionId !== definition.definitionId) {
|
|
197
|
+
return {
|
|
198
|
+
ok: false,
|
|
199
|
+
reason: `Definition id mismatch: artifact=${artifactDefinition.definitionId} actual=${definition.definitionId}`,
|
|
200
|
+
definition,
|
|
201
|
+
artifactDefinition,
|
|
202
|
+
trust,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (artifactDefinition.schemaVersion !== definition.schemaVersion) {
|
|
206
|
+
return {
|
|
207
|
+
ok: false,
|
|
208
|
+
reason: `Definition schemaVersion mismatch: artifact=${artifactDefinition.schemaVersion} actual=${definition.schemaVersion}`,
|
|
209
|
+
definition,
|
|
210
|
+
artifactDefinition,
|
|
211
|
+
trust,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (artifactDefinition.hash !== definition.hash) {
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
reason: `Definition hash mismatch: artifact=${artifactDefinition.hash} actual=${definition.hash}`,
|
|
218
|
+
definition,
|
|
219
|
+
artifactDefinition,
|
|
220
|
+
trust,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
ok: true,
|
|
225
|
+
definition,
|
|
226
|
+
artifactDefinition,
|
|
227
|
+
trust: {
|
|
228
|
+
...trust,
|
|
229
|
+
artifactMatch: true,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
async function resolveDefinitionTrust(artifact, baseTrust) {
|
|
234
|
+
if (!artifact.definition) {
|
|
235
|
+
return {
|
|
236
|
+
artifactMatch: false,
|
|
237
|
+
onChainAnchorPresent: false,
|
|
238
|
+
onChainAnchorVerified: false,
|
|
239
|
+
effectiveMode: "none",
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (artifact.definition.anchorMode !== "on-chain-constant-committed") {
|
|
243
|
+
return baseTrust;
|
|
244
|
+
}
|
|
245
|
+
if (artifact.source.mode !== "file" || !artifact.source.simfPath) {
|
|
246
|
+
return {
|
|
247
|
+
...baseTrust,
|
|
248
|
+
onChainAnchorPresent: true,
|
|
249
|
+
onChainAnchorVerified: false,
|
|
250
|
+
effectiveMode: "on-chain-constant-committed",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
const source = await (0, promises_1.readFile)(artifact.source.simfPath, "utf8");
|
|
255
|
+
const detection = detectOnChainDefinitionAnchor(source);
|
|
256
|
+
return {
|
|
257
|
+
...baseTrust,
|
|
258
|
+
onChainAnchorPresent: true,
|
|
259
|
+
onChainAnchorVerified: detection.sourceVerified === true,
|
|
260
|
+
effectiveMode: "on-chain-constant-committed",
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return {
|
|
265
|
+
...baseTrust,
|
|
266
|
+
onChainAnchorPresent: true,
|
|
267
|
+
onChainAnchorVerified: false,
|
|
268
|
+
effectiveMode: "on-chain-constant-committed",
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async function verifyDefinitionAgainstArtifact(input) {
|
|
273
|
+
const definition = await loadDefinitionInput(input.definition);
|
|
274
|
+
const result = verifyDefinitionDescriptorAgainstArtifact(definition, input.artifact.definition, input.expectedType, input.expectedId);
|
|
275
|
+
return {
|
|
276
|
+
...result,
|
|
277
|
+
trust: await resolveDefinitionTrust(input.artifact, result.trust),
|
|
278
|
+
};
|
|
279
|
+
}
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -31,3 +31,9 @@ export declare class ValidationError extends SimplicitySdkError {
|
|
|
31
31
|
export declare class PresetExecutionError extends SimplicitySdkError {
|
|
32
32
|
constructor(message: string, details?: unknown);
|
|
33
33
|
}
|
|
34
|
+
export declare class DefinitionError extends SimplicitySdkError {
|
|
35
|
+
constructor(message: string, details?: unknown);
|
|
36
|
+
}
|
|
37
|
+
export declare class DefinitionVerificationError extends SimplicitySdkError {
|
|
38
|
+
constructor(message: string, details?: unknown);
|
|
39
|
+
}
|
package/dist/core/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PresetExecutionError = exports.ValidationError = exports.UnsupportedFeatureError = exports.RelayerError = exports.UtxoNotFoundError = exports.ExecutionError = exports.ArtifactError = exports.CompilerError = exports.ToolchainError = exports.SimplicitySdkError = void 0;
|
|
3
|
+
exports.DefinitionVerificationError = exports.DefinitionError = exports.PresetExecutionError = exports.ValidationError = exports.UnsupportedFeatureError = exports.RelayerError = exports.UtxoNotFoundError = exports.ExecutionError = exports.ArtifactError = exports.CompilerError = exports.ToolchainError = exports.SimplicitySdkError = void 0;
|
|
4
4
|
class SimplicitySdkError extends Error {
|
|
5
5
|
code;
|
|
6
6
|
details;
|
|
@@ -68,3 +68,15 @@ class PresetExecutionError extends SimplicitySdkError {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
exports.PresetExecutionError = PresetExecutionError;
|
|
71
|
+
class DefinitionError extends SimplicitySdkError {
|
|
72
|
+
constructor(message, details) {
|
|
73
|
+
super("DEFINITION_ERROR", message, details);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.DefinitionError = DefinitionError;
|
|
77
|
+
class DefinitionVerificationError extends SimplicitySdkError {
|
|
78
|
+
constructor(message, details) {
|
|
79
|
+
super("DEFINITION_VERIFICATION_ERROR", message, details);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.DefinitionVerificationError = DefinitionVerificationError;
|
package/dist/core/executor.js
CHANGED
|
@@ -202,8 +202,17 @@ function buildPsetSummary(decoded, meta) {
|
|
|
202
202
|
return {
|
|
203
203
|
network: meta.network,
|
|
204
204
|
purpose: meta.purpose,
|
|
205
|
-
bondDefinitionId: meta.
|
|
205
|
+
bondDefinitionId: meta.definitionType === "bond"
|
|
206
|
+
? (meta.definitionId ?? meta.bondDefinitionId ?? null)
|
|
207
|
+
: (meta.bondDefinitionId ?? null),
|
|
206
208
|
periodId: meta.periodId ?? null,
|
|
209
|
+
definition: {
|
|
210
|
+
type: meta.definitionType ?? null,
|
|
211
|
+
id: meta.definitionId ?? null,
|
|
212
|
+
hash: meta.definitionHash ?? null,
|
|
213
|
+
trustMode: meta.definitionTrustMode ?? null,
|
|
214
|
+
anchorMode: meta.definitionAnchorMode ?? null,
|
|
215
|
+
},
|
|
207
216
|
contract: {
|
|
208
217
|
address: meta.contractAddress,
|
|
209
218
|
cmr: meta.cmr,
|
|
@@ -290,6 +299,11 @@ async function buildExecutionState(config, artifact, input) {
|
|
|
290
299
|
purpose: input.purpose ?? "sdk_execute",
|
|
291
300
|
bondDefinitionId: input.bondDefinitionId,
|
|
292
301
|
periodId: input.periodId,
|
|
302
|
+
definitionType: artifact.definition?.definitionType,
|
|
303
|
+
definitionId: artifact.definition?.definitionId,
|
|
304
|
+
definitionHash: artifact.definition?.hash,
|
|
305
|
+
definitionTrustMode: artifact.definition?.trustMode,
|
|
306
|
+
definitionAnchorMode: artifact.definition?.anchorMode,
|
|
293
307
|
expectedLiquidReceiver: input.expectedLiquidReceiver ?? recipientAddress,
|
|
294
308
|
contractAddress: artifact.compiled.contractAddress,
|
|
295
309
|
cmr: artifact.compiled.cmr,
|
|
@@ -440,6 +454,12 @@ async function executeGaslessContractCall(config, artifact, input) {
|
|
|
440
454
|
const summary = buildPsetSummary(decoded, {
|
|
441
455
|
network: artifact.network,
|
|
442
456
|
purpose: "sdk_gasless_execute",
|
|
457
|
+
bondDefinitionId: null,
|
|
458
|
+
definitionType: artifact.definition?.definitionType,
|
|
459
|
+
definitionId: artifact.definition?.definitionId,
|
|
460
|
+
definitionHash: artifact.definition?.hash,
|
|
461
|
+
definitionTrustMode: artifact.definition?.trustMode,
|
|
462
|
+
definitionAnchorMode: artifact.definition?.anchorMode,
|
|
443
463
|
contractAddress: artifact.compiled.contractAddress,
|
|
444
464
|
cmr: artifact.compiled.cmr,
|
|
445
465
|
internalKey: artifact.compiled.internalKey,
|
|
@@ -624,8 +644,15 @@ async function executeRelayedGaslessContractCall(config, artifact, input, relaye
|
|
|
624
644
|
summary: {
|
|
625
645
|
network: artifact.network,
|
|
626
646
|
purpose: "sdk_gasless_execute_relayer",
|
|
627
|
-
bondDefinitionId: null,
|
|
647
|
+
bondDefinitionId: artifact.definition?.definitionType === "bond" ? artifact.definition.definitionId : null,
|
|
628
648
|
periodId: null,
|
|
649
|
+
definition: {
|
|
650
|
+
type: artifact.definition?.definitionType ?? null,
|
|
651
|
+
id: artifact.definition?.definitionId ?? null,
|
|
652
|
+
hash: artifact.definition?.hash ?? null,
|
|
653
|
+
trustMode: artifact.definition?.trustMode ?? null,
|
|
654
|
+
anchorMode: artifact.definition?.anchorMode ?? null,
|
|
655
|
+
},
|
|
629
656
|
contract: {
|
|
630
657
|
address: request.detailedSummary.contract.contractAddress,
|
|
631
658
|
cmr: request.detailedSummary.contract.cmr,
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export type NetworkName = "liquidtestnet" | "liquidv1" | "regtest";
|
|
2
2
|
export type UtxoPolicy = "smallest_over" | "largest" | "newest";
|
|
3
|
+
export type DefinitionTrustMode = "hash-anchor";
|
|
4
|
+
export type DefinitionAnchorMode = "artifact-hash-anchor" | "on-chain-constant-committed";
|
|
3
5
|
export interface RpcConfig {
|
|
4
6
|
url: string;
|
|
5
7
|
username: string;
|
|
@@ -68,6 +70,7 @@ export interface SimplicityArtifact {
|
|
|
68
70
|
sdkVersion: string;
|
|
69
71
|
notes: string | null;
|
|
70
72
|
};
|
|
73
|
+
definition?: ArtifactDefinitionMetadata;
|
|
71
74
|
legacy?: {
|
|
72
75
|
simfTemplatePath?: string;
|
|
73
76
|
params?: {
|
|
@@ -81,11 +84,42 @@ export interface CompileFromFileInput {
|
|
|
81
84
|
simfPath: string;
|
|
82
85
|
templateVars?: Record<string, string | number>;
|
|
83
86
|
artifactPath?: string;
|
|
87
|
+
definition?: DefinitionInput;
|
|
84
88
|
}
|
|
85
89
|
export interface CompileFromPresetInput {
|
|
86
90
|
preset: string;
|
|
87
91
|
params: Record<string, string | number>;
|
|
88
92
|
artifactPath?: string;
|
|
93
|
+
definition?: DefinitionInput;
|
|
94
|
+
}
|
|
95
|
+
export interface DefinitionInput {
|
|
96
|
+
type: string;
|
|
97
|
+
id: string;
|
|
98
|
+
schemaVersion?: string;
|
|
99
|
+
jsonPath?: string;
|
|
100
|
+
value?: unknown;
|
|
101
|
+
anchorMode?: DefinitionAnchorMode;
|
|
102
|
+
}
|
|
103
|
+
export interface DefinitionDescriptor {
|
|
104
|
+
definitionType: string;
|
|
105
|
+
definitionId: string;
|
|
106
|
+
schemaVersion: string;
|
|
107
|
+
canonicalJson: string;
|
|
108
|
+
hash: string;
|
|
109
|
+
sourcePath?: string;
|
|
110
|
+
}
|
|
111
|
+
export interface ArtifactDefinitionMetadata {
|
|
112
|
+
definitionType: string;
|
|
113
|
+
definitionId: string;
|
|
114
|
+
schemaVersion: string;
|
|
115
|
+
hash: string;
|
|
116
|
+
trustMode: DefinitionTrustMode;
|
|
117
|
+
anchorMode: DefinitionAnchorMode;
|
|
118
|
+
onChainAnchor?: {
|
|
119
|
+
helper: "nonzero-eq_256";
|
|
120
|
+
templateVar: "DEFINITION_HASH";
|
|
121
|
+
sourceVerified: boolean;
|
|
122
|
+
};
|
|
89
123
|
}
|
|
90
124
|
export interface DeploymentInfo {
|
|
91
125
|
contractAddress: string;
|
|
@@ -151,6 +185,13 @@ export interface PsetSummary {
|
|
|
151
185
|
purpose?: string;
|
|
152
186
|
bondDefinitionId?: string | null;
|
|
153
187
|
periodId?: string | null;
|
|
188
|
+
definition?: {
|
|
189
|
+
type: string | null;
|
|
190
|
+
id: string | null;
|
|
191
|
+
hash: string | null;
|
|
192
|
+
trustMode: DefinitionTrustMode | null;
|
|
193
|
+
anchorMode: DefinitionAnchorMode | null;
|
|
194
|
+
};
|
|
154
195
|
contract: {
|
|
155
196
|
address: string;
|
|
156
197
|
cmr: string;
|
|
@@ -218,6 +259,18 @@ export interface GaslessExecuteResult {
|
|
|
218
259
|
amountSat: number;
|
|
219
260
|
};
|
|
220
261
|
}
|
|
262
|
+
export interface DefinitionVerificationResult {
|
|
263
|
+
ok: boolean;
|
|
264
|
+
reason?: string;
|
|
265
|
+
definition: DefinitionDescriptor;
|
|
266
|
+
artifactDefinition?: ArtifactDefinitionMetadata;
|
|
267
|
+
trust: {
|
|
268
|
+
artifactMatch: boolean;
|
|
269
|
+
onChainAnchorPresent: boolean;
|
|
270
|
+
onChainAnchorVerified: boolean;
|
|
271
|
+
effectiveMode: "none" | DefinitionAnchorMode;
|
|
272
|
+
};
|
|
273
|
+
}
|
|
221
274
|
export interface WaitForFundingInput {
|
|
222
275
|
minAmountSat?: number;
|
|
223
276
|
pollIntervalMs?: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,3 +6,4 @@ export * from "./core/types";
|
|
|
6
6
|
export * from "./core/errors";
|
|
7
7
|
export { listPresets, getPresetOrThrow } from "./core/presets";
|
|
8
8
|
export { loadArtifact, saveArtifact, normalizeArtifact } from "./core/artifact";
|
|
9
|
+
export { loadDefinitionInput, buildArtifactDefinitionMetadata, verifyDefinitionAgainstArtifact, verifyDefinitionDescriptorAgainstArtifact, } from "./core/definition";
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ 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.normalizeArtifact = exports.saveArtifact = exports.loadArtifact = exports.getPresetOrThrow = exports.listPresets = exports.RelayerClient = exports.DeployedContract = exports.CompiledContract = exports.SimplicityClient = exports.createSimplicityClient = void 0;
|
|
17
|
+
exports.verifyDefinitionDescriptorAgainstArtifact = exports.verifyDefinitionAgainstArtifact = exports.buildArtifactDefinitionMetadata = exports.loadDefinitionInput = exports.normalizeArtifact = exports.saveArtifact = exports.loadArtifact = exports.getPresetOrThrow = exports.listPresets = exports.RelayerClient = exports.DeployedContract = exports.CompiledContract = exports.SimplicityClient = exports.createSimplicityClient = void 0;
|
|
18
18
|
var SimplicityClient_1 = require("./client/SimplicityClient");
|
|
19
19
|
Object.defineProperty(exports, "createSimplicityClient", { enumerable: true, get: function () { return SimplicityClient_1.createSimplicityClient; } });
|
|
20
20
|
Object.defineProperty(exports, "SimplicityClient", { enumerable: true, get: function () { return SimplicityClient_1.SimplicityClient; } });
|
|
@@ -33,3 +33,8 @@ var artifact_1 = require("./core/artifact");
|
|
|
33
33
|
Object.defineProperty(exports, "loadArtifact", { enumerable: true, get: function () { return artifact_1.loadArtifact; } });
|
|
34
34
|
Object.defineProperty(exports, "saveArtifact", { enumerable: true, get: function () { return artifact_1.saveArtifact; } });
|
|
35
35
|
Object.defineProperty(exports, "normalizeArtifact", { enumerable: true, get: function () { return artifact_1.normalizeArtifact; } });
|
|
36
|
+
var definition_1 = require("./core/definition");
|
|
37
|
+
Object.defineProperty(exports, "loadDefinitionInput", { enumerable: true, get: function () { return definition_1.loadDefinitionInput; } });
|
|
38
|
+
Object.defineProperty(exports, "buildArtifactDefinitionMetadata", { enumerable: true, get: function () { return definition_1.buildArtifactDefinitionMetadata; } });
|
|
39
|
+
Object.defineProperty(exports, "verifyDefinitionAgainstArtifact", { enumerable: true, get: function () { return definition_1.verifyDefinitionAgainstArtifact; } });
|
|
40
|
+
Object.defineProperty(exports, "verifyDefinitionDescriptorAgainstArtifact", { enumerable: true, get: function () { return definition_1.verifyDefinitionDescriptorAgainstArtifact; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazbase/simplicity",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "An SDK for Simplicity on Liquid",
|
|
5
5
|
"author": "IndieSquare Inc <info@hazbase.com>",
|
|
6
6
|
"keywords": [
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"build": "tsc && node scripts/copy-presets.mjs",
|
|
37
37
|
"start": "node dist/cli.js",
|
|
38
38
|
"test": "npm run build && node --test dist/test",
|
|
39
|
-
"e2e:simplicity-relayer": "npm run build && node scripts/e2e-simplicity-relayer.mjs"
|
|
39
|
+
"e2e:simplicity-relayer": "npm run build && node scripts/e2e-simplicity-relayer.mjs",
|
|
40
|
+
"prepublishOnly": "npm run build"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@types/node": "^22.0.0",
|