@ottochain/sdk 1.0.3 → 1.1.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 (153) hide show
  1. package/dist/cjs/generated/index.js +11 -1
  2. package/dist/cjs/metakit/drop-nulls.js +50 -0
  3. package/dist/cjs/metakit/normalize.js +147 -0
  4. package/dist/cjs/ottochain/metagraph-client.js +147 -0
  5. package/dist/cjs/validation.js +1 -1
  6. package/dist/esm/generated/index.js +11 -1
  7. package/dist/esm/metakit/drop-nulls.js +46 -0
  8. package/dist/esm/metakit/normalize.js +140 -0
  9. package/dist/esm/ottochain/metagraph-client.js +147 -0
  10. package/dist/esm/validation.js +1 -1
  11. package/dist/types/generated/index.d.ts +11 -1
  12. package/dist/types/metakit/drop-nulls.d.ts +29 -0
  13. package/dist/types/metakit/normalize.d.ts +67 -0
  14. package/dist/types/ottochain/index.d.ts +1 -1
  15. package/dist/types/ottochain/metagraph-client.d.ts +90 -0
  16. package/package.json +12 -12
  17. package/dist/apps/contracts/index.d.ts +0 -21
  18. package/dist/apps/contracts/index.js +0 -39
  19. package/dist/apps/contracts/types.d.ts +0 -24
  20. package/dist/apps/contracts/types.js +0 -48
  21. package/dist/apps/identity/index.d.ts +0 -22
  22. package/dist/apps/identity/index.js +0 -40
  23. package/dist/apps/identity/types.d.ts +0 -30
  24. package/dist/apps/identity/types.js +0 -53
  25. package/dist/apps/index.d.ts +0 -29
  26. package/dist/apps/index.js +0 -60
  27. package/dist/apps/markets/index.d.ts +0 -26
  28. package/dist/apps/markets/index.js +0 -46
  29. package/dist/apps/markets/types.d.ts +0 -185
  30. package/dist/apps/markets/types.js +0 -252
  31. package/dist/apps/oracles/index.d.ts +0 -26
  32. package/dist/apps/oracles/index.js +0 -46
  33. package/dist/apps/oracles/types.d.ts +0 -211
  34. package/dist/apps/oracles/types.js +0 -306
  35. package/dist/cjs/apps/contracts/types.js +0 -48
  36. package/dist/cjs/apps/corporate/types.js +0 -44
  37. package/dist/cjs/apps/governance/types.js +0 -42
  38. package/dist/cjs/apps/identity/types.js +0 -53
  39. package/dist/cjs/apps/markets/types.js +0 -219
  40. package/dist/cjs/apps/oracles/types.js +0 -282
  41. package/dist/cjs/generated/ottochain/apps/contracts/v1/contract_pb.js +0 -100
  42. package/dist/cjs/generated/ottochain/apps/corporate/v1/corporate_pb.js +0 -392
  43. package/dist/cjs/generated/ottochain/apps/governance/v1/governance_pb.js +0 -235
  44. package/dist/cjs/generated/ottochain/apps/identity/v1/agent_pb.js +0 -116
  45. package/dist/cjs/generated/ottochain/apps/identity/v1/attestation_pb.js +0 -79
  46. package/dist/cjs/generated/ottochain/apps/markets/v1/market_pb.js +0 -151
  47. package/dist/cjs/generated/ottochain/apps/oracles/v1/oracle_pb.js +0 -109
  48. package/dist/cjs/generated/ottochain/v1/common_pb.js +0 -37
  49. package/dist/cjs/generated/ottochain/v1/fiber_pb.js +0 -86
  50. package/dist/cjs/generated/ottochain/v1/messages_pb.js +0 -44
  51. package/dist/cjs/generated/ottochain/v1/records_pb.js +0 -44
  52. package/dist/errors.d.ts +0 -221
  53. package/dist/errors.js +0 -293
  54. package/dist/esm/apps/contracts/types.js +0 -44
  55. package/dist/esm/apps/corporate/types.js +0 -38
  56. package/dist/esm/apps/governance/types.js +0 -35
  57. package/dist/esm/apps/identity/types.js +0 -50
  58. package/dist/esm/apps/markets/types.js +0 -206
  59. package/dist/esm/apps/oracles/types.js +0 -267
  60. package/dist/esm/generated/ottochain/apps/contracts/v1/contract_pb.js +0 -97
  61. package/dist/esm/generated/ottochain/apps/corporate/v1/corporate_pb.js +0 -389
  62. package/dist/esm/generated/ottochain/apps/governance/v1/governance_pb.js +0 -232
  63. package/dist/esm/generated/ottochain/apps/identity/v1/agent_pb.js +0 -113
  64. package/dist/esm/generated/ottochain/apps/identity/v1/attestation_pb.js +0 -76
  65. package/dist/esm/generated/ottochain/apps/markets/v1/market_pb.js +0 -148
  66. package/dist/esm/generated/ottochain/apps/oracles/v1/oracle_pb.js +0 -106
  67. package/dist/esm/generated/ottochain/v1/common_pb.js +0 -34
  68. package/dist/esm/generated/ottochain/v1/fiber_pb.js +0 -83
  69. package/dist/esm/generated/ottochain/v1/messages_pb.js +0 -41
  70. package/dist/esm/generated/ottochain/v1/records_pb.js +0 -41
  71. package/dist/generated/index.d.ts +0 -15
  72. package/dist/generated/index.js +0 -34
  73. package/dist/generated/ottochain/apps/contracts/v1/contract_pb.d.ts +0 -274
  74. package/dist/generated/ottochain/apps/contracts/v1/contract_pb.js +0 -100
  75. package/dist/generated/ottochain/apps/identity/v1/agent_pb.d.ts +0 -211
  76. package/dist/generated/ottochain/apps/identity/v1/agent_pb.js +0 -116
  77. package/dist/generated/ottochain/apps/identity/v1/attestation_pb.d.ts +0 -238
  78. package/dist/generated/ottochain/apps/identity/v1/attestation_pb.js +0 -79
  79. package/dist/generated/ottochain/apps/markets/v1/market_pb.d.ts +0 -436
  80. package/dist/generated/ottochain/apps/markets/v1/market_pb.js +0 -151
  81. package/dist/generated/ottochain/apps/oracles/v1/oracle_pb.d.ts +0 -393
  82. package/dist/generated/ottochain/apps/oracles/v1/oracle_pb.js +0 -109
  83. package/dist/generated/ottochain/v1/common_pb.d.ts +0 -86
  84. package/dist/generated/ottochain/v1/common_pb.js +0 -37
  85. package/dist/generated/ottochain/v1/fiber_pb.d.ts +0 -292
  86. package/dist/generated/ottochain/v1/fiber_pb.js +0 -86
  87. package/dist/generated/ottochain/v1/messages_pb.d.ts +0 -190
  88. package/dist/generated/ottochain/v1/messages_pb.js +0 -44
  89. package/dist/generated/ottochain/v1/records_pb.d.ts +0 -221
  90. package/dist/generated/ottochain/v1/records_pb.js +0 -44
  91. package/dist/index.d.ts +0 -21
  92. package/dist/index.js +0 -77
  93. package/dist/metakit/binary.d.ts +0 -38
  94. package/dist/metakit/binary.js +0 -58
  95. package/dist/metakit/canonicalize.d.ts +0 -26
  96. package/dist/metakit/canonicalize.js +0 -40
  97. package/dist/metakit/codec.d.ts +0 -16
  98. package/dist/metakit/codec.js +0 -45
  99. package/dist/metakit/currency-transaction.d.ts +0 -157
  100. package/dist/metakit/currency-transaction.js +0 -319
  101. package/dist/metakit/currency-types.d.ts +0 -55
  102. package/dist/metakit/currency-types.js +0 -13
  103. package/dist/metakit/hash.d.ts +0 -50
  104. package/dist/metakit/hash.js +0 -84
  105. package/dist/metakit/index.d.ts +0 -23
  106. package/dist/metakit/index.js +0 -74
  107. package/dist/metakit/network/client.d.ts +0 -23
  108. package/dist/metakit/network/client.js +0 -78
  109. package/dist/metakit/network/currency-l1-client.d.ts +0 -71
  110. package/dist/metakit/network/currency-l1-client.js +0 -101
  111. package/dist/metakit/network/data-l1-client.d.ts +0 -57
  112. package/dist/metakit/network/data-l1-client.js +0 -76
  113. package/dist/metakit/network/index.d.ts +0 -10
  114. package/dist/metakit/network/index.js +0 -16
  115. package/dist/metakit/network/types.d.ts +0 -74
  116. package/dist/metakit/network/types.js +0 -20
  117. package/dist/metakit/sign.d.ts +0 -65
  118. package/dist/metakit/sign.js +0 -120
  119. package/dist/metakit/signed-object.d.ts +0 -66
  120. package/dist/metakit/signed-object.js +0 -100
  121. package/dist/metakit/types.d.ts +0 -67
  122. package/dist/metakit/types.js +0 -14
  123. package/dist/metakit/verify.d.ts +0 -55
  124. package/dist/metakit/verify.js +0 -217
  125. package/dist/metakit/wallet.d.ts +0 -70
  126. package/dist/metakit/wallet.js +0 -127
  127. package/dist/ottochain/index.d.ts +0 -13
  128. package/dist/ottochain/index.js +0 -45
  129. package/dist/ottochain/metagraph-client.d.ts +0 -111
  130. package/dist/ottochain/metagraph-client.js +0 -157
  131. package/dist/ottochain/snapshot.d.ts +0 -86
  132. package/dist/ottochain/snapshot.js +0 -110
  133. package/dist/ottochain/types.d.ts +0 -278
  134. package/dist/ottochain/types.js +0 -11
  135. package/dist/types/apps/contracts/types.d.ts +0 -24
  136. package/dist/types/apps/corporate/types.d.ts +0 -9861
  137. package/dist/types/apps/governance/types.d.ts +0 -344
  138. package/dist/types/apps/identity/types.d.ts +0 -30
  139. package/dist/types/apps/markets/types.d.ts +0 -155
  140. package/dist/types/apps/oracles/types.d.ts +0 -193
  141. package/dist/types/generated/ottochain/apps/contracts/v1/contract_pb.d.ts +0 -274
  142. package/dist/types/generated/ottochain/apps/corporate/v1/corporate_pb.d.ts +0 -1172
  143. package/dist/types/generated/ottochain/apps/governance/v1/governance_pb.d.ts +0 -772
  144. package/dist/types/generated/ottochain/apps/identity/v1/agent_pb.d.ts +0 -211
  145. package/dist/types/generated/ottochain/apps/identity/v1/attestation_pb.d.ts +0 -238
  146. package/dist/types/generated/ottochain/apps/markets/v1/market_pb.d.ts +0 -436
  147. package/dist/types/generated/ottochain/apps/oracles/v1/oracle_pb.d.ts +0 -393
  148. package/dist/types/generated/ottochain/v1/common_pb.d.ts +0 -86
  149. package/dist/types/generated/ottochain/v1/fiber_pb.d.ts +0 -292
  150. package/dist/types/generated/ottochain/v1/messages_pb.d.ts +0 -190
  151. package/dist/types/generated/ottochain/v1/records_pb.d.ts +0 -221
  152. package/dist/validation.d.ts +0 -449
  153. package/dist/validation.js +0 -312
