@hazbase/simplicity 0.0.1 → 0.0.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 CHANGED
@@ -7,6 +7,22 @@ This SDK is designed to help Node developers get productive quickly, but it is s
7
7
  - This SDK currently optimizes for explicit / unblinded success paths first.
8
8
  - Gasless support exists, but it comes in multiple modes with different tradeoffs.
9
9
 
10
+ Consumer validation note:
11
+ - The published npm package has been validated from a fresh external Node.js project using `npm install @hazbase/simplicity`.
12
+ - Verified flows include preset-based contract execution, custom `.simf` execution, and relayer-backed gasless execution on `liquidtestnet`.
13
+
14
+ ## Validated Scenarios
15
+
16
+ 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).
17
+
18
+ | Scenario | Status | Notes |
19
+ | --- | --- | --- |
20
+ | Fresh install + JS import | Success | `npm install @hazbase/simplicity` and `import { createSimplicityClient } from "@hazbase/simplicity"` both worked |
21
+ | CLI smoke | Success | `npx simplicity-cli presets list` worked from the external project |
22
+ | Preset flow (`p2pkLockHeight`) | Success | compile -> fund -> inspect -> execute(`broadcast=true`) |
23
+ | Custom `.simf` flow | Success | `compileFromFile(...)` -> fund -> inspect -> execute(`broadcast=true`) |
24
+ | Relayer-backed gasless flow | Success | `executeGasless(...)` succeeded from the external project |
25
+
10
26
  ## Who This Is For
11
27
 
12
28
  This README is for you if:
@@ -45,6 +61,68 @@ With the current SDK you can build and test flows such as:
45
61
 
46
62
  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
63
 
64
+ ## Trusted Definition JSON
65
+
66
+ 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.
67
+
68
+ What that means:
69
+ - the SDK canonicalizes the JSON using stable key ordering,
70
+ - computes `sha256(canonicalJson)`,
71
+ - stores that hash in the artifact as a definition anchor,
72
+ - injects `DEFINITION_HASH` and `DEFINITION_ID` into compile-time template vars when a definition is provided,
73
+ - lets you verify later that the JSON you are reading still matches the contract/artifact it was compiled against.
74
+
75
+ Minimal TypeScript flow:
76
+
77
+ ```ts
78
+ const definition = await sdk.loadDefinition({
79
+ type: "bond",
80
+ id: "BOND-2026-001",
81
+ jsonPath: "./docs/definitions/bond-definition.json",
82
+ });
83
+
84
+ const compiled = await sdk.compileFromFile({
85
+ simfPath: "./docs/definitions/bond-anchor.simf",
86
+ templateVars: {
87
+ MIN_HEIGHT: 2344430,
88
+ SIGNER_XONLY: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
89
+ },
90
+ definition: {
91
+ type: definition.definitionType,
92
+ id: definition.definitionId,
93
+ schemaVersion: definition.schemaVersion,
94
+ jsonPath: definition.sourcePath,
95
+ },
96
+ artifactPath: "./bond.artifact.json",
97
+ });
98
+
99
+ const verification = await sdk.verifyDefinitionAgainstArtifact({
100
+ artifactPath: "./bond.artifact.json",
101
+ jsonPath: "./docs/definitions/bond-definition.json",
102
+ type: "bond",
103
+ id: "BOND-2026-001",
104
+ });
105
+
106
+ console.log(verification.ok);
107
+ ```
108
+
109
+ CLI equivalents:
110
+
111
+ ```bash
112
+ simplicity-cli definition show \
113
+ --type bond \
114
+ --id BOND-2026-001 \
115
+ --json-path ./docs/definitions/bond-definition.json
116
+
117
+ simplicity-cli definition verify \
118
+ --artifact ./bond.artifact.json \
119
+ --type bond \
120
+ --id BOND-2026-001 \
121
+ --json-path ./docs/definitions/bond-definition.json
122
+ ```
123
+
124
+ For a bond-oriented walkthrough, see [docs/definitions/README.md](./docs/definitions/README.md).
125
+
48
126
  ## Install
49
127
 
50
128
  You need three things:
@@ -467,6 +545,12 @@ Use custom `.simf` when:
467
545
  - you need your own parameterization,
468
546
  - you want to build app-specific wrappers on top of the generic SDK.
