@ottochain/sdk 2.5.0 → 2.7.0

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.
Files changed (34) hide show
  1. package/dist/cjs/apps/lending/assets.js +11 -1
  2. package/dist/cjs/index.js +9 -2
  3. package/dist/cjs/ottochain/index.js +11 -1
  4. package/dist/cjs/ottochain/metagraph-client.js +47 -3
  5. package/dist/cjs/ottochain/morphism-lint.js +99 -0
  6. package/dist/cjs/ottochain/state-proof.js +118 -0
  7. package/dist/cjs/ottochain/transaction.js +7 -1
  8. package/dist/cjs/ottochain/webhook-notifications.js +33 -0
  9. package/dist/cjs/schema/effects.js +43 -6
  10. package/dist/cjs/schema/fiber-app.js +7 -2
  11. package/dist/cjs/schema/guard-lint.js +102 -0
  12. package/dist/esm/apps/lending/assets.js +11 -1
  13. package/dist/esm/index.js +4 -1
  14. package/dist/esm/ottochain/index.js +4 -0
  15. package/dist/esm/ottochain/metagraph-client.js +47 -3
  16. package/dist/esm/ottochain/morphism-lint.js +95 -0
  17. package/dist/esm/ottochain/state-proof.js +113 -0
  18. package/dist/esm/ottochain/transaction.js +7 -1
  19. package/dist/esm/ottochain/webhook-notifications.js +30 -0
  20. package/dist/esm/schema/effects.js +41 -5
  21. package/dist/esm/schema/fiber-app.js +7 -2
  22. package/dist/esm/schema/guard-lint.js +102 -0
  23. package/dist/types/apps/lending/assets.d.ts +11 -1
  24. package/dist/types/index.d.ts +5 -1
  25. package/dist/types/ottochain/index.d.ts +7 -1
  26. package/dist/types/ottochain/metagraph-client.d.ts +69 -15
  27. package/dist/types/ottochain/morphism-lint.d.ts +56 -0
  28. package/dist/types/ottochain/state-proof.d.ts +51 -0
  29. package/dist/types/ottochain/transaction.d.ts +7 -1
  30. package/dist/types/ottochain/types.d.ts +22 -0
  31. package/dist/types/ottochain/webhook-notifications.d.ts +91 -0
  32. package/dist/types/schema/effects.d.ts +39 -5
  33. package/dist/types/schema/guard-lint.d.ts +6 -0
  34. package/package.json +6 -6