@@ -3,9 +3,19 @@
3
3
  * Generated Protobuf Types
4
4
  *
5
5
  * Auto-generated from proto/ definitions using ts-proto.
6
- * DO NOT EDIT - regenerate with `pnpm run generate`
6
+ * DO NOT EDIT - regenerate with `npm run generate`
7
7
  *
8
8
  * @packageDocumentation
9
+ *
10
+ * NOTE — Dual-type architecture:
11
+ * These generated types use proto conventions (FIBER_STATUS_ACTIVE, StateId { value } wrappers).
12
+ * The wire-format REST API types in src/ottochain/types.ts use plain strings ('Active').
13
+ * Both coexist intentionally until PR #89 (Migrate fiber-engine to generated Scala types) merges.
14
+ *
15
+ * TODO PR #89 migration: after PR #89 merges and cluster confirms new format,
16
+ * migrate all state-machine JSON files in src/apps/ from Circe-format
17
+ * ({ value: '...' } wrapped initialState) to plain string format.
18
+ * See docs/type-architecture.md for full migration plan.
9
19
  */
10
20
  Object.defineProperty(exports, "__esModule", { value: true });
11
21
  exports.MarketDefinition = exports.CancelMarketRequest = exports.SubmitResolutionRequest = exports.CommitToMarketRequest = exports.CreateMarketRequest = exports.Market = exports.Resolution = exports.Commitment = exports.MarketState = exports.MarketType = exports.ContractDefinition = exports.DisputeContractRequest = exports.RejectContractRequest = exports.CompleteContractRequest = exports.AcceptContractRequest = exports.ProposeContractRequest = exports.Contract = exports.ContractState = exports.ReputationConfig = exports.ChallengeRequest = exports.VouchRequest = exports.Attestation = exports.ReputationDelta = exports.AttestationType = exports.AgentIdentityDefinition = exports.AgentIdentity = exports.PlatformLink = exports.Platform = exports.AgentState = exports.CalculatedState = exports.OnChainState = exports.FiberCommit = exports.ScriptFiberRecord = exports.StateMachineFiberRecord = exports.OttochainMessage = exports.InvokeScript = exports.CreateScript = exports.ArchiveStateMachine = exports.TransitionStateMachine = exports.CreateStateMachine = exports.FiberLogEntry = exports.ScriptInvocation = exports.EventReceipt = exports.EmittedEvent = exports.StateMachineDefinition = exports.FiberOwnedAccess = exports.WhitelistAccess = exports.PublicAccess = exports.AccessControlPolicy = exports.FiberStatus = void 0;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /**
3
+ * Drop Null Values from JSON Objects
4
+ *
5
+ * Recursively removes null-valued keys from objects to match
6
+ * the Scala-side `JsonBinaryCodec.dropNulls` behavior in metakit.
7
+ *
8
+ * This ensures that the canonical JSON used for signing on the
9
+ * TypeScript side matches what the Scala metagraph produces when
10
+ * verifying signatures.
11
+ *
12
+ * Note: null values inside arrays are preserved (to maintain index
13
+ * positions). Only object field values that are null are removed.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.dropNulls = void 0;
17
+ /**
18
+ * Recursively remove null values from objects
19
+ *
20
+ * @param value - Any JSON-serializable value
21
+ * @returns A deep copy with null object fields removed
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * dropNulls({ a: 1, b: null, c: { d: null, e: 2 } })
26
+ * // => { a: 1, c: { e: 2 } }
27
+ *
28
+ * dropNulls([1, null, 3])
29
+ * // => [1, null, 3] (array nulls preserved)
30
+ * ```
31
+ */
32
+ function dropNulls(value) {
33
+ if (value === null || value === undefined) {
34
+ return value;
35
+ }
36
+ if (Array.isArray(value)) {
37
+ return value.map(dropNulls);
38
+ }
39
+ if (typeof value === 'object') {
40
+ const result = {};
41
+ for (const [k, v] of Object.entries(value)) {
42
+ if (v !== null && v !== undefined) {
43
+ result[k] = dropNulls(v);
44
+ }
45
+ }
46
+ return result;
47
+ }
48
+ return value;
49
+ }
50
+ exports.dropNulls = dropNulls;
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ /**
3
+ * Normalize OttoChain Messages for Signing
4
+ *
5
+ * Converts OttoChain message objects to the wire format expected by the
6
+ * Scala metagraph. Specifically, ensures all `Option[A]=None` fields are
7
+ * present as explicit `null` values, matching what circe's magnolia encoder
8
+ * produces on the Scala side.
9
+ *
10
+ * This is necessary because metakit's `JsonBinaryCodec.deriveDataUpdate`
11
+ * (rc.8) canonicalizes the circe-encoded JSON **including null fields**.
12
+ * If the TypeScript client omits optional fields (undefined), the canonical
13
+ * JSON won't match and signature verification will fail.
14
+ *
15
+ * Schema mapping (Scala → TypeScript):
16
+ * - `Option[A] = None` → `null` (must be explicit, not undefined/absent)
17
+ * - `Option[A] = Some(v)` → `v`
18
+ * - `List.empty` → `[]`
19
+ * - `Map.empty` → `{}`
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.normalizeMessage = exports.normalizeArchiveStateMachine = exports.normalizeTransitionStateMachine = exports.normalizeCreateStateMachine = void 0;
23
+ /**
24
+ * Normalize a State object for wire format
25
+ */
26
+ function normalizeState(state) {
27
+ return {
28
+ id: state.id,
29
+ isFinal: state.isFinal ?? false,
30
+ metadata: state.metadata ?? null,
31
+ };
32
+ }
33
+ /**
34
+ * Normalize a Transition object for wire format
35
+ */
36
+ function normalizeTransition(t) {
37
+ return {
38
+ from: t.from,
39
+ eventName: t.eventName,
40
+ to: t.to,
41
+ guard: t.guard ?? null,
42
+ actions: t.actions ?? null,
43
+ metadata: t.metadata ?? null,
44
+ };
45
+ }
46
+ /**
47
+ * Normalize a StateMachineDefinition for wire format
48
+ */
49
+ function normalizeDefinition(def) {
50
+ const states = def.states;
51
+ const normalizedStates = {};
52
+ if (states) {
53
+ for (const [key, state] of Object.entries(states)) {
54
+ normalizedStates[key] = normalizeState(state);
55
+ }
56
+ }
57
+ const transitions = def.transitions ?? [];
58
+ return {
59
+ states: normalizedStates,
60
+ initialState: def.initialState,
61
+ transitions: transitions.map(normalizeTransition),
62
+ metadata: def.metadata ?? null,
63
+ };
64
+ }
65
+ /**
66
+ * Normalize a CreateStateMachine message for wire format
67
+ *
68
+ * Ensures all Option fields are explicit null:
69
+ * - definition.metadata
70
+ * - definition.states[*].metadata
71
+ * - definition.transitions[*].guard
72
+ * - definition.transitions[*].actions
73
+ * - definition.transitions[*].metadata
74
+ * - parentFiberId
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const message = normalizeCreateStateMachine({
79
+ * fiberId: '...',
80
+ * definition: { states: { INIT: { id: { value: 'INIT' }, isFinal: false } }, ... },
81
+ * initialData: {}
82
+ * });
83
+ * // message now has parentFiberId: null, definition.metadata: null, etc.
84
+ * ```
85
+ */
86
+ function normalizeCreateStateMachine(msg) {
87
+ return {
88
+ fiberId: msg.fiberId,
89
+ definition: normalizeDefinition(msg.definition),
90
+ initialData: msg.initialData ?? {},
91
+ parentFiberId: msg.parentFiberId ?? null,
92
+ };
93
+ }
94
+ exports.normalizeCreateStateMachine = normalizeCreateStateMachine;
95
+ /**
96
+ * Normalize a TransitionStateMachine message for wire format
97
+ */
98
+ function normalizeTransitionStateMachine(msg) {
99
+ return {
100
+ fiberId: msg.fiberId,
101
+ eventName: msg.eventName,
102
+ eventData: msg.eventData ?? null,
103
+ fiberOrdinal: msg.fiberOrdinal,
104
+ };
105
+ }
106
+ exports.normalizeTransitionStateMachine = normalizeTransitionStateMachine;
107
+ /**
108
+ * Normalize an ArchiveStateMachine message for wire format
109
+ */
110
+ function normalizeArchiveStateMachine(msg) {
111
+ return {
112
+ fiberId: msg.fiberId,
113
+ reason: msg.reason ?? null,
114
+ };
115
+ }
116
+ exports.normalizeArchiveStateMachine = normalizeArchiveStateMachine;
117
+ /**
118
+ * Normalize any OttochainMessage wrapper for wire format.
119
+ *
120
+ * Detects the message type from the wrapper key and applies the
121
+ * appropriate normalization.
122
+ *
123
+ * @param message - OttochainMessage in wrapper format: `{ CreateStateMachine: {...} }`
124
+ * @returns Normalized message with all Option fields as explicit null
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const normalized = normalizeMessage({
129
+ * CreateStateMachine: { fiberId: '...', definition: {...}, initialData: {} }
130
+ * });
131
+ * const signed = await signDataUpdate(normalized, privateKey);
132
+ * ```
133
+ */
134
+ function normalizeMessage(message) {
135
+ if ('CreateStateMachine' in message) {
136
+ return { CreateStateMachine: normalizeCreateStateMachine(message.CreateStateMachine) };
137
+ }
138
+ if ('TransitionStateMachine' in message) {
139
+ return { TransitionStateMachine: normalizeTransitionStateMachine(message.TransitionStateMachine) };
140
+ }
141
+ if ('ArchiveStateMachine' in message) {
142
+ return { ArchiveStateMachine: normalizeArchiveStateMachine(message.ArchiveStateMachine) };
143
+ }
144
+ // CreateScript and InvokeScript — pass through (no optional fields)
145
+ return message;
146
+ }
147
+ exports.normalizeMessage = normalizeMessage;
@@ -138,6 +138,153 @@ class MetagraphClient {
138
138
  return snapshot.value.ordinal;
139
139
  }