469
547
 
548
+ A real external-consumer validation of this path has been completed with:
549
+ - a fresh project created outside this repo,
550
+ - a local `contract.simf` file owned by that project,
551
+ - `compileFromFile(...)`,
552
+ - funding + inspect + `broadcast: true` execution.
553
+
470
554
  Recommended path:
471
555
  - learn the lifecycle with a preset first,
472
556
  - then move to `compileFromFile(...)` once the model is clear.
@@ -714,6 +798,15 @@ These examples are included to help you jump to the right workflow quickly.
714
798
  - [execute-htlc.ts](./examples/execute-htlc.ts): HTLC preset with custom witness values.
715
799
  - [execute-transfer-with-timeout-cooperative.ts](./examples/execute-transfer-with-timeout-cooperative.ts): cooperative multi-witness timeout flow.
716
800
  - [gasless-transfer.ts](./examples/gasless-transfer.ts): standard relayer-backed gasless L-BTC transfer.
801
+ - [define-bond.ts](./examples/define-bond.ts): compile a bond example with a trusted definition hash anchor.
802
+ - [show-bond-definition.ts](./examples/show-bond-definition.ts): verify and retrieve a trusted bond definition from JSON + artifact.
803
+
804
+ In addition to the in-repo examples, the package has also been validated from a blank external consumer project with:
805
+ - `npm install @hazbase/simplicity`
806
+ - JS/TS import of `createSimplicityClient`
807
+ - preset compile -> fund -> inspect -> execute
808
+ - custom `.simf` compile -> fund -> inspect -> execute
809
+ - relayer-backed gasless execution
717
810
 
718
811
  ## FAQ / Practical Notes
719
812
 
@@ -729,6 +822,15 @@ It means: compile the contract, derive its address, then fund that address with
729
822
 
730
823
  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
824
 
825
+ When you compile with `definition: { ... }`, the artifact also carries:
826
+ - `definitionType`
827
+ - `definitionId`
828
+ - `schemaVersion`
829
+ - `hash`
830
+ - `trustMode`
831
+
832
+ That is what allows the SDK and CLI to verify that an off-chain JSON definition still matches the contract you compiled.
833
+
732
834
  ### When should I use a preset instead of a custom `.simf` file?
733
835
 
734
836
  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,23 @@ 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