@@ -112,6 +112,11 @@ function createMintAssetPayload(params) {
112
112
  /**
113
113
  * Apply a typed morphism (Transfer / Burn / Compose / ...). asset-model.md §7.
114
114
  * Transfer = `{ kind: "Transfer", recipient }`; Burn (repay) = `{ kind: "Burn" }`.
115
+ *
116
+ * C2 — a `Compose`/`Pool` that folds in a counter-party the signer does NOT own is now rejected on-chain
117
+ * unless a live `AuthorizeCompose` nonce authorizes it: pass that nonce here (`nonce`) and build the commit
118
+ * half with {@link createAuthorizeComposePayload}. A same-holder compose (all parts signer-owned) needs no
119
+ * nonce. `otherAssets` must be duplicate-free and must not include `assetId`.
115
120
  */
116
121
  function createApplyMorphismPayload(params) {
117
122
  return {
@@ -128,7 +133,12 @@ function createApplyMorphismPayload(params) {
128
133
  },
129
134
  };
130
135
  }
131
- /** Commit half of a two-party compose. asset-model.md §7/§8. */
136
+ /**
137
+ * Commit half of a two-party (cross-holder) compose. asset-model.md §7/§8. As of chain finding C2 this
138
+ * handshake is MANDATORY: a cross-holder `Compose`/`Pool` is rejected unless a matching, unexpired nonce
139
+ * from this call is live for the counter-party. The composing party then echoes `nonce` in its
140
+ * `ApplyMorphism`. A same-holder compose (all parts owned by the signer) does not need this.
141
+ */
132
142
  function createAuthorizeComposePayload(params) {
133
143
  return {
134
144
  AuthorizeCompose: {
package/dist/cjs/index.js CHANGED
@@ -31,8 +31,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
31
31
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
32
32
  };
33
33
  Object.defineProperty(exports, "__esModule", { value: true });
34
- exports.IMMUTABLE_POLICY = exports.immutable = exports.unconstrained = exports.constrained = exports.defineFiberApp = exports.toProtoDefinition = exports.OttoMetagraphClient = exports.assert = exports.safeParse = exports.validateKeyPair = exports.validateAddress = exports.validatePublicKey = exports.validatePrivateKey = exports.validate = exports.CompleteContractRequestSchema = exports.AcceptContractRequestSchema = exports.ProposeContractRequestSchema = exports.ContractTermsSchema = exports.PlatformLinkSchema = exports.AgentIdentityRegistrationSchema = exports.TransferParamsSchema = exports.CurrencyTransactionSchema = exports.CurrencyTransactionValueSchema = exports.TransactionReferenceSchema = exports.SignedSchema = exports.SignatureProofSchema = exports.KeyPairSchema = exports.PublicKeySchema = exports.PrivateKeySchema = exports.DagAddressSchema = exports.wrapError = exports.isErrorCode = exports.ErrorCode = exports.TransactionError = exports.SigningError = exports.ValidationError = exports.NetworkError = exports.OttoChainError = exports.GENESIS_MANIFEST_VERSION = exports.buildGenesisManifest = exports.dropNulls = exports.MetagraphNetworkError = exports.HttpClient = exports.createMetagraphClient = exports.MetagraphClient = exports.batchSign = exports.addSignature = exports.createSignedObject = exports.signDataUpdate = exports.verify = void 0;
35
- exports.SHIELDED_POOL_STATE = exports.shieldApp = exports.RESERVED_EFFECT_KEYS = exports.toWallet = exports.toFiber = exports.emit = exports.spawn = exports.triggers = exports.transferAsset = exports.setDependencyActive = exports.addDependency = exports.actorNotInArray = exports.depInState = exports.signerHasRoleVia = exports.signerHasReputationVia = exports.signerHasRole = exports.signerHasReputation = exports.actorHasEntry = exports.actorInSet = exports.actorIsSigner = exports.assetSignerIs = exports.signerHasEntry = exports.signerIsNotParty = exports.signerInSet = exports.signerIsAnyParty = exports.signerIsParty = exports.projectFiberPolicy = void 0;
34
+ exports.constrained = exports.defineFiberApp = exports.toProtoDefinition = exports.SNAPSHOT_FINALIZED_EVENT = exports.OttoMetagraphClient = exports.assert = exports.safeParse = exports.validateKeyPair = exports.validateAddress = exports.validatePublicKey = exports.validatePrivateKey = exports.validate = exports.CompleteContractRequestSchema = exports.AcceptContractRequestSchema = exports.ProposeContractRequestSchema = exports.ContractTermsSchema = exports.PlatformLinkSchema = exports.AgentIdentityRegistrationSchema = exports.TransferParamsSchema = exports.CurrencyTransactionSchema = exports.CurrencyTransactionValueSchema = exports.TransactionReferenceSchema = exports.SignedSchema = exports.SignatureProofSchema = exports.KeyPairSchema = exports.PublicKeySchema = exports.PrivateKeySchema = exports.DagAddressSchema = exports.wrapError = exports.isErrorCode = exports.ErrorCode = exports.TransactionError = exports.SigningError = exports.ValidationError = exports.NetworkError = exports.OttoChainError = exports.GENESIS_MANIFEST_VERSION = exports.buildGenesisManifest = exports.MORPHISM_LINT_CODES = exports.lintApplyMorphism = exports.dropNulls = exports.MetagraphNetworkError = exports.HttpClient = exports.createMetagraphClient = exports.MetagraphClient = exports.batchSign = exports.addSignature = exports.createSignedObject = exports.signDataUpdate = exports.verify = void 0;
35
+ exports.SHIELDED_POOL_STATE = exports.shieldApp = exports.RESERVED_EFFECT_KEYS = exports.toWallet = exports.toFiber = exports.scriptCall = exports.emit = exports.spawn = exports.triggers = exports.transferAsset = exports.setDependencyActive = exports.addDependency = exports.actorNotInArray = exports.depInState = exports.signerHasRoleVia = exports.signerHasReputationVia = exports.signerHasRole = exports.signerHasReputation = exports.actorHasEntry = exports.actorInSet = exports.actorIsSigner = exports.assetSignerIs = exports.signerHasEntry = exports.signerIsNotParty = exports.signerInSet = exports.signerIsAnyParty = exports.signerIsParty = exports.projectFiberPolicy = exports.IMMUTABLE_POLICY = exports.immutable = exports.unconstrained = void 0;
36
36
  // ─── Core metagraph SDK ───────────────────────────────────────────────────────
37
37
  // Consumers can also import directly from '@constellation-network/metagraph-sdk'
38
38
  __exportStar(require("@constellation-network/metagraph-sdk"), exports);
@@ -62,6 +62,10 @@ __exportStar(require("./types.js"), exports);
62
62
  __exportStar(require("./ottochain/transaction.js"), exports);
63
63
  var drop_nulls_js_1 = require("./ottochain/drop-nulls.js");
64
64
  Object.defineProperty(exports, "dropNulls", { enumerable: true, get: function () { return drop_nulls_js_1.dropNulls; } });
65
+ // Message-layer advisory lint for ApplyMorphism (Compose/Pool consent + inflation checks).
66
+ var morphism_lint_js_1 = require("./ottochain/morphism-lint.js");
67
+ Object.defineProperty(exports, "lintApplyMorphism", { enumerable: true, get: function () { return morphism_lint_js_1.lintApplyMorphism; } });
68
+ Object.defineProperty(exports, "MORPHISM_LINT_CODES", { enumerable: true, get: function () { return morphism_lint_js_1.MORPHISM_LINT_CODES; } });
65
69
  // ─── Genesis manifest exporter (std-app pre-registration content) ─────────────
66
70
  var genesis_manifest_js_1 = require("./ottochain/genesis-manifest.js");
67
71
  Object.defineProperty(exports, "buildGenesisManifest", { enumerable: true, get: function () { return genesis_manifest_js_1.buildGenesisManifest; } });
@@ -108,6 +112,8 @@ Object.defineProperty(exports, "assert", { enumerable: true, get: function () {
108
112
  // ─── OttoChain metagraph client ───────────────────────────────────────────────
109
113
  var metagraph_client_js_1 = require("./ottochain/metagraph-client.js");
110
114
  Object.defineProperty(exports, "OttoMetagraphClient", { enumerable: true, get: function () { return metagraph_client_js_1.MetagraphClient; } });
115
+ var webhook_notifications_js_1 = require("./ottochain/webhook-notifications.js");
116
+ Object.defineProperty(exports, "SNAPSHOT_FINALIZED_EVENT", { enumerable: true, get: function () { return webhook_notifications_js_1.SNAPSHOT_FINALIZED_EVENT; } });
111
117
  // Proto definition helper
112
118
  var fiber_app_js_1 = require("./schema/fiber-app.js");
113
119
  Object.defineProperty(exports, "toProtoDefinition", { enumerable: true, get: function () { return fiber_app_js_1.toProtoDefinition; } });
@@ -150,6 +156,7 @@ Object.defineProperty(exports, "transferAsset", { enumerable: true, get: functio
150
156
  Object.defineProperty(exports, "triggers", { enumerable: true, get: function () { return effects_js_1.triggers; } });
151
157
  Object.defineProperty(exports, "spawn", { enumerable: true, get: function () { return effects_js_1.spawn; } });
152
158
  Object.defineProperty(exports, "emit", { enumerable: true, get: function () { return effects_js_1.emit; } });
159
+ Object.defineProperty(exports, "scriptCall", { enumerable: true, get: function () { return effects_js_1.scriptCall; } });
153
160
  Object.defineProperty(exports, "toFiber", { enumerable: true, get: function () { return effects_js_1.toFiber; } });
154
161
  Object.defineProperty(exports, "toWallet", { enumerable: true, get: function () { return effects_js_1.toWallet; } });
155
162
  Object.defineProperty(exports, "RESERVED_EFFECT_KEYS", { enumerable: true, get: function () { return effects_js_1.RESERVED_EFFECT_KEYS; } });
@@ -40,7 +40,7 @@ var __importStar = (this && this.__importStar) || (function () {
40
40
  };
41
41
  })();
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
- exports.GENESIS_MANIFEST_VERSION = exports.buildGenesisManifest = exports.dropNulls = exports.createAuthorizeComposePayload = exports.createApplyMorphismPayload = exports.createMintAssetPayload = exports.createAssetPolicyPayload = exports.createDataTransactionRequest = exports.createScriptPayload = exports.createStateMachinePayload = exports.getPublicKeyForRegistration = exports.addTransactionSignature = exports.signTransaction = exports.createInvokeScriptPayload = exports.createArchivePayload = exports.createTransitionPayload = exports.MetagraphClient = exports.extractOnChainState = exports.getScriptInvocations = exports.getEventReceipts = exports.getLogsForFiber = exports.getLatestOnChainState = exports.getSnapshotOnChainState = exports.decodeOnChainState = exports.TOKEN_BEHAVIOR_BITS = exports.OTTOCHAIN_MESSAGE_TYPES = exports.proto = void 0;
43
+ exports.GENESIS_MANIFEST_VERSION = exports.buildGenesisManifest = exports.dropNulls = exports.MORPHISM_LINT_CODES = exports.lintApplyMorphism = exports.createAuthorizeComposePayload = exports.createApplyMorphismPayload = exports.createMintAssetPayload = exports.createAssetPolicyPayload = exports.createDataTransactionRequest = exports.createScriptPayload = exports.createStateMachinePayload = exports.getPublicKeyForRegistration = exports.addTransactionSignature = exports.signTransaction = exports.createInvokeScriptPayload = exports.createArchivePayload = exports.createTransitionPayload = exports.SNAPSHOT_FINALIZED_EVENT = exports.commitKeyPath = exports.verifyMptInclusion = exports.verifyStateProof = exports.MetagraphClient = exports.extractOnChainState = exports.getScriptInvocations = exports.getEventReceipts = exports.getLogsForFiber = exports.getLatestOnChainState = exports.getSnapshotOnChainState = exports.decodeOnChainState = exports.TOKEN_BEHAVIOR_BITS = exports.OTTOCHAIN_MESSAGE_TYPES = exports.proto = void 0;
44
44
  // Re-export generated protobuf types (for binary encoding)
45
45
  exports.proto = __importStar(require("../generated/index.js"));
46
46
  // Runtime message type validation
@@ -57,6 +57,12 @@ Object.defineProperty(exports, "getScriptInvocations", { enumerable: true, get:
57
57
  Object.defineProperty(exports, "extractOnChainState", { enumerable: true, get: function () { return snapshot_js_1.extractOnChainState; } });
58
58
  var metagraph_client_js_1 = require("./metagraph-client.js");
59
59
  Object.defineProperty(exports, "MetagraphClient", { enumerable: true, get: function () { return metagraph_client_js_1.MetagraphClient; } });
60
+ var state_proof_js_1 = require("./state-proof.js");
61
+ Object.defineProperty(exports, "verifyStateProof", { enumerable: true, get: function () { return state_proof_js_1.verifyStateProof; } });
62
+ Object.defineProperty(exports, "verifyMptInclusion", { enumerable: true, get: function () { return state_proof_js_1.verifyMptInclusion; } });
63
+ Object.defineProperty(exports, "commitKeyPath", { enumerable: true, get: function () { return state_proof_js_1.commitKeyPath; } });
64
+ var webhook_notifications_js_1 = require("./webhook-notifications.js");
65
+ Object.defineProperty(exports, "SNAPSHOT_FINALIZED_EVENT", { enumerable: true, get: function () { return webhook_notifications_js_1.SNAPSHOT_FINALIZED_EVENT; } });
60
66
  // Transaction helpers (state machine payloads, signing)
61
67
  var transaction_js_1 = require("./transaction.js");
62
68
  Object.defineProperty(exports, "createTransitionPayload", { enumerable: true, get: function () { return transaction_js_1.createTransitionPayload; } });
@@ -72,6 +78,10 @@ Object.defineProperty(exports, "createAssetPolicyPayload", { enumerable: true, g
72
78
  Object.defineProperty(exports, "createMintAssetPayload", { enumerable: true, get: function () { return transaction_js_1.createMintAssetPayload; } });
73
79
  Object.defineProperty(exports, "createApplyMorphismPayload", { enumerable: true, get: function () { return transaction_js_1.createApplyMorphismPayload; } });
74
80
  Object.defineProperty(exports, "createAuthorizeComposePayload", { enumerable: true, get: function () { return transaction_js_1.createAuthorizeComposePayload; } });
81
+ // Message-layer advisory lint for ApplyMorphism (Compose/Pool C2 mistakes the chain rejects).
82
+ var morphism_lint_js_1 = require("./morphism-lint.js");
83
+ Object.defineProperty(exports, "lintApplyMorphism", { enumerable: true, get: function () { return morphism_lint_js_1.lintApplyMorphism; } });
84
+ Object.defineProperty(exports, "MORPHISM_LINT_CODES", { enumerable: true, get: function () { return morphism_lint_js_1.MORPHISM_LINT_CODES; } });
75
85
  // Data utilities
76
86
  var drop_nulls_js_1 = require("./drop-nulls.js");
77
87
  Object.defineProperty(exports, "dropNulls", { enumerable: true, get: function () { return drop_nulls_js_1.dropNulls; } });
@@ -119,7 +119,11 @@ class MetagraphClient {
119
119
  // -------------------------------------------------------------------------
120
120
  // Registry, audit, state-proofs, fee estimates (ML0 /data-application/v1/*)
121
121
  // -------------------------------------------------------------------------
122
- /** Get the metagraph version string. */
122
+ /**
123
+ * Get the node's service identity + build metadata. The chain's `GET …/version` returns a
124
+ * `VersionInfo` OBJECT (`{ service, version, name, scalaVersion, sbtVersion, gitCommit, buildTime,
125
+ * tessellationVersion }`) — chain `ServiceMeta.scala` — NOT a bare string.
126
+ */
123
127
  async getVersion() {
124
128
  return this.ml0.get('/data-application/v1/version');
125
129
  }
@@ -165,15 +169,55 @@ class MetagraphClient {
165
169
  async getAssetStateProof(assetId, field) {
166
170
  return this.ml0.get(`/data-application/v1/assets/${assetId}/state-proof?field=${encodeURIComponent(field)}`);
167
171
  }
168
- /** Estimate the fee/gas for a transition event on a state-machine fiber. */
172
+ /**
173
+ * Estimate the fee/gas for a transition event on a state-machine fiber. Returns a
174
+ * {@link TransitionFeeEstimate} (`{ fiberId, currentState, event, gasEstimate, opCount, maxDepth,
175
+ * candidateTransitions, note }`) — chain `FeeEstimates.scala`.
176
+ */
169
177
  async estimateTransitionFee(fiberId, eventName) {
170
178
  return this.ml0.get(`/data-application/v1/state-machines/${fiberId}/estimate-fee?event=${encodeURIComponent(eventName)}`);
171
179
  }
172
- /** Estimate the fee/gas for invoking a script fiber. */
180
+ /**
181
+ * Estimate the fee/gas for invoking a script fiber. Returns a {@link ScriptFeeEstimate}
182
+ * (`{ scriptId, gasEstimate, opCount, maxDepth, note }`) — chain `FeeEstimates.scala`.
183
+ */
173
184
  async estimateScriptFee(fiberId) {
174
185
  return this.ml0.get(`/data-application/v1/scripts/${fiberId}/estimate-fee`);
175
186
  }
176
187
  // -------------------------------------------------------------------------
188
+ // Webhook subscriptions (ML0 /data-application/v1/webhooks/*)
189
+ // -------------------------------------------------------------------------
190
+ /**
191
+ * Subscribe a callback URL to snapshot-notification webhooks.
192
+ *
193
+ * `POST …/webhooks/subscribe` with `SubscribeRequest { callbackUrl, secret? }`; the chain replies
194
+ * `201 Created` with `SubscribeResponse { id, callbackUrl, createdAt }`. Chain: `ML0Routes.scala`
195
+ * (`webhook.subscribe`) + `webhooks/Subscriber.scala`.
196
+ */
197
+ async subscribeWebhook(request) {
198
+ return this.ml0.post('/data-application/v1/webhooks/subscribe', request);
199
+ }
200
+ /**
201
+ * Unsubscribe a webhook by its subscriber id.
202
+ *
203
+ * `DELETE …/webhooks/subscribe/{id}` → `204 No Content` (empty body) on success, or `404` if the id is
204
+ * unknown. Chain: `ML0Routes.scala` (`webhook.unsubscribe`). The vendored `HttpClient` exposes only
205
+ * `get`/`post`, so this reuses its shared private `request` helper — same `NetworkError`/timeout
206
+ * handling as every other call — to issue the DELETE.
207
+ */
208
+ async unsubscribeWebhook(id) {
209
+ await this.ml0.request('DELETE', `/data-application/v1/webhooks/subscribe/${encodeURIComponent(id)}`);
210
+ }
211
+ /**
212
+ * List the current webhook subscribers (secrets are redacted server-side).
213
+ *
214
+ * `GET …/webhooks/subscribers` → `SubscriberList { subscribers: Subscriber[] }`. Chain:
215
+ * `ML0Routes.scala` (`webhook.list`) + `webhooks/Subscriber.scala`.
216
+ */
217
+ async listWebhookSubscribers() {
218
+ return this.ml0.get('/data-application/v1/webhooks/subscribers');
219
+ }
220
+ // -------------------------------------------------------------------------
177
221
  // Framework snapshot endpoints (ML0 /snapshots/*)
178
222
  // -------------------------------------------------------------------------
179
223
  /**
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ /**
3
+ * Message-layer advisory lint for an {@link ApplyMorphism} transaction (asset-model.md §7; chain
4
+ * audit finding C2, enforced in `AssetCombiner`).
5
+ *
6
+ * Purpose
7
+ * -------
8
+ * The definition-scoped {@link ../schema/guard-lint.ts | guard-lint} walks fiber-app DEFINITIONS; it
9
+ * never sees an `ApplyMorphism` MESSAGE, so a malformed / no-consent `Compose` slips past it. This
10
+ * linter closes that gap at the transaction-builder layer: hand it the `ApplyMorphism` you are about
11
+ * to sign and it flags the two classes of C2 mistake the chain rejects.
12
+ *
13
+ * The chain rule being mirrored (C2, `Compose`/`Pool` only):
14
+ * - `otherAssetIds` must be free of duplicates and must NOT include the source `assetId`
15
+ * (the old self/duplicate-inflation path — rejected unconditionally, holder-independent).
16
+ * - every counter-party the signer does NOT hold requires a live {@link AuthorizeCompose} `nonce`;
17
+ * a same-holder / all-signer-owned compose needs none.
18
+ *
19
+ * ERRORS are statically certain (the chain rejects regardless of who holds what). WARNINGS are
20
+ * heuristic — the signer's holdings/ownership are unknown at build time, so a missing consent nonce
21
+ * cannot be proven fatal (a same-holder compose is legitimately nonce-less).
22
+ *
23
+ * This is a PURE, non-throwing advisory. It is deliberately NOT wired into
24
+ * {@link createApplyMorphismPayload} (which must stay non-breaking and side-effect-free); call it
25
+ * explicitly before signing, or in your app's own pre-flight checks.
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.MORPHISM_LINT_CODES = void 0;
29
+ exports.lintApplyMorphism = lintApplyMorphism;
30
+ /** Stable rule codes for {@link lintApplyMorphism} — referenced by tests and report tooling. */
31
+ exports.MORPHISM_LINT_CODES = {
32
+ /** `otherAssetIds` contains a duplicate id (chain rejects — inflation path). */
33
+ DUPLICATE_OTHER_ID: 'morphism-duplicate-other-id',
34
+ /** `otherAssetIds` includes the source `assetId` (self-composition — chain rejects). */
35
+ SELF_COMPOSE: 'morphism-self-compose',
36
+ /** `otherAssetIds` is non-empty but `kind` ignores it (not Compose/Pool — likely a mistake). */
37
+ OTHER_IDS_IGNORED: 'morphism-other-ids-ignored',
38
+ /** Compose/Pool with counter-parties and no consent `nonce` (rejected IF any part is not signer-owned). */
39
+ COMPOSE_NO_NONCE: 'morphism-compose-no-nonce',
40
+ };
41
+ /** The morphism kinds whose semantics READ `otherAssetIds` (fold counter-party parts into a composite). */
42
+ const COMPOSING_KINDS = new Set(['COMPOSE', 'POOL']);
43
+ function finding(severity, code, message, path) {
44
+ return { severity, code, message, path };
45
+ }
46
+ /**
47
+ * Lint a single {@link ApplyMorphism} message. Pure and non-throwing. Returns every finding
48
+ * (`error` + `warn`); callers decide the policy (e.g. block on any `error`, surface `warn`s).
49
+ *
50
+ * ERRORS (statically certain — the chain rejects unconditionally, holder-independent):
51
+ * - {@link MORPHISM_LINT_CODES.DUPLICATE_OTHER_ID} — `otherAssetIds` contains a duplicate id.
52
+ * - {@link MORPHISM_LINT_CODES.SELF_COMPOSE} — `otherAssetIds` includes the source `assetId`.
53
+ * - {@link MORPHISM_LINT_CODES.OTHER_IDS_IGNORED} — `otherAssetIds` is non-empty but `kind` is not
54
+ * `COMPOSE`/`POOL` (the field is ignored for other kinds — a likely authoring mistake).
55
+ *
56
+ * WARNINGS (heuristic — holder/ownership unknown at build time):
57
+ * - {@link MORPHISM_LINT_CODES.COMPOSE_NO_NONCE} — a `COMPOSE`/`POOL` with a non-empty
58
+ * `otherAssetIds` and NO `nonce`. IF any counter-party is not signer-owned the chain rejects it
59
+ * (C2); attach the reveal half of an {@link AuthorizeCompose} handshake as `nonce`. A same-holder
60
+ * compose (all parts signer-owned) is legitimately nonce-less — hence a warning, not an error.
61
+ */
62
+ function lintApplyMorphism(msg) {
63
+ const out = [];
64
+ const ids = msg.otherAssetIds ?? [];
65
+ const isComposing = COMPOSING_KINDS.has(msg.kind);
66
+ // ---- error: duplicate ids in otherAssetIds ---------------------------------
67
+ // One finding per DISTINCT duplicated id (path points at its first repeat occurrence).
68
+ const seen = new Set();
69
+ const dupReported = new Set();
70
+ ids.forEach((id, i) => {
71
+ if (seen.has(id) && !dupReported.has(id)) {
72
+ dupReported.add(id);
73
+ out.push(finding('error', exports.MORPHISM_LINT_CODES.DUPLICATE_OTHER_ID, `otherAssetIds contains duplicate id "${id}". The chain rejects a Compose/Pool whose ` +
74
+ `otherAssetIds has duplicates (the old inflation path). List each counter-party asset once.`, `otherAssetIds[${i}]`));
75
+ }
76
+ seen.add(id);
77
+ });
78
+ // ---- error: otherAssetIds includes the source assetId (self-composition) ----
79
+ const selfIdx = ids.indexOf(msg.assetId);
80
+ if (selfIdx !== -1) {
81
+ out.push(finding('error', exports.MORPHISM_LINT_CODES.SELF_COMPOSE, `otherAssetIds includes the source assetId "${msg.assetId}" (self-composition). The chain ` +
82
+ `rejects this unconditionally. otherAssetIds must list only the OTHER parts folded into the composite.`, `otherAssetIds[${selfIdx}]`));
83
+ }
84
+ // ---- error: otherAssetIds set but kind ignores it ---------------------------
85
+ if (ids.length > 0 && !isComposing) {
86
+ out.push(finding('error', exports.MORPHISM_LINT_CODES.OTHER_IDS_IGNORED, `otherAssetIds is non-empty but kind is "${msg.kind}", which ignores otherAssetIds entirely ` +
87
+ `(only COMPOSE/POOL fold in counter-party parts). This is almost certainly a mistake — set ` +
88
+ `kind to COMPOSE/POOL, or drop otherAssetIds.`, 'otherAssetIds'));
89
+ }
90
+ // ---- warn: composing with counter-parties but no consent nonce --------------
91
+ // nonce === 0 is a valid live nonce; only an OMITTED nonce (undefined/null) triggers the warning.
92
+ if (isComposing && ids.length > 0 && msg.nonce == null) {
93
+ out.push(finding('warn', exports.MORPHISM_LINT_CODES.COMPOSE_NO_NONCE, `${msg.kind} folds in otherAssetIds but carries no consent nonce. If ANY counter-party asset ` +
94
+ `is not held by the signer, the chain REJECTS this (C2): attach the reveal half of an ` +
95
+ `AuthorizeCompose handshake as \`nonce\`. A same-holder compose (all parts signer-owned) ` +
96
+ `needs none, so this is advisory.`, 'nonce'));
97
+ }
98
+ return out;
99
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.commitKeyPath = commitKeyPath;
4
+ exports.verifyMptInclusion = verifyMptInclusion;
5
+ exports.verifyStateProof = verifyStateProof;
6
+ /**
7
+ * Light-client verification of committed state proofs.
8
+ *
9
+ * The chain's `/state-machines|scripts|assets/{id}/state-proof` endpoints return a
10
+ * {@link StateProof}: the record plus a Merkle-Patricia inclusion proof against `mptRoot`,
11
+ * whose combined hash IS the snapshot's consensus-signed `calculatedStateProof`. Verifying
12
+ * the proof locally means trusting NO serving node — only snapshot consensus.
13
+ *
14
+ * The fold mirrors metakit's `MerklePatriciaVerifier` byte-for-byte (validated against live
15
+ * cluster proofs):
16
+ *
17
+ * - the response `witness` is LEAF-FIRST; verification walks it reversed (root-first);
18
+ * - every node's digest = `sha256(typePrefix ++ utf8(JCS(dropNulls(contents))))` over the
19
+ * commitment's SUBTYPE (contents-only) encoding — prefixes: leaf `0x00`, branch `0x01`,
20
+ * extension `0x02`;
21
+ * - a Branch consumes one path nibble via `pathsDigest`; an Extension consumes its `shared`
22
+ * nibbles and continues at `childDigest`;
23
+ * - the terminal Leaf must match the remaining path (`remaining`) and bind
24
+ * `dataDigest == sha256(JCS(dropNulls(record)))` (no prefix);
25
+ * - the trie path of a committed key is `hex(utf8(key))` (metakit `CommitKey.toHex`), e.g.
26
+ * `fiber/<uuid>` → `66696265722f…`.
27
+ *
28
+ * NOTE: this intentionally does NOT route through the JLVM `mpt_verify` opcode — the
29
+ * TypeScript port of that opcode mis-canonicalizes non-scalar leaf values (fiber records)
30
+ * and returns false; its conformance vectors only cover scalar leaves. The Scala (chain)
31
+ * side is correct. Once the port is fixed upstream this helper stays valid either way: it
32
+ * is the same algorithm without the JsonLogic value-domain round-trip.
33
+ */
34
+ const metagraph_sdk_1 = require("@constellation-network/metagraph-sdk");
35
+ const sha256_js_1 = require("@noble/hashes/sha256.js");
36
+ const utf8 = (s) => new TextEncoder().encode(s);
37
+ const toHexStr = (b) => Array.from(b, (x) => x.toString(16).padStart(2, '0')).join('');
38
+ /** `sha256(prefixByte? ++ utf8(JCS(dropNulls(value))))` — the chain's content-digest rule. */
39
+ function jcsSha256(value, prefix) {
40
+ const canonical = utf8((0, metagraph_sdk_1.canonicalize)(value));
41
+ if (prefix === undefined)
42
+ return toHexStr((0, sha256_js_1.sha256)(canonical));
43
+ const framed = new Uint8Array(1 + canonical.length);
44
+ framed[0] = prefix;
45
+ framed.set(canonical, 1);
46
+ return toHexStr((0, sha256_js_1.sha256)(framed));
47
+ }
48
+ /** The MPT trie path of a committed key — `hex(utf8(key))` (metakit `CommitKey.toHex`). */
49
+ function commitKeyPath(key) {
50
+ return toHexStr(utf8(key));
51
+ }
52
+ const NODE_PREFIX = { Leaf: 0, Branch: 1, Extension: 2 };
53
+ /**
54
+ * Verify a Merkle-Patricia inclusion proof: `record` is committed at `key` under `mptRoot`.
55
+ *
56
+ * Pure and side-effect free; never throws on malformed input (returns `{ ok: false }`).
57
+ */
58
+ function verifyMptInclusion(mptRoot, key, record, proof) {
59
+ const fail = (reason) => ({ ok: false, reason });
60
+ if (!proof || typeof proof.path !== 'string' || !Array.isArray(proof.witness) || proof.witness.length === 0)
61
+ return fail('malformed proof (missing path/witness)');
62
+ const expectedPath = commitKeyPath(key);
63
+ if (proof.path.toLowerCase() !== expectedPath)
64
+ return fail(`trie path != hex(utf8("${key}"))`);
65
+ let digest = String(mptRoot).replace(/^0x/, '').toLowerCase();
66
+ let path = proof.path.toLowerCase();
67
+ const nodes = [...proof.witness].reverse(); // response is leaf-first; fold root-first
68
+ for (const [depth, node] of nodes.entries()) {
69
+ const isLast = depth === nodes.length - 1;
70
+ const prefix = NODE_PREFIX[node?.type];
71
+ if (prefix === undefined)
72
+ return fail(`unknown witness node type '${String(node?.type)}'`);
73
+ if (jcsSha256(node.contents, prefix) !== digest)
74
+ return fail(`${node.type.toLowerCase()} commitment mismatch at depth ${depth}`);
75
+ if (node.type === 'Branch') {
76
+ const child = node.contents.pathsDigest?.[path[0]];
77
+ if (!child)
78
+ return fail(`branch has no child at nibble '${path[0]}' (depth ${depth})`);
79
+ digest = child.toLowerCase();
80
+ path = path.slice(1);
81
+ }
82
+ else if (node.type === 'Extension') {
83
+ const shared = String(node.contents.shared ?? '');
84
+ if (!path.startsWith(shared.toLowerCase()))
85
+ return fail(`extension shared-nibble mismatch at depth ${depth}`);
86
+ path = path.slice(shared.length);
87
+ digest = String(node.contents.childDigest ?? '').toLowerCase();
88
+ }
89
+ else {
90
+ // Leaf
91
+ if (!isLast)
92
+ return fail('leaf before end of witness');
93
+ if (String(node.contents.remaining ?? '').toLowerCase() !== path)
94
+ return fail('leaf remaining-path mismatch');
95
+ if (String(node.contents.dataDigest ?? '').toLowerCase() !== jcsSha256(record))
96
+ return fail('leaf dataDigest != sha256(JCS(record))');
97
+ return { ok: true };
98
+ }
99
+ }
100
+ return fail('witness does not terminate in a leaf');
101
+ }
102
+ /**
103
+ * Verify a whole {@link StateProof} response for a committed key.
104
+ *
105
+ * Checks the trie inclusion of `record` at `key` under the response's `mptRoot` (see
106
+ * {@link verifyMptInclusion}). The caller supplies the key it EXPECTS (`fiber/<id>`,
107
+ * `script/<id>`, `asset/<id>`, …) so a proof for some other record cannot be substituted;
108
+ * the response's own `key` field must agree.
109
+ *
110
+ * Trust anchor: `committedRoot` (= `sha256(mptRoot ++ catalogRoot)`) rides in the
111
+ * consensus-signed snapshot as `calculatedStateProof`; binding `mptRoot` to a snapshot is
112
+ * the caller's (or a snapshot-following light client's) responsibility.
113
+ */
114
+ function verifyStateProof(resp, expectedKey) {
115
+ if (resp.key !== expectedKey)
116
+ return { ok: false, reason: `response key '${resp.key}' != expected '${expectedKey}'` };
117
+ return verifyMptInclusion(String(resp.mptRoot), expectedKey, resp.record, resp.proof);
118
+ }
@@ -166,7 +166,13 @@ function createAssetPolicyPayload(params) {
166
166
  function createMintAssetPayload(params) {
167
167
  return { MintAsset: params };
168
168
  }
169
- /** Wrap an {@link ApplyMorphism} (apply a typed morphism to an asset instance). */
169
+ /**
170
+ * Wrap an {@link ApplyMorphism} (apply a typed morphism to an asset instance).
171
+ *
172
+ * @see lintApplyMorphism (`./morphism-lint.ts`) — pure, non-throwing advisory that flags the
173
+ * Compose/Pool mistakes the chain rejects (duplicate / self-referential `otherAssetIds`; a
174
+ * cross-holder compose with no consent `nonce`). Run it on `params` before signing.
175
+ */
170
176
  function createApplyMorphismPayload(params) {
171
177
  return { ApplyMorphism: params };
172
178
  }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**
3
+ * Webhook notification PAYLOAD types — the server-initiated push the chain POSTs to a subscribed
4
+ * callback, NOT part of the client's request surface.
5
+ *
6
+ * These are hand-authored (not generated from the OpenAPI contract) because the push is an OUTBOUND
7
+ * POST from the metagraph to the subscriber's callback URL — it is not an endpoint the chain's tapir
8
+ * `ApiEndpoints` serves, so it never appears in `openapi/ottochain-openapi-ml0.json`. They mirror the
9
+ * chain's shared network DTOs byte-for-byte:
10
+ *
11
+ * chain: modules/models/src/main/scala/xyz/kd5ujc/schema/api/webhooks/Subscriber.scala
12
+ * (SnapshotNotification / NotificationStats / SnapshotRejection)
13
+ * emit: modules/l0/.../webhooks/WebhookDispatcher.scala — `notification.asJson.noSpaces`
14
+ *
15
+ * Field-shape notes tied to the chain source:
16
+ * - The dispatcher serializes with a plain derived encoder (`.asJson`, NO `dropNullValues`), so an
17
+ * `Option[Long]` that is `None` is emitted as an explicit `null` — the KEY is always present, the
18
+ * VALUE is nullable. Hence `number | null` (a required key), not `number | undefined` (an optional
19
+ * key). Consumers must handle `null`.
20
+ * - `Long` → JSON number; `Instant` → ISO-8601 string; `UUID` → string.
21
+ * - The dispatcher only ever emits `"snapshot.finalized"` today (there is a dead, never-dispatched
22
+ * `RejectionNotification`/`transaction.rejected` type in the chain models — deliberately NOT mirrored
23
+ * here, because the SDK should model only what is actually sent). Rejections now ride the finalized
24
+ * snapshot's `rejections[]`, drained from its committed `RejectionReceipt`s.
25
+ *
26
+ * @see modules/models/.../schema/api/webhooks/Subscriber.scala
27
+ * @see modules/l0/.../webhooks/WebhookDispatcher.scala
28
+ * @packageDocumentation
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.SNAPSHOT_FINALIZED_EVENT = void 0;
32
+ /** The only event the chain's `WebhookDispatcher` currently emits. */
33
+ exports.SNAPSHOT_FINALIZED_EVENT = 'snapshot.finalized';
@@ -16,7 +16,7 @@
16
16
  * ```
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.RESERVED_EFFECT_KEYS = exports.emit = exports.spawn = exports.triggers = exports.toWallet = exports.toFiber = exports.transferAsset = exports.setDependencyActive = exports.addDependency = void 0;
19
+ exports.RESERVED_EFFECT_KEYS = exports.scriptCall = exports.emit = exports.spawn = exports.triggers = exports.toWallet = exports.toFiber = exports.transferAsset = exports.setDependencyActive = exports.addDependency = void 0;
20
20
  /**
21
21
  * `_addDependency` (#24): add — or re-activate — a runtime DYNAMIC dependency on `fiberId`, so that
22
22
  * fiber's state appears in `machines.<fiberId>` for SUBSEQUENT transitions of this fiber. `fiberId` may
@@ -121,11 +121,20 @@ exports.triggers = triggers;
121
121
  * MUST be a literal {@link ProtoStateMachineDefinition} (e.g. a nested `machine().wireDefinition()` /
122
122
  * `toProtoDefinition(child)` output) — NOT an expression the engine would evaluate at runtime.
123
123
  *
124
- * F8 gotcha — `owners` is load-bearing. A spawned child's transitions are gated by
125
- * `owners authorizedSigners` (`riverdale-economy/README.md`), so EVERY party that will later drive
126
- * the child (e.g. every bidder on a spawned auction) MUST be listed in `owners`, or their events are
127
- * rejected. `owners` may be a literal id array or an expression (e.g. `{ var: "event.auctionOwners" }`);
128
- * `childId` / `initialData` likewise accept literals or expressions.
124
+ * H1 constraintchild `owners` MUST be a SUBSET of the SPAWNING PARENT fiber's `owners`. As of the
125
+ * fiber-engine permissionless-hardening (chain branch `fix/fiber-engine-permissionless-hardening`, audit
126
+ * finding H1) the chain FAILS-CLOSED it aborts the whole transition under EVERY `spawnOwnerPolicy` if
127
+ * a `_spawn`'s resolved child `owners` are not the parent's owners. Assigning arbitrary, unsigned owners
128
+ * (the old "auction owned by all its bidders" pattern, where the bidders are NOT parent owners) is no
129
+ * longer valid: those events are not silently rejected, the spawn itself is rejected.
130
+ *
131
+ * So DO NOT try to enroll later participants by listing them here. A spawned child's transitions are gated
132
+ * by `owners ∪ authorizedSigners` for the DIRECT (wallet-signed) path; admit additional drivers through
133
+ * the child's OWN transitions (`authorizedSigners`), or drive it via the cascade (`_triggers`) subject to
134
+ * its `acceptedCallers` — never by widening `owners` beyond the parent's set. `owners` may be a literal id
135
+ * array or an expression (e.g. `{ var: "state.owners" }` — resolve it WITHIN the parent's owners), but a
136
+ * value the parent does not own (e.g. `{ var: "event.auctionOwners" }` supplied by the caller) will be
137
+ * rejected on-chain. `childId` / `initialData` likewise accept literals or expressions.
129
138
  */
130
139
  const spawn = (ds) => ({ _spawn: ds });
131
140
  exports.spawn = spawn;
@@ -139,6 +148,34 @@ const emit = (es) => ({
139
148
  _emit: es,
140
149
  });
141
150
  exports.emit = emit;
151
+ /**
152
+ * `_scriptCall`: invoke a SCRIPT fiber's `method` from an effect. Unlike the array-valued directives,
153
+ * `_scriptCall` is a SINGLE object `{ fiberId, method, args }` (chain `ReservedKeys.scala` `SCRIPT_CALL` /
154
+ * `EffectExtractor.extractScriptCall`). `fiberId` is the target script's UUID (a literal or an expression,
155
+ * e.g. `{ var: "state.resolverId" }`); `method` is the script method NAME; `args` is the argument body —
156
+ * a JSON-Logic value the chain EVALUATES against the transition context before dispatch.
157
+ *
158
+ * The chain's extractor requires ALL THREE fields — if `args` is absent the whole call is silently DROPPED
159
+ * (fail-silent, like the other extractors). So the builder ALWAYS emits `args`, defaulting an omitted one
160
+ * to `{}` (no-args), mirroring how {@link triggers} defaults an absent `payload`. Authoring this as a
161
+ * builder makes a typo'd `_scriptcall` / wrong field a TypeScript error rather than a silently-merged
162
+ * state field. Place the fragment INSIDE the effect's state-update map so it rides in the evaluated result.
163
+ *
164
+ * @example
165
+ * effect: { merge: [ { var: "state" }, {
166
+ * status: "resolving",
167
+ * ...scriptCall({ fiberId: { var: "state.resolverId" }, method: "resolve",
168
+ * args: { marketId: { var: "machineId" } } }),
169
+ * } ] }
170
+ */
171
+ const scriptCall = (call) => ({
172
+ _scriptCall: {
173
+ fiberId: call.fiberId,
174
+ method: call.method,
175
+ args: call.args ?? {},
176
+ },
177
+ });
178
+ exports.scriptCall = scriptCall;
142
179
  /**
143
180
  * The complete set of `_`-prefixed RESERVED effect keys the chain's `EffectExtractor` consumes and
144
181
  * `StateMerger` strips from state (`ReservedKeys.scala:12-49`). Exported so a validator (Proposal 01)
@@ -194,8 +194,13 @@ function toProtoDefinition(def) {
194
194
  from: t.from,
195
195
  to: t.to,
196
196
  eventName: t.eventName,
197
- guard: t.guard, // Required - Scala has no default
198
- effect: t.effect, // Required - Scala has no default
197
+ // guard/effect are REQUIRED by the Scala `Transition` case class (no defaults). The authoring
198
+ // `Transition` types them OPTIONAL for ergonomics, so ALWAYS project a concrete value here — an
199
+ // omitted key would serialize to `undefined`, get dropped, and fail the chain's decode (HTTP 400).
200
+ // The defaults are the exact shapes the chain's `PublishVersionSigningCanonicalSuite` accepts:
201
+ // an always-true guard `{"==":[1,1]}` and an empty (no-op) effect `{}`.
202
+ guard: t.guard ?? { '==': [1, 1] },
203
+ effect: t.effect ?? {},
199
204
  // Chain `Transition.dependencies: Set[UUID]` is REQUIRED (no Scala default), and each
200
205
  // element must be a fiber UUID string. A `DependencySpec` object is a build-time-only
201
206
  // authoring affordance with no wire representation (concrete dependency UUIDs are per