140
140
  // -------------------------------------------------------------------------
141
+ // Fiber state subscription
142
+ // -------------------------------------------------------------------------
143
+ /**
144
+ * Subscribe to state changes for a state machine fiber.
145
+ *
146
+ * Polls ML0 at the configured interval and compares `sequenceNumber` to
147
+ * detect changes. Fires the callback with `(current, previous)` on the
148
+ * first poll (if `fireImmediately` is true — the default) and on every
149
+ * subsequent change.
150
+ *
151
+ * ML0 consistency: state is read from snapshot-level `CalculatedState`
152
+ * (via `checkpointService.get`), so a poll never returns a mid-transition
153
+ * value — every observation is a fully committed state.
154
+ *
155
+ * Uses `setTimeout` recursion to prevent overlapping polls when a request
156
+ * takes longer than `pollIntervalMs`.
157
+ *
158
+ * @param fiberId - UUID of the state machine fiber to watch
159
+ * @param callback - Called on first observation and on every state change
160
+ * @param options - Polling interval, error handler, fireImmediately flag
161
+ * @returns Unsubscribe function — call to stop polling immediately
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * const unsub = client.subscribeFiberState(fiberId, (current, prev) => {
166
+ * if (current?.currentState === 'Completed') {
167
+ * unsub();
168
+ * }
169
+ * });
170
+ * // Later:
171
+ * unsub();
172
+ * ```
173
+ */
174
+ subscribeFiberState(fiberId, callback, options) {
175
+ const intervalMs = options?.pollIntervalMs ?? 2000;
176
+ const onError = options?.onError ?? ((e) => console.warn('[subscribeFiberState]', e));
177
+ const fireImmediately = options?.fireImmediately ?? true;
178
+ let previous = null;
179
+ let lastSeqNum = null;
180
+ let firstPoll = true; // Track first poll separately — handles null seqNum on missing fiber
181
+ let active = true;
182
+ const invokeCallback = (current, prev) => {
183
+ try {
184
+ callback(current, prev);
185
+ }
186
+ catch (cbErr) {
187
+ try {
188
+ onError(cbErr instanceof Error ? cbErr : new Error(String(cbErr)));
189
+ }
190
+ catch { /* ignore */ }
191
+ }
192
+ };
193
+ const poll = async () => {
194
+ if (!active)
195
+ return;
196
+ let current;
197
+ try {
198
+ current = await this.getStateMachine(fiberId);
199
+ }
200
+ catch (err) {
201
+ // Network / timeout error — notify and reschedule
202
+ if (!active)
203
+ return;
204
+ try {
205
+ onError(err instanceof Error ? err : new Error(String(err)));
206
+ }
207
+ catch { /* ignore */ }
208
+ if (active)
209
+ setTimeout(poll, intervalMs);
210
+ return;
211
+ }
212
+ // Guard: unsubscribe may have been called while awaiting
213
+ if (!active)
214
+ return;
215
+ const currentSeq = current?.sequenceNumber ?? null;
216
+ if (firstPoll) {
217
+ firstPoll = false;
218
+ lastSeqNum = currentSeq;
219
+ previous = current;
220
+ if (fireImmediately) {
221
+ invokeCallback(current, null);
222
+ }
223
+ }
224
+ else if (currentSeq !== lastSeqNum) {
225
+ // State changed — sequenceNumber differs (handles null → value and value → null)
226
+ const prev = previous;
227
+ previous = current;
228
+ lastSeqNum = currentSeq;
229
+ invokeCallback(current, prev);
230
+ }
231
+ // No change → no callback
232
+ // Schedule next poll (setTimeout recursion prevents overlapping polls)
233
+ if (active)
234
+ setTimeout(poll, intervalMs);
235
+ };
236
+ // Start first poll via setTimeout(0) so Jest fake-timer control works correctly:
237
+ // callers can unsubscribe before the first poll runs, and each
238
+ // jest.runOnlyPendingTimersAsync() advances exactly one poll cycle.
239
+ setTimeout(poll, 0);
240
+ return () => { active = false; };
241
+ }
242
+ /**
243
+ * Wait for a fiber to reach a specific state, with an optional timeout.
244
+ *
245
+ * Builds on `subscribeFiberState` — resolves with the fiber record once
246
+ * `currentState === targetState`, or resolves with `null` after `timeoutMs`.
247
+ * Always calls unsubscribe on resolution or timeout (no memory leaks).
248
+ *
249
+ * @param fiberId - UUID of the state machine fiber
250
+ * @param targetState - State name to wait for (e.g. `'Completed'`)
251
+ * @param timeoutMs - Maximum wait in milliseconds (default: 30 000)
252
+ * @returns Fiber record when target state reached, or `null` on timeout
253
+ *
254
+ * @example
255
+ * ```typescript
256
+ * const result = await client.waitForState(fiberId, 'Completed', 15_000);
257
+ * if (result === null) console.warn('Timed out waiting for Completed');
258
+ * ```
259
+ */
260
+ waitForState(fiberId, targetState, timeoutMs = 30000, options) {
261
+ return new Promise((resolve) => {
262
+ let resolved = false;
263
+ let timer = null;
264
+ // unsub starts as a no-op so it's safe to call even if subscribeFiberState
265
+ // invokes the callback synchronously (before returning the real unsubscribe).
266
+ let unsub = () => { };
267
+ const done = (result) => {
268
+ if (resolved)
269
+ return;
270
+ resolved = true;
271
+ if (timer !== null)
272
+ clearTimeout(timer);
273
+ unsub(); // safe: either the real unsub or the initial no-op
274
+ resolve(result);
275
+ };
276
+ unsub = this.subscribeFiberState(fiberId, (current) => {
277
+ if (current?.currentState === targetState) {
278
+ done(current);
279
+ }
280
+ }, { fireImmediately: true, ...options });
281
+ // Only arm the timeout if the callback hasn't already resolved (synchronous case)
282
+ if (!resolved) {
283
+ timer = setTimeout(() => done(null), timeoutMs);
284
+ }
285
+ });
286
+ }
287
+ // -------------------------------------------------------------------------
141
288
  // DL1 data submission (framework POST /data)