+ if (!type && !id && !jsonPath && !valueJson && !schemaVersion) {
76
+ return undefined;
77
+ }
78
+ return {
79
+ type: type ?? "",
80
+ id: id ?? "",
81
+ schemaVersion: schemaVersion ?? undefined,
82
+ jsonPath,
83
+ value: valueJson ? JSON.parse(valueJson) : undefined,
84
+ };
85
+ }
68
86
  function resolveConfig() {
69
87
  return {
70
88
  network: getArg("network", "liquidtestnet"),
@@ -357,6 +375,15 @@ function formatArtifactHelp(artifact, preset, utxos) {
357
375
  ` status: ${status}`,
358
376
  ` ready: ${ready ? "yes" : "no"}`,
359
377
  ].join("\n");
378
+ const definition = artifact.definition
379
+ ? [
380
+ ` type: ${artifact.definition.definitionType}`,
381
+ ` id: ${artifact.definition.definitionId}`,
382
+ ` schema version: ${artifact.definition.schemaVersion}`,
383
+ ` hash: ${artifact.definition.hash}`,
384
+ ` trust mode: ${artifact.definition.trustMode}`,
385
+ ].join("\n")
386
+ : " (none)";
360
387
  const compileSource = artifact.source.simfPath ?? artifact.legacy?.simfTemplatePath ?? "(unknown)";
361
388
  const templateVars = artifact.source.templateVars ?? {};
362
389
  const inspectCommand = `simplicity-cli contract inspect --artifact ./artifact.json --wallet simplicity-test --privkey <privkey-hex> --to-address tex1...`;
@@ -375,6 +402,9 @@ function formatArtifactHelp(artifact, preset, utxos) {
375
402
  "Template Vars:",
376
403
  indent(JSON.stringify(templateVars, null, 2)),
377
404
  "",
405
+ "Definition Anchor:",
406
+ definition,
407
+ "",
378
408
  "Suggested Commands:",
379
409
  ` ${inspectCommand}`,
380
410
  ` ${executeCommand}`,
@@ -416,17 +446,48 @@ async function main() {
416
446
  const subcommand = process.argv[3];
417
447
  const sdk = (0, SimplicityClient_1.createSimplicityClient)(resolveConfig());
418
448
  if (!command) {
419
- throw new Error("Usage: simplicity-cli <compile|presets|preset|contract|artifact|gasless> ...");
449
+ throw new Error("Usage: simplicity-cli <compile|presets|preset|contract|artifact|definition|gasless> ...");
420
450
  }
421
451
  if (command === "compile") {
422
452
  const result = await sdk.compileFromFile({
423
453
  simfPath: requireArg("simf"),
424
454
  templateVars: parseAssignments(getMultiArgs("template-var")),
425
455
  artifactPath: getArg("artifact"),
456
+ definition: parseDefinitionInput(),
426
457
  });
427
458
  printJson({ artifact: result.artifact, deployment: result.deployment() });
428
459
  return;
429
460
  }
461
+ if (command === "definition" && subcommand === "show") {
462
+ const definition = await (0, definition_1.loadDefinitionInput)({
463
+ type: requireArg("type"),
464
+ id: requireArg("id"),
465
+ jsonPath: getArg("json-path"),
466
+ value: getArg("value") ? JSON.parse(getArg("value")) : undefined,
467
+ schemaVersion: getArg("schema-version"),
468
+ });
469
+ printJson(definition);
470
+ return;
471
+ }
472
+ if (command === "definition" && subcommand === "verify") {
473
+ const verification = await sdk.verifyDefinitionAgainstArtifact({
474
+ artifactPath: requireArg("artifact"),
475
+ type: getArg("type"),
476
+ id: getArg("id"),
477
+ expectedType: getArg("expected-type"),
478
+ expectedId: getArg("expected-id"),
479
+ jsonPath: getArg("json-path"),
480
+ value: getArg("value") ? JSON.parse(getArg("value")) : undefined,
481
+ schemaVersion: getArg("schema-version"),
482
+ });
483
+ printJson({
484
+ verified: verification.ok,
485
+ reason: verification.reason,
486
+ definition: verification.definition,
487
+ artifactDefinition: verification.artifactDefinition ?? null,
488
+ });
489
+ return;
490
+ }
430
491
  if (command === "presets" && subcommand === "list") {
431
492
  printJson((0, presets_1.listPresets)().map((preset) => (0, presets_1.describePreset)(preset)));
432
493
  return;
@@ -472,6 +533,7 @@ async function main() {
472
533
  preset: requireArg("preset"),
473
534
  params,
474
535
  artifactPath: getArg("artifact"),
536
+ definition: parseDefinitionInput(),
475
537
  });
476
538
  printJson({ artifact: result.artifact, deployment: result.deployment() });
477
539
  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;
@@ -19,6 +19,9 @@ class CompiledContract {
19
19
  get program() {
20
20
  return this.artifact.compiled.program;
21
21
  }
22
+ definition() {
23
+ return this.artifact.definition ?? null;
24
+ }
22
25
  deployment() {
23
26
  return {
24
27
  contractAddress: this.artifact.compiled.contractAddress,
@@ -1,4 +1,5 @@
1
- import { ContractUtxo, ExecuteCallInput, ExecuteResult, GaslessExecuteInput, GaslessExecuteResult, InspectCallInput, InspectResult, SimplicityArtifact, SimplicityClientConfig, WaitForFundingInput } from "../core/types";
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,16 @@ 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
+ }>;
12
25
  }
@@ -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,25 @@ 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
+ };
62
+ }
42
63
  }
43
64
  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
  }
@@ -55,6 +55,7 @@ function normalizeArtifact(artifact, networkDefault = "liquidtestnet") {
55
55
  sdkVersion: exports.SDK_PACKAGE_VERSION,
56
56
  notes: null,
57
57
  },
58
+ definition: undefined,
58
59
  legacy: {
59
60
  simfTemplatePath: artifact.simfTemplatePath,
60
61
  params: artifact.params,
@@ -90,6 +91,7 @@ async function loadArtifact(artifactPath, networkDefault = "liquidtestnet") {
90
91
  simfTemplatePath: legacySimfTemplatePath,
91
92
  }
92
93
  : undefined,
94
+ definition: normalized.definition,
93
95
  };
94
96
  }