142
289
  // -------------------------------------------------------------------------
143
290
  /**
@@ -135,7 +135,7 @@ exports.PlatformLinkSchema = zod_1.z.object({
135
135
  /**
136
136
  * Schema for contract terms (flexible structure)
137
137
  */
138
- exports.ContractTermsSchema = zod_1.z.record(zod_1.z.unknown());
138
+ exports.ContractTermsSchema = zod_1.z.record(zod_1.z.string(), zod_1.z.unknown());
139
139
  /**
140
140
  * Schema for ProposeContractRequest
141
141
  */
@@ -2,9 +2,19 @@
2
2
  * Generated Protobuf Types
3
3
  *
4
4
  * Auto-generated from proto/ definitions using ts-proto.
5
- * DO NOT EDIT - regenerate with `pnpm run generate`
5
+ * DO NOT EDIT - regenerate with `npm run generate`
6
6
  *
7
7
  * @packageDocumentation
8
+ *
9
+ * NOTE — Dual-type architecture:
10
+ * These generated types use proto conventions (FIBER_STATUS_ACTIVE, StateId { value } wrappers).
11
+ * The wire-format REST API types in src/ottochain/types.ts use plain strings ('Active').
12
+ * Both coexist intentionally until PR #89 (Migrate fiber-engine to generated Scala types) merges.
13
+ *
14
+ * TODO PR #89 migration: after PR #89 merges and cluster confirms new format,
15
+ * migrate all state-machine JSON files in src/apps/ from Circe-format
16
+ * ({ value: '...' } wrapped initialState) to plain string format.
17
+ * See docs/type-architecture.md for full migration plan.
8
18
  */
9
19
  // Core types (primitives - no wrapper messages)
10
20
  // Type aliases are in src/types.ts
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Drop Null Values from JSON Objects
3
+ *
4
+ * Recursively removes null-valued keys from objects to match
5
+ * the Scala-side `JsonBinaryCodec.dropNulls` behavior in metakit.
6
+ *
7
+ * This ensures that the canonical JSON used for signing on the
8
+ * TypeScript side matches what the Scala metagraph produces when
9
+ * verifying signatures.
10
+ *
11
+ * Note: null values inside arrays are preserved (to maintain index
12
+ * positions). Only object field values that are null are removed.
13
+ */
14
+ /**
15
+ * Recursively remove null values from objects
16
+ *
17
+ * @param value - Any JSON-serializable value
18
+ * @returns A deep copy with null object fields removed
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * dropNulls({ a: 1, b: null, c: { d: null, e: 2 } })
23
+ * // => { a: 1, c: { e: 2 } }
24
+ *
25
+ * dropNulls([1, null, 3])
26
+ * // => [1, null, 3] (array nulls preserved)
27
+ * ```
28
+ */
29
+ export function dropNulls(value) {
30
+ if (value === null || value === undefined) {
31
+ return value;
32
+ }
33
+ if (Array.isArray(value)) {
34
+ return value.map(dropNulls);
35
+ }
36
+ if (typeof value === 'object') {
37
+ const result = {};
38
+ for (const [k, v] of Object.entries(value)) {
39
+ if (v !== null && v !== undefined) {
40
+ result[k] = dropNulls(v);
41
+ }
42
+ }
43
+ return result;
44
+ }
45
+ return value;
46
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Normalize OttoChain Messages for Signing
3
+ *
4
+ * Converts OttoChain message objects to the wire format expected by the
5
+ * Scala metagraph. Specifically, ensures all `Option[A]=None` fields are
6
+ * present as explicit `null` values, matching what circe's magnolia encoder
7
+ * produces on the Scala side.
8
+ *
9
+ * This is necessary because metakit's `JsonBinaryCodec.deriveDataUpdate`
10
+ * (rc.8) canonicalizes the circe-encoded JSON **including null fields**.
11
+ * If the TypeScript client omits optional fields (undefined), the canonical
12
+ * JSON won't match and signature verification will fail.
13
+ *
14
+ * Schema mapping (Scala → TypeScript):
15
+ * - `Option[A] = None` → `null` (must be explicit, not undefined/absent)
16
+ * - `Option[A] = Some(v)` → `v`
17
+ * - `List.empty` → `[]`
18
+ * - `Map.empty` → `{}`
19
+ */
20
+ /**
21
+ * Normalize a State object for wire format
22
+ */
23
+ function normalizeState(state) {
24
+ return {
25
+ id: state.id,
26
+ isFinal: state.isFinal ?? false,
27
+ metadata: state.metadata ?? null,
28
+ };
29
+ }
30
+ /**
31
+ * Normalize a Transition object for wire format
32
+ */
33
+ function normalizeTransition(t) {
34
+ return {
35
+ from: t.from,
36
+ eventName: t.eventName,
37
+ to: t.to,
38
+ guard: t.guard ?? null,
39
+ actions: t.actions ?? null,
40
+ metadata: t.metadata ?? null,
41
+ };
42
+ }
43
+ /**
44
+ * Normalize a StateMachineDefinition for wire format
45
+ */
46
+ function normalizeDefinition(def) {
47
+ const states = def.states;
48
+ const normalizedStates = {};
49
+ if (states) {
50
+ for (const [key, state] of Object.entries(states)) {
51
+ normalizedStates[key] = normalizeState(state);
52
+ }
53
+ }
54
+ const transitions = def.transitions ?? [];
55
+ return {
56
+ states: normalizedStates,
57
+ initialState: def.initialState,
58
+ transitions: transitions.map(normalizeTransition),
59
+ metadata: def.metadata ?? null,
60
+ };
61
+ }
62
+ /**
63
+ * Normalize a CreateStateMachine message for wire format
64
+ *
65
+ * Ensures all Option fields are explicit null:
66
+ * - definition.metadata
67
+ * - definition.states[*].metadata
68
+ * - definition.transitions[*].guard
69
+ * - definition.transitions[*].actions
70
+ * - definition.transitions[*].metadata
71
+ * - parentFiberId
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * const message = normalizeCreateStateMachine({
76
+ * fiberId: '...',
77
+ * definition: { states: { INIT: { id: { value: 'INIT' }, isFinal: false } }, ... },
78
+ * initialData: {}
79
+ * });
80
+ * // message now has parentFiberId: null, definition.metadata: null, etc.
81
+ * ```
82
+ */
83
+ export function normalizeCreateStateMachine(msg) {
84
+ return {
85
+ fiberId: msg.fiberId,
86
+ definition: normalizeDefinition(msg.definition),
87
+ initialData: msg.initialData ?? {},
88
+ parentFiberId: msg.parentFiberId ?? null,
89
+ };
90
+ }
91
+ /**
92
+ * Normalize a TransitionStateMachine message for wire format
93
+ */
94
+ export function normalizeTransitionStateMachine(msg) {
95
+ return {
96
+ fiberId: msg.fiberId,
97
+ eventName: msg.eventName,
98
+ eventData: msg.eventData ?? null,
99
+ fiberOrdinal: msg.fiberOrdinal,
100
+ };
101
+ }
102
+ /**
103
+ * Normalize an ArchiveStateMachine message for wire format
104
+ */
105
+ export function normalizeArchiveStateMachine(msg) {
106
+ return {
107
+ fiberId: msg.fiberId,
108
+ reason: msg.reason ?? null,
109
+ };
110
+ }
111
+ /**
112
+ * Normalize any OttochainMessage wrapper for wire format.
113
+ *
114
+ * Detects the message type from the wrapper key and applies the
115
+ * appropriate normalization.
116
+ *
117
+ * @param message - OttochainMessage in wrapper format: `{ CreateStateMachine: {...} }`
118
+ * @returns Normalized message with all Option fields as explicit null
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const normalized = normalizeMessage({
123
+ * CreateStateMachine: { fiberId: '...', definition: {...}, initialData: {} }
124
+ * });
125
+ * const signed = await signDataUpdate(normalized, privateKey);
126
+ * ```
127
+ */
128
+ export function normalizeMessage(message) {
129
+ if ('CreateStateMachine' in message) {
130
+ return { CreateStateMachine: normalizeCreateStateMachine(message.CreateStateMachine) };
131
+ }
132
+ if ('TransitionStateMachine' in message) {
133
+ return { TransitionStateMachine: normalizeTransitionStateMachine(message.TransitionStateMachine) };
134
+ }
135
+ if ('ArchiveStateMachine' in message) {
136
+ return { ArchiveStateMachine: normalizeArchiveStateMachine(message.ArchiveStateMachine) };
137
+ }
138
+ // CreateScript and InvokeScript — pass through (no optional fields)
139
+ return message;
140
+ }