95
97
  async function saveArtifact(artifactPath, artifact) {
@@ -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,7 @@ async function buildArtifact(input) {
67
68
  sdkVersion: artifact_1.SDK_PACKAGE_VERSION,
68
69
  notes: null,
69
70
  },
71
+ definition: input.definition ? (0, definition_1.buildArtifactDefinitionMetadata)(input.definition) : undefined,
70
72
  legacy: {
71
73
  simfTemplatePath: input.sourceSimfPath,
72
74
  params: {
@@ -77,8 +79,14 @@ async function buildArtifact(input) {
77
79
  }, input.config.network);
78
80
  }
79
81
  async function compileFromFile(config, input) {
82
+ const definition = input.definition ? await (0, definition_1.loadDefinitionInput)(input.definition) : undefined;
80
83
  const rawSource = await (0, promises_1.readFile)(input.simfPath, "utf8");
81
- const rendered = (0, templating_1.renderTemplate)(rawSource, input.templateVars ?? {});
84
+ const templateVars = {
85
+ ...(input.templateVars ?? {}),
86
+ ...(definition && input.templateVars?.DEFINITION_HASH === undefined ? { DEFINITION_HASH: definition.hash } : {}),
87
+ ...(definition && input.templateVars?.DEFINITION_ID === undefined ? { DEFINITION_ID: definition.definitionId } : {}),
88
+ };
89
+ const rendered = (0, templating_1.renderTemplate)(rawSource, templateVars);
82
90
  const workDir = await (0, promises_1.mkdtemp)(node_path_1.default.join((0, node_os_1.tmpdir)(), "simplicity-sdk-compile-"));
83
91
  const renderedPath = node_path_1.default.join(workDir, node_path_1.default.basename(input.simfPath));
84
92
  await (0, promises_1.writeFile)(renderedPath, rendered, "utf8");
@@ -87,7 +95,8 @@ async function compileFromFile(config, input) {
87
95
  renderedSimfPath: renderedPath,
88
96
  sourceMode: "file",
89
97
  sourceSimfPath: input.simfPath,
90
- templateVars: input.templateVars,
98
+ templateVars,
99
+ definition,
91
100
  });
92
101
  if (input.artifactPath) {
93
102
  await (0, artifact_1.saveArtifact)(input.artifactPath, artifact);
@@ -96,7 +105,12 @@ async function compileFromFile(config, input) {
96
105
  }
97
106
  async function compileFromPreset(config, input) {
98
107
  const preset = (0, presets_1.getPresetOrThrow)(input.preset);
99
- const params = (0, presets_1.validatePresetParams)(preset, input.params);
108
+ const definition = input.definition ? await (0, definition_1.loadDefinitionInput)(input.definition) : undefined;
109
+ const params = {
110
+ ...(0, presets_1.validatePresetParams)(preset, input.params),
111
+ ...(definition && input.params.DEFINITION_HASH === undefined ? { DEFINITION_HASH: definition.hash } : {}),
112
+ ...(definition && input.params.DEFINITION_ID === undefined ? { DEFINITION_ID: definition.definitionId } : {}),
113
+ };
100
114
  const rawSource = await (0, promises_1.readFile)(preset.simfTemplatePath, "utf8");
101
115
  const rendered = (0, templating_1.renderTemplate)(rawSource, params);
102
116
  const workDir = await (0, promises_1.mkdtemp)(node_path_1.default.join((0, node_os_1.tmpdir)(), "simplicity-sdk-preset-"));
@@ -109,6 +123,7 @@ async function compileFromPreset(config, input) {
109
123
  sourceSimfPath: preset.simfTemplatePath,
110
124
  preset: preset.id,
111
125
  templateVars: params,
126
+ definition,
112
127
  });
113
128
  if (input.artifactPath) {
114
129
  await (0, artifact_1.saveArtifact)(input.artifactPath, artifact);
@@ -0,0 +1,10 @@
1
+ import { ArtifactDefinitionMetadata, DefinitionDescriptor, DefinitionInput, DefinitionVerificationResult, SimplicityArtifact } from "./types";
2
+ export declare function loadDefinitionInput(input: DefinitionInput): Promise<DefinitionDescriptor>;
3
+ export declare function buildArtifactDefinitionMetadata(definition: DefinitionDescriptor): ArtifactDefinitionMetadata;
4
+ export declare function verifyDefinitionDescriptorAgainstArtifact(definition: DefinitionDescriptor, artifactDefinition?: ArtifactDefinitionMetadata, expectedType?: string, expectedId?: string): DefinitionVerificationResult;
5
+ export declare function verifyDefinitionAgainstArtifact(input: {
6
+ artifact: SimplicityArtifact;
7
+ definition: DefinitionInput;
8
+ expectedType?: string;
9
+ expectedId?: string;
10
+ }): Promise<DefinitionVerificationResult>;
@@ -0,0 +1,150 @@
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.buildArtifactDefinitionMetadata = buildArtifactDefinitionMetadata;
8
+ exports.verifyDefinitionDescriptorAgainstArtifact = verifyDefinitionDescriptorAgainstArtifact;
9
+ exports.verifyDefinitionAgainstArtifact = verifyDefinitionAgainstArtifact;
10
+ const promises_1 = require("node:fs/promises");
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const errors_1 = require("./errors");
13
+ const summary_1 = require("./summary");
14
+ const DEFAULT_SCHEMA_VERSION = "1";
15
+ function assertNonEmpty(value, fieldName) {
16
+ if (!value || value.trim().length === 0) {
17
+ throw new errors_1.DefinitionError(`${fieldName} must not be empty`);
18
+ }
19
+ return value;
20
+ }
21
+ function ensureSerializable(value, seen = new WeakSet()) {
22
+ if (value === undefined) {
23
+ throw new errors_1.DefinitionError("Definition JSON must not contain undefined values");
24
+ }
25
+ if (value === null)
26
+ return;
27
+ if (typeof value === "bigint") {
28
+ throw new errors_1.DefinitionError("Definition JSON must not contain bigint values");
29
+ }
30
+ if (value instanceof Date) {
31
+ throw new errors_1.DefinitionError("Definition JSON must not contain Date objects; normalize them first");
32
+ }
33
+ if (Array.isArray(value)) {
34
+ for (const entry of value)
35
+ ensureSerializable(entry, seen);
36
+ return;
37
+ }
38
+ if (typeof value === "object") {
39
+ const objectValue = value;
40
+ if (seen.has(objectValue)) {
41
+ throw new errors_1.DefinitionError("Definition JSON must not contain circular references");
42
+ }
43
+ seen.add(objectValue);
44
+ for (const entry of Object.values(objectValue))
45
+ ensureSerializable(entry, seen);
46
+ }
47
+ }
48
+ async function resolveDefinitionValue(input) {
49
+ if ((input.jsonPath ? 1 : 0) + (input.value !== undefined ? 1 : 0) !== 1) {
50
+ throw new errors_1.DefinitionError("Exactly one of jsonPath or value must be provided");
51
+ }
52
+ if (input.jsonPath) {
53
+ const sourcePath = node_path_1.default.resolve(input.jsonPath);
54
+ const raw = await (0, promises_1.readFile)(sourcePath, "utf8");
55
+ try {
56
+ return { value: JSON.parse(raw), sourcePath };
57
+ }
58
+ catch (error) {
59
+ throw new errors_1.DefinitionError(`Failed to parse definition JSON at ${sourcePath}`, error);
60
+ }
61
+ }
62
+ return { value: input.value };
63
+ }
64
+ async function loadDefinitionInput(input) {
65
+ const definitionType = assertNonEmpty(input.type, "definition.type");
66
+ const definitionId = assertNonEmpty(input.id, "definition.id");
67
+ const schemaVersion = assertNonEmpty(input.schemaVersion ?? DEFAULT_SCHEMA_VERSION, "definition.schemaVersion");
68
+ const { value, sourcePath } = await resolveDefinitionValue(input);
69
+ ensureSerializable(value);
70
+ const canonicalJson = (0, summary_1.stableStringify)(value);
71
+ return {
72
+ definitionType,
73
+ definitionId,
74
+ schemaVersion,
75
+ canonicalJson,
76
+ hash: (0, summary_1.sha256HexUtf8)(canonicalJson),
77
+ sourcePath,
78
+ };
79
+ }
80
+ function buildArtifactDefinitionMetadata(definition) {
81
+ return {
82
+ definitionType: definition.definitionType,
83
+ definitionId: definition.definitionId,
84
+ schemaVersion: definition.schemaVersion,
85
+ hash: definition.hash,
86
+ trustMode: "hash-anchor",
87
+ };
88
+ }
89
+ function verifyDefinitionDescriptorAgainstArtifact(definition, artifactDefinition, expectedType, expectedId) {
90
+ if (expectedType && expectedType !== definition.definitionType) {
91
+ return {
92
+ ok: false,
93
+ reason: `Definition type mismatch: expected=${expectedType} actual=${definition.definitionType}`,
94
+ definition,
95
+ artifactDefinition,
96
+ };
97
+ }
98
+ if (expectedId && expectedId !== definition.definitionId) {
99
+ return {
100
+ ok: false,
101
+ reason: `Definition id mismatch: expected=${expectedId} actual=${definition.definitionId}`,
102
+ definition,
103
+ artifactDefinition,
104
+ };
105
+ }
106
+ if (!artifactDefinition) {
107
+ return {
108
+ ok: false,
109
+ reason: "Artifact does not contain definition metadata",
110
+ definition,
111
+ };
112
+ }
113
+ if (artifactDefinition.definitionType !== definition.definitionType) {
114
+ return {
115
+ ok: false,
116
+ reason: `Definition type mismatch: artifact=${artifactDefinition.definitionType} actual=${definition.definitionType}`,
117
+ definition,
118
+ artifactDefinition,
119
+ };
120
+ }
121
+ if (artifactDefinition.definitionId !== definition.definitionId) {
122
+ return {
123
+ ok: false,
124
+ reason: `Definition id mismatch: artifact=${artifactDefinition.definitionId} actual=${definition.definitionId}`,
125
+ definition,
126
+ artifactDefinition,
127
+ };
128
+ }
129
+ if (artifactDefinition.schemaVersion !== definition.schemaVersion) {
130
+ return {
131
+ ok: false,
132
+ reason: `Definition schemaVersion mismatch: artifact=${artifactDefinition.schemaVersion} actual=${definition.schemaVersion}`,
133
+ definition,
134
+ artifactDefinition,
135
+ };
136
+ }
137
+ if (artifactDefinition.hash !== definition.hash) {
138
+ return {
139
+ ok: false,
140
+ reason: `Definition hash mismatch: artifact=${artifactDefinition.hash} actual=${definition.hash}`,
141
+ definition,
142
+ artifactDefinition,
143
+ };
144
+ }
145
+ return { ok: true, definition, artifactDefinition };
146
+ }
147
+ async function verifyDefinitionAgainstArtifact(input) {
148
+ const definition = await loadDefinitionInput(input.definition);
149
+ return verifyDefinitionDescriptorAgainstArtifact(definition, input.artifact.definition, input.expectedType, input.expectedId);
150
+ }
@@ -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
+ }
@@ -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;
@@ -202,8 +202,16 @@ function buildPsetSummary(decoded, meta) {
202
202
  return {
203
203
  network: meta.network,
204
204
  purpose: meta.purpose,
205
- bondDefinitionId: meta.bondDefinitionId ?? null,
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
+ },
207
215
  contract: {
208
216
  address: meta.contractAddress,
209
217
  cmr: meta.cmr,
@@ -290,6 +298,10 @@ async function buildExecutionState(config, artifact, input) {
290
298
  purpose: input.purpose ?? "sdk_execute",
291
299
  bondDefinitionId: input.bondDefinitionId,
292
300
  periodId: input.periodId,
301
+ definitionType: artifact.definition?.definitionType,
302
+ definitionId: artifact.definition?.definitionId,
303
+ definitionHash: artifact.definition?.hash,
304
+ definitionTrustMode: artifact.definition?.trustMode,
293
305
  expectedLiquidReceiver: input.expectedLiquidReceiver ?? recipientAddress,
294
306
  contractAddress: artifact.compiled.contractAddress,
295
307
  cmr: artifact.compiled.cmr,
@@ -440,6 +452,11 @@ async function executeGaslessContractCall(config, artifact, input) {
440
452
  const summary = buildPsetSummary(decoded, {
441
453
  network: artifact.network,
442
454
  purpose: "sdk_gasless_execute",
455
+ bondDefinitionId: null,
456
+ definitionType: artifact.definition?.definitionType,
457
+ definitionId: artifact.definition?.definitionId,
458
+ definitionHash: artifact.definition?.hash,
459
+ definitionTrustMode: artifact.definition?.trustMode,
443
460
  contractAddress: artifact.compiled.contractAddress,
444
461
  cmr: artifact.compiled.cmr,
445
462
  internalKey: artifact.compiled.internalKey,
@@ -624,8 +641,14 @@ async function executeRelayedGaslessContractCall(config, artifact, input, relaye
624
641
  summary: {
625
642
  network: artifact.network,
626
643
  purpose: "sdk_gasless_execute_relayer",
627
- bondDefinitionId: null,
644
+ bondDefinitionId: artifact.definition?.definitionType === "bond" ? artifact.definition.definitionId : null,
628
645
  periodId: null,
646
+ definition: {
647
+ type: artifact.definition?.definitionType ?? null,
648
+ id: artifact.definition?.definitionId ?? null,
649
+ hash: artifact.definition?.hash ?? null,
650
+ trustMode: artifact.definition?.trustMode ?? null,
651
+ },
629
652
  contract: {
630
653
  address: request.detailedSummary.contract.contractAddress,
631
654
  cmr: request.detailedSummary.contract.cmr,
@@ -1,5 +1,6 @@
1
1
  export type NetworkName = "liquidtestnet" | "liquidv1" | "regtest";
2
2
  export type UtxoPolicy = "smallest_over" | "largest" | "newest";
3
+ export type DefinitionTrustMode = "hash-anchor";
3
4
  export interface RpcConfig {
4
5
  url: string;
5
6
  username: string;
@@ -68,6 +69,7 @@ export interface SimplicityArtifact {
68
69
  sdkVersion: string;
69
70
  notes: string | null;
70
71
  };
72
+ definition?: ArtifactDefinitionMetadata;
71
73
  legacy?: {
72
74
  simfTemplatePath?: string;
73
75
  params?: {
@@ -81,11 +83,35 @@ export interface CompileFromFileInput {
81
83
  simfPath: string;
82
84
  templateVars?: Record<string, string | number>;
83
85
  artifactPath?: string;
86
+ definition?: DefinitionInput;
84
87
  }
85
88
  export interface CompileFromPresetInput {
86
89
  preset: string;
87
90
  params: Record<string, string | number>;
88
91
  artifactPath?: string;
92
+ definition?: DefinitionInput;
93
+ }
94
+ export interface DefinitionInput {
95
+ type: string;
96
+ id: string;
97
+ schemaVersion?: string;
98
+ jsonPath?: string;
99
+ value?: unknown;
100
+ }
101
+ export interface DefinitionDescriptor {
102
+ definitionType: string;
103
+ definitionId: string;
104
+ schemaVersion: string;
105
+ canonicalJson: string;
106
+ hash: string;
107
+ sourcePath?: string;
108
+ }
109
+ export interface ArtifactDefinitionMetadata {
110
+ definitionType: string;
111
+ definitionId: string;
112
+ schemaVersion: string;
113
+ hash: string;
114
+ trustMode: DefinitionTrustMode;
89
115
  }
90
116
  export interface DeploymentInfo {
91
117
  contractAddress: string;
@@ -151,6 +177,12 @@ export interface PsetSummary {
151
177
  purpose?: string;
152
178
  bondDefinitionId?: string | null;
153
179
  periodId?: string | null;
180
+ definition?: {
181
+ type: string | null;
182
+ id: string | null;
183
+ hash: string | null;
184
+ trustMode: DefinitionTrustMode | null;
185
+ };
154
186
  contract: {
155
187
  address: string;
156
188
  cmr: string;
@@ -218,6 +250,12 @@ export interface GaslessExecuteResult {
218
250
  amountSat: number;
219
251
  };
220
252
  }
253
+ export interface DefinitionVerificationResult {
254
+ ok: boolean;
255
+ reason?: string;
256
+ definition: DefinitionDescriptor;
257
+ artifactDefinition?: ArtifactDefinitionMetadata;
258
+ }
221
259
  export interface WaitForFundingInput {
222
260
  minAmountSat?: number;
223
261
  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.1",
3
+ "version": "0.0.2",
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",