@kynesyslabs/demosdk 2.12.1 → 3.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 (108) hide show
  1. package/build/abstraction/EvmCoinFinder.js +14 -2
  2. package/build/abstraction/EvmCoinFinder.js.map +1 -1
  3. package/build/abstraction/Identities.js +3 -1
  4. package/build/abstraction/Identities.js.map +1 -1
  5. package/build/bridge/nativeBridgeTypes.d.ts +4 -3
  6. package/build/bridge/nativeBridgeTypes.js.map +1 -1
  7. package/build/d402/client/types.d.ts +6 -3
  8. package/build/d402/server/D402Server.d.ts +25 -0
  9. package/build/d402/server/D402Server.js +63 -2
  10. package/build/d402/server/D402Server.js.map +1 -1
  11. package/build/d402/server/middleware.d.ts +7 -4
  12. package/build/d402/server/middleware.js.map +1 -1
  13. package/build/d402/server/types.d.ts +15 -7
  14. package/build/denomination/conversion.test.js +1 -1
  15. package/build/denomination/conversion.test.js.map +1 -1
  16. package/build/denomination/index.d.ts +3 -0
  17. package/build/denomination/index.js +6 -0
  18. package/build/denomination/index.js.map +1 -1
  19. package/build/denomination/networkInfo.d.ts +69 -0
  20. package/build/denomination/networkInfo.js +38 -0
  21. package/build/denomination/networkInfo.js.map +1 -0
  22. package/build/denomination/networkInfo.test.d.ts +1 -0
  23. package/build/denomination/networkInfo.test.js +28 -0
  24. package/build/denomination/networkInfo.test.js.map +1 -0
  25. package/build/denomination/roundTripHash.test.d.ts +1 -0
  26. package/build/denomination/roundTripHash.test.js +227 -0
  27. package/build/denomination/roundTripHash.test.js.map +1 -0
  28. package/build/denomination/serializerGate.d.ts +46 -0
  29. package/build/denomination/serializerGate.js +283 -0
  30. package/build/denomination/serializerGate.js.map +1 -0
  31. package/build/denomination/serializerGate.test.d.ts +1 -0
  32. package/build/denomination/serializerGate.test.js +245 -0
  33. package/build/denomination/serializerGate.test.js.map +1 -0
  34. package/build/encryption/zK/interactive/index.js +5 -1
  35. package/build/encryption/zK/interactive/index.js.map +1 -1
  36. package/build/escrow/EscrowTransaction.d.ts +36 -5
  37. package/build/escrow/EscrowTransaction.js +91 -10
  38. package/build/escrow/EscrowTransaction.js.map +1 -1
  39. package/build/instant_messaging/L2PSMessagingPeer.js +4 -1
  40. package/build/instant_messaging/L2PSMessagingPeer.js.map +1 -1
  41. package/build/ipfs/IPFSOperations.d.ts +37 -10
  42. package/build/ipfs/IPFSOperations.js +38 -8
  43. package/build/ipfs/IPFSOperations.js.map +1 -1
  44. package/build/storage/StorageProgram.d.ts +16 -8
  45. package/build/storage/StorageProgram.js +16 -8
  46. package/build/storage/StorageProgram.js.map +1 -1
  47. package/build/tlsnotary/TLSNotaryService.d.ts +19 -8
  48. package/build/tlsnotary/TLSNotaryService.js +22 -7
  49. package/build/tlsnotary/TLSNotaryService.js.map +1 -1
  50. package/build/tlsnotary/helpers.d.ts +11 -5
  51. package/build/tlsnotary/helpers.js +17 -5
  52. package/build/tlsnotary/helpers.js.map +1 -1
  53. package/build/types/blockchain/CustomCharges.d.ts +28 -16
  54. package/build/types/blockchain/CustomCharges.js +15 -5
  55. package/build/types/blockchain/CustomCharges.js.map +1 -1
  56. package/build/types/blockchain/GCREdit.d.ts +80 -3
  57. package/build/types/blockchain/NetworkParameters.d.ts +54 -0
  58. package/build/types/blockchain/NetworkParameters.js +9 -0
  59. package/build/types/blockchain/NetworkParameters.js.map +1 -0
  60. package/build/types/blockchain/Transaction.d.ts +21 -3
  61. package/build/types/blockchain/Transaction.js.map +1 -1
  62. package/build/types/blockchain/TransactionSubtypes/D402PaymentTransaction.d.ts +8 -3
  63. package/build/types/blockchain/TransactionSubtypes/NetworkUpgradeTransaction.d.ts +20 -0
  64. package/build/types/blockchain/TransactionSubtypes/NetworkUpgradeTransaction.js +2 -0
  65. package/build/types/blockchain/TransactionSubtypes/NetworkUpgradeTransaction.js.map +1 -0
  66. package/build/types/blockchain/TransactionSubtypes/NetworkUpgradeVoteTransaction.d.ts +13 -0
  67. package/build/types/blockchain/TransactionSubtypes/NetworkUpgradeVoteTransaction.js +2 -0
  68. package/build/types/blockchain/TransactionSubtypes/NetworkUpgradeVoteTransaction.js.map +1 -0
  69. package/build/types/blockchain/TransactionSubtypes/StorageProgramTransaction.d.ts +6 -5
  70. package/build/types/blockchain/TransactionSubtypes/StorageProgramTransaction.js +15 -3
  71. package/build/types/blockchain/TransactionSubtypes/StorageProgramTransaction.js.map +1 -1
  72. package/build/types/blockchain/TransactionSubtypes/ValidatorExitTransaction.d.ts +10 -0
  73. package/build/types/blockchain/TransactionSubtypes/ValidatorExitTransaction.js +2 -0
  74. package/build/types/blockchain/TransactionSubtypes/ValidatorExitTransaction.js.map +1 -0
  75. package/build/types/blockchain/TransactionSubtypes/ValidatorStakeTransaction.d.ts +15 -0
  76. package/build/types/blockchain/TransactionSubtypes/ValidatorStakeTransaction.js +2 -0
  77. package/build/types/blockchain/TransactionSubtypes/ValidatorStakeTransaction.js.map +1 -0
  78. package/build/types/blockchain/TransactionSubtypes/ValidatorUnstakeTransaction.d.ts +10 -0
  79. package/build/types/blockchain/TransactionSubtypes/ValidatorUnstakeTransaction.js +2 -0
  80. package/build/types/blockchain/TransactionSubtypes/ValidatorUnstakeTransaction.js.map +1 -0
  81. package/build/types/blockchain/TransactionSubtypes/index.d.ts +11 -1
  82. package/build/types/blockchain/TransactionSubtypes/index.js +7 -0
  83. package/build/types/blockchain/TransactionSubtypes/index.js.map +1 -1
  84. package/build/types/blockchain/TxFee.d.ts +16 -3
  85. package/build/types/blockchain/address.d.ts +23 -0
  86. package/build/types/blockchain/rawTransaction.d.ts +13 -4
  87. package/build/types/blockchain/statusNative.d.ts +9 -1
  88. package/build/types/bridge/bridgeTradePayload.d.ts +1 -1
  89. package/build/types/gls/StateChange.d.ts +29 -3
  90. package/build/types/index.d.ts +4 -2
  91. package/build/types/index.js.map +1 -1
  92. package/build/types/native/INativePayload.d.ts +6 -1
  93. package/build/types/validator/ValidatorTypes.d.ts +13 -0
  94. package/build/types/validator/ValidatorTypes.js +3 -0
  95. package/build/types/validator/ValidatorTypes.js.map +1 -0
  96. package/build/wallet/Wallet.d.ts +27 -1
  97. package/build/wallet/Wallet.js +30 -17
  98. package/build/wallet/Wallet.js.map +1 -1
  99. package/build/websdk/DemosTransactions.d.ts +78 -4
  100. package/build/websdk/DemosTransactions.js +253 -9
  101. package/build/websdk/DemosTransactions.js.map +1 -1
  102. package/build/websdk/GCRGeneration.d.ts +11 -2
  103. package/build/websdk/GCRGeneration.js +143 -14
  104. package/build/websdk/GCRGeneration.js.map +1 -1
  105. package/build/websdk/demosclass.d.ts +238 -26
  106. package/build/websdk/demosclass.js +434 -54
  107. package/build/websdk/demosclass.js.map +1 -1
  108. package/package.json +1 -1
@@ -15,6 +15,9 @@ import { web2Calls } from "./Web2Calls.js";
15
15
  import { hexToUint8Array, uint8ArrayToHex, UnifiedCrypto, } from "../encryption/unifiedCrypto.js";
16
16
  import { GCRGeneration } from "./GCRGeneration.js";
17
17
  import { Hashing } from "../encryption/Hashing.js";
18
+ import { OS_PER_DEM, demToOs, parseOsString, } from "../denomination/index.js";
19
+ import { serializeTransactionContent } from "../denomination/serializerGate.js";
20
+ import { SubDemPrecisionError, } from "../denomination/networkInfo.js";
18
21
  import * as bip39 from "@scure/bip39";
19
22
  import { wordList } from "../encryption/PQC/falconts/index.js";
20
23
  async function sleep(ms) {
@@ -55,6 +58,24 @@ export class Demos {
55
58
  this.rpc_url = null;
56
59
  /** Cached TLSNotary instance */
57
60
  this._tlsnotaryInstance = null;
61
+ this._cachedNetworkParameters = null;
62
+ this._cachedNetworkParametersAt = 0;
63
+ this._cachedNetworkParametersRpcUrl = null;
64
+ // P4 commit 3: per-instance cached fork status. `null` after a failed
65
+ // detection attempt (`_cachedNetworkInfoFailed = true`) means we have
66
+ // already assumed pre-fork; we keep this state until the rpc_url
67
+ // changes or the failed-cache TTL elapses, to avoid hammering an
68
+ // unreachable node, and warn exactly once.
69
+ //
70
+ // PR-86 myc#18 — keyed by rpc_url, mirroring `_cachedNetworkParametersRpcUrl`.
71
+ // Without this, a single Demos instance reused across two RPCs (one
72
+ // pre-fork, one post-fork) would lock onto the first answer and sign
73
+ // transactions with the wrong wire shape against the second.
74
+ this._cachedNetworkInfo = null;
75
+ this._cachedNetworkInfoRpcUrl = null;
76
+ this._cachedNetworkInfoFailed = false;
77
+ this._cachedNetworkInfoFailedAt = 0;
78
+ this._cachedNetworkInfoWarned = false;
58
79
  /** Connection status of the RPC URL */
59
80
  this.connected = false;
60
81
  this.dual_sign = false;
@@ -120,9 +141,12 @@ export class Demos {
120
141
  /**
121
142
  * Get a cost quote for an IPFS operation without submitting a transaction.
122
143
  *
123
- * Use this to estimate costs and populate custom_charges before signing.
124
- * The returned cost should be used as max_cost_dem in the transaction's
125
- * custom_charges field.
144
+ * Use this to estimate costs and populate `custom_charges` before signing.
145
+ * Pipe the response through `IPFSOperations.quoteToCustomCharges`
146
+ * (or `createCustomCharges`) to obtain a `max_cost_os` decimal-string
147
+ * suitable for the transaction's `custom_charges.ipfs.max_cost_os`
148
+ * field. The helpers handle both pre-fork (`cost_dem`) and
149
+ * post-fork (`cost_os`) node response shapes.
126
150
  *
127
151
  * @param fileSizeBytes - Size of file in bytes
128
152
  * @param operation - IPFS operation type ('IPFS_ADD', 'IPFS_PIN', or 'IPFS_UNPIN')
@@ -133,7 +157,6 @@ export class Demos {
133
157
  * ```typescript
134
158
  * // Get quote for add operation
135
159
  * const quote = await demos.ipfs.quote(content.length, 'IPFS_ADD')
136
- * console.log(`Cost: ${quote.cost_dem} DEM`)
137
160
  *
138
161
  * // Use quote to build transaction with cost control
139
162
  * const payload = IPFSOperations.createAddPayload(content, {
@@ -205,6 +228,17 @@ export class Demos {
205
228
  async connect(rpc_url) {
206
229
  const response = await axios.get(rpc_url);
207
230
  if (response.status == 200) {
231
+ // PR-86 myc#18: a fresh connect (or reconnect to a different
232
+ // RPC) must drop the per-rpc cached fork status so the next
233
+ // sign() re-detects against the new node. Don't reset the
234
+ // warn-once flag — operators have already seen the warning
235
+ // for this instance's lifetime.
236
+ if (this.rpc_url !== rpc_url) {
237
+ this._cachedNetworkInfo = null;
238
+ this._cachedNetworkInfoRpcUrl = null;
239
+ this._cachedNetworkInfoFailed = false;
240
+ this._cachedNetworkInfoFailedAt = 0;
241
+ }
208
242
  this.rpc_url = rpc_url;
209
243
  }
210
244
  this.connected = true;
@@ -285,41 +319,69 @@ export class Demos {
285
319
  }
286
320
  // !SECTION Connection and listeners
287
321
  /**
288
- * Generates a random MUID.
289
- *
290
- * @returns The MUID
322
+ * Generates a random MUID using a CSPRNG. Math.random is unsafe in any
323
+ * security-sensitive path (a predictable PRNG lets an attacker
324
+ * pre-compute MUIDs). 26 bytes → 52 hex chars, same length range as the
325
+ * legacy MUID format.
291
326
  */
292
327
  generateMuid() {
293
- const number_1 = Math.random().toString(36).substring(2, 15) +
294
- Math.random().toString(36).substring(2, 15);
295
- const number_2 = Math.random().toString(36).substring(2, 15) +
296
- Math.random().toString(36).substring(2, 15);
297
- const muid = number_1 + number_2;
298
- return muid;
328
+ const buf = new Uint8Array(26);
329
+ crypto.getRandomValues(buf);
330
+ return Array.from(buf, b => b.toString(16).padStart(2, "0")).join("");
299
331
  }
300
332
  /**
301
333
  * Create a signed DEMOS transaction to send native tokens to a given address.
302
334
  *
303
- * @param to - The reciever
304
- * @param amount - The amount in DEM
335
+ * P4 dual-input:
336
+ * - `bigint` (preferred, post-v3): amount in OS (smallest unit;
337
+ * 1 DEM = 10^9 OS). Use `denomination.demToOs(...)` to convert
338
+ * a human-readable DEM input.
339
+ * - `number` (deprecated, v2 callers): amount in DEM. Auto-converted
340
+ * to OS internally via `OS_PER_DEM`. Will be removed in v4.
341
+ *
342
+ * Sub-DEM precision is rejected with `SubDemPrecisionError` when
343
+ * the connected node is pre-fork — its legacy DEM-`number` wire
344
+ * cannot carry < 1 DEM and silent truncation is unacceptable. The
345
+ * caller can either round to a whole DEM or upgrade the target
346
+ * node.
347
+ *
348
+ * @example
349
+ * ```ts
350
+ * import { denomination } from "@kynesyslabs/demosdk"
351
+ * await demos.pay("0x...", denomination.demToOs(100)) // 100 DEM
352
+ * await demos.pay("0x...", 100_000_000_000n) // raw OS
353
+ * await demos.pay("0x...", 100) // legacy DEM number (deprecated)
354
+ * ```
305
355
  *
356
+ * @param to - The receiver address (0x-prefixed hex).
357
+ * @param amount - DEM `number` (legacy) or OS `bigint` (preferred).
306
358
  * @returns The signed transaction.
307
359
  */
308
- pay(to, amount) {
360
+ async pay(to, amount) {
309
361
  required(this.keypair, "Wallet not connected");
310
- return DemosTransactions.pay(to, amount, this);
362
+ const amountOs = typeof amount === "bigint" ? amount : demToOs(amount);
363
+ await this._assertAmountAcceptableOnTargetNode(amountOs);
364
+ return DemosTransactions.pay(to, amountOs, this);
311
365
  }
312
366
  /**
313
367
  * Create a signed DEMOS transaction to send native tokens to a given address.
314
368
  *
315
- * @param to - The reciever
316
- * @param amount - The amount in DEM
369
+ * Alias of {@link pay}. Same dual-input semantics — `bigint` is the
370
+ * preferred OS shape; `number` is the deprecated legacy DEM shape.
317
371
  *
372
+ * @example
373
+ * ```ts
374
+ * import { denomination } from "@kynesyslabs/demosdk"
375
+ * await demos.transfer("0x...", denomination.demToOs("1.5")) // 1.5 DEM
376
+ * await demos.transfer("0x...", 1_500_000_000n) // raw OS
377
+ * ```
378
+ *
379
+ * @param to - The receiver address (0x-prefixed hex).
380
+ * @param amount - DEM `number` (legacy) or OS `bigint` (preferred).
318
381
  * @returns The signed transaction.
319
382
  */
320
- transfer(to, amount) {
321
- required(this.keypair, "Wallet not connected");
322
- return DemosTransactions.pay(to, amount, this);
383
+ async transfer(to, amount) {
384
+ return this.pay(to, amount);
323
385
  }
324
386
  /**
325
387
  * Create a signed DEMOS transaction to store binary data on the blockchain.
@@ -432,17 +494,49 @@ export class Demos {
432
494
  raw_tx.content.from = "0x" + raw_tx.content.from;
433
495
  }
434
496
  raw_tx.content.gcr_edits = await GCRGeneration.generate(raw_tx);
435
- // We derive the network fee from GCR edits (sum of sender balance removals minus `amount`).
436
- // This is a pragmatic interim approach; ideally the node should authoritatively return fees
437
- // to avoid drift between SDK and node logic.
497
+ // Node is source of truth for governance-driven fees; edits-derived
498
+ // calc kicks in only if RPC is unreachable or endpoint is missing.
499
+ let appliedFromNode = false;
438
500
  try {
439
- raw_tx = this._calculateAndApplyGasFee(raw_tx);
501
+ const params = await this._getNetworkParametersCached();
502
+ if (params && typeof params.networkFee === "number") {
503
+ raw_tx.content.transaction_fee = {
504
+ network_fee: params.networkFee,
505
+ rpc_fee: typeof params.rpcFee === "number" ? params.rpcFee : 0,
506
+ additional_fee: 0,
507
+ };
508
+ appliedFromNode = true;
509
+ }
440
510
  }
441
- catch (e) {
442
- // Non-fatal: if fee derivation fails, continue without modifying fees
443
- console.warn("[demos] fee derivation skipped:", e);
511
+ catch {
512
+ /* fall through */
444
513
  }
445
- raw_tx.hash = Hashing.sha256(JSON.stringify(raw_tx.content));
514
+ if (!appliedFromNode) {
515
+ try {
516
+ raw_tx = this._calculateAndApplyGasFee(raw_tx);
517
+ }
518
+ catch (e) {
519
+ console.warn("[demos] fee derivation skipped:", e);
520
+ }
521
+ }
522
+ // P4 commit 3: route hashing through the dual-format serializerGate
523
+ // with the cached fork status. The first `sign()` call after
524
+ // `connect()` triggers `getNetworkInfo`; subsequent signs reuse
525
+ // the cached result. On RPC failure the cache stays in
526
+ // "assume pre-fork" mode for the instance's lifetime.
527
+ const isPostFork = await this._isPostForkCached();
528
+ const serialized = serializeTransactionContent(raw_tx.content, isPostFork);
529
+ raw_tx.hash = Hashing.sha256(serialized);
530
+ // Normalise content to the wire shape the hash committed to.
531
+ // Without this, internal `bigint` carriers in `tx.content.amount`,
532
+ // `tx.content.transaction_fee.*`, and `gcr_edits[].amount` would
533
+ // leak into downstream `JSON.stringify` (axios body serialisation
534
+ // in confirm/broadcast), throwing TypeError on bigint or sending
535
+ // a different byte-shape than the one we just signed (which the
536
+ // node rejects as InvalidSignature). `JSON.parse(serialized)`
537
+ // round-trips through the canonical post-fork-or-pre-fork shape
538
+ // and matches the bytes hashed.
539
+ raw_tx.content = JSON.parse(serialized);
446
540
  const signature = await this.crypto.sign(this.algorithm, new TextEncoder().encode(raw_tx.hash));
447
541
  // INFO: We only dual-sign when signing with PQC keypairs
448
542
  let dual_sign = this.dual_sign && this.algorithm !== "ed25519";
@@ -511,12 +605,24 @@ export class Demos {
511
605
  /**
512
606
  * @private
513
607
  * Calculates and applies the gas fee for a transaction (SDK-level fallback).
514
- * NOTE: We infer the fee by analyzing the generated GCR (Gas Consumption Record) edits:
515
- * - Sum all "balance" edits with operation "remove" for the sender
516
- * - Subtract the declared transaction `amount`
517
- * The remainder is treated as the network fee. If a fee already exists on the tx, we only raise
518
- * `network_fee` if the newly inferred fee is higher (prevents double-charging on re-sign).
519
- * This is an interim approach; the preferred design is for the node to return fees explicitly.
608
+ *
609
+ * NOTE: We infer the fee by analyzing the generated GCR (Gas Consumption
610
+ * Record) edits:
611
+ * - Sum all "balance" edits with operation "remove" for the sender.
612
+ * - Subtract the declared transaction `amount`.
613
+ * The remainder is treated as the network fee. If a fee already exists on
614
+ * the tx, we only raise `network_fee` if the newly inferred fee is higher
615
+ * (prevents double-charging on re-sign). This is an interim approach; the
616
+ * preferred design is for the node to return fees explicitly.
617
+ *
618
+ * P4: arithmetic uses `bigint` internally so OS-magnitude amounts
619
+ * (~10^19 OS for whole-network supply) don't overflow JS `number`'s
620
+ * 2^53 safe-integer ceiling. The function tolerates legacy `number`
621
+ * (DEM) and post-fork `string` (OS) inputs in any field, normalises
622
+ * to OS-bigint via `_coerceWireAmountToOs`, then writes the
623
+ * derived fee back in DEM-`number` (legacy wire) — the
624
+ * serializerGate (P4 commit 2) re-encodes to OS at hash time when
625
+ * the connected node is post-fork.
520
626
  *
521
627
  * @param raw_tx - The transaction for which to calculate the fee.
522
628
  * @returns The updated transaction with the fee applied.
@@ -530,23 +636,28 @@ export class Demos {
530
636
  // INFO: The gas fee is calculated by summing up all "remove" balance edits for the sender's account
531
637
  // and then subtracting the actual transaction amount. This gives the value of the fee.
532
638
  const sender = (raw_tx.content.from_ed25519_address ?? "").toLowerCase();
533
- const totalRemoved = edits.reduce((sum, edit) => {
639
+ let totalRemovedOs = 0n;
640
+ for (const edit of edits) {
534
641
  try {
535
642
  if (edit.type === "balance" &&
536
643
  edit.operation === "remove" &&
537
644
  typeof edit.account === "string" &&
538
645
  edit.account.toLowerCase() === sender) {
539
- const amt = Number(edit.amount);
540
- return sum + (Number.isFinite(amt) ? amt : 0);
646
+ totalRemovedOs += Demos._coerceWireAmountToOs(edit.amount);
541
647
  }
542
648
  }
543
649
  catch {
544
650
  // skip malformed entries
545
651
  }
546
- return sum;
547
- }, 0);
548
- const txAmt = Number(raw_tx.content.amount ?? 0);
549
- const calculatedFee = Math.max(totalRemoved - (Number.isFinite(txAmt) ? txAmt : 0), 0);
652
+ }
653
+ let txAmtOs = 0n;
654
+ try {
655
+ txAmtOs = Demos._coerceWireAmountToOs(raw_tx.content.amount ?? 0);
656
+ }
657
+ catch {
658
+ txAmtOs = 0n;
659
+ }
660
+ const calculatedFeeOs = totalRemovedOs > txAmtOs ? totalRemovedOs - txAmtOs : 0n;
550
661
  // INFO: This logic handles both initial fee creation and accumulation for re-signed transactions.
551
662
  // To avoid fee accumulation, a new transaction object should be created for each signing.
552
663
  const existing = raw_tx.content.transaction_fee ?? {
@@ -554,19 +665,50 @@ export class Demos {
554
665
  rpc_fee: 0,
555
666
  additional_fee: 0,
556
667
  };
557
- const totalExisting = (Number(existing.network_fee) || 0) +
558
- (Number(existing.rpc_fee) || 0) +
559
- (Number(existing.additional_fee) || 0);
560
- // Only set the fee if no fees are already applied
561
- if (totalExisting === 0) {
668
+ const totalExistingOs = Demos._coerceWireAmountToOs(existing.network_fee ?? 0) +
669
+ Demos._coerceWireAmountToOs(existing.rpc_fee ?? 0) +
670
+ Demos._coerceWireAmountToOs(existing.additional_fee ?? 0);
671
+ // Only set the fee if no fees are already applied. We emit the
672
+ // legacy DEM-number wire shape; the serializerGate normalises to
673
+ // OS-string post-fork. Convert OS bigint back to DEM number,
674
+ // assuming the inferred fee is a whole multiple of OS_PER_DEM
675
+ // (true for current 1-DEM gas + 1-DEM-per-KB storage formulas).
676
+ if (totalExistingOs === 0n) {
677
+ const feeDem = Number(calculatedFeeOs / OS_PER_DEM);
562
678
  raw_tx.content.transaction_fee = {
563
- network_fee: calculatedFee,
679
+ network_fee: feeDem,
564
680
  rpc_fee: 0,
565
681
  additional_fee: 0,
566
682
  };
567
683
  }
568
684
  return raw_tx;
569
685
  }
686
+ /**
687
+ * Coerce a wire-format amount/fee value (legacy DEM `number`,
688
+ * post-fork OS decimal `string`, or in-flight `bigint`) to an OS
689
+ * `bigint`. Used by `_calculateAndApplyGasFee` and other internal
690
+ * arithmetic paths to stay in OS-bigint regardless of which wire
691
+ * shape the caller produced. Mirrors the node's `toOsBigint` helper
692
+ * in `forks/serializerGate.ts`.
693
+ *
694
+ * @internal
695
+ */
696
+ static _coerceWireAmountToOs(value) {
697
+ if (typeof value === "bigint") {
698
+ return value;
699
+ }
700
+ if (typeof value === "string") {
701
+ // Already on the wire as OS decimal string.
702
+ return parseOsString(value);
703
+ }
704
+ if (typeof value === "number") {
705
+ if (!Number.isFinite(value))
706
+ return 0n;
707
+ // Pre-fork DEM number → OS bigint.
708
+ return demToOs(value);
709
+ }
710
+ return 0n;
711
+ }
570
712
  // L2PS calls are defined here
571
713
  /**
572
714
  * Single transport wrapper for axios.post against the Demos RPC node.
@@ -871,6 +1013,18 @@ export class Demos {
871
1013
  /**
872
1014
  * Get information about an address.
873
1015
  *
1016
+ * P4: `balance` is `bigint` in **OS** (smallest unit, 1 DEM = 10^9 OS).
1017
+ * Use `denomination.osToDem(info.balance)` for display.
1018
+ *
1019
+ * @example
1020
+ * ```ts
1021
+ * import { denomination } from "@kynesyslabs/demosdk"
1022
+ * const info = await demos.getAddressInfo("0x...")
1023
+ * if (info) {
1024
+ * console.log("balance:", denomination.osToDem(info.balance), "DEM")
1025
+ * }
1026
+ * ```
1027
+ *
874
1028
  * @param address - The address
875
1029
  */
876
1030
  async getAddressInfo(address) {
@@ -878,13 +1032,13 @@ export class Demos {
878
1032
  address,
879
1033
  });
880
1034
  if (info) {
881
- // REVIEW Fix for when the balance is 0 (see FIXME below)
882
- if (!info.balance) {
883
- info.balance = 0;
884
- }
1035
+ // Balance can come back as `null`/`undefined`/`0`/`"0"`/
1036
+ // bigint-string. `BigInt(null)` throws, so coalesce to 0
1037
+ // before parsing.
1038
+ const rawBalance = info.balance ?? 0;
885
1039
  return {
886
1040
  ...info,
887
- balance: BigInt(info.balance), // FIXME This fails when the balance is 0
1041
+ balance: BigInt(rawBalance),
888
1042
  };
889
1043
  }
890
1044
  return null;
@@ -910,6 +1064,225 @@ export class Demos {
910
1064
  }
911
1065
  return 0;
912
1066
  }
1067
+ /**
1068
+ * Get a validator's current record (stake, status, unstake timestamps).
1069
+ * Returns null if the address is not (and never was) a validator.
1070
+ */
1071
+ async getValidatorInfo(address) {
1072
+ return (await this.nodeCall("getValidatorInfo", {
1073
+ address,
1074
+ }));
1075
+ }
1076
+ /**
1077
+ * List validators at a given block (defaults to the current head). Only
1078
+ * returns validators whose `valid_at` block is <= the queried block and
1079
+ * whose status is still active.
1080
+ */
1081
+ async getValidators(blockNumber) {
1082
+ return ((await this.nodeCall("getValidators", {
1083
+ blockNumber,
1084
+ })) ?? []);
1085
+ }
1086
+ /**
1087
+ * Convenience: return a single validator's current staked amount as a
1088
+ * bigint-encoded string. Returns `"0"` for non-validators.
1089
+ */
1090
+ async getStakedAmount(address) {
1091
+ const v = await this.nodeCall("getStakedAmount", { address });
1092
+ return typeof v === "string" ? v : "0";
1093
+ }
1094
+ // SECTION Governance (stackable-genesis, Phase 1)
1095
+ /**
1096
+ * Returns the currently-active NetworkParameters — the result of folding
1097
+ * every `active` NetworkUpgrade over the genesis defaults.
1098
+ */
1099
+ async getNetworkParameters() {
1100
+ return (await this.nodeCall("getNetworkParameters"));
1101
+ }
1102
+ /**
1103
+ * Returns network parameters with a short-lived TTL cache to reduce RPC
1104
+ * calls during signing. Cache is scoped to the active `rpc_url` — a
1105
+ * network change within TTL invalidates the entry.
1106
+ *
1107
+ * @param ttlMs - Cache TTL in milliseconds (default: 30_000).
1108
+ * @returns The cached `NetworkParameters`, or `null` if not connected /
1109
+ * the response shape is invalid.
1110
+ */
1111
+ async _getNetworkParametersCached(ttlMs = 30000) {
1112
+ if (!this.rpc_url)
1113
+ return null;
1114
+ const now = Date.now();
1115
+ // Reuse the cached entry only if the underlying node hasn't changed.
1116
+ // Without the rpc-url guard, switching networks within TTL applies
1117
+ // the previous chain's fees to the new one's signed transactions.
1118
+ if (this._cachedNetworkParameters &&
1119
+ this._cachedNetworkParametersRpcUrl === this.rpc_url &&
1120
+ now - this._cachedNetworkParametersAt < ttlMs) {
1121
+ return this._cachedNetworkParameters;
1122
+ }
1123
+ const fresh = await this.getNetworkParameters();
1124
+ // Validate shape before caching: nodeCall() can return a truthy
1125
+ // RPC error envelope on transient failures; caching that would
1126
+ // freeze the SDK on locally-derived fees for the full TTL.
1127
+ if (fresh &&
1128
+ typeof fresh === "object" &&
1129
+ typeof fresh.networkFee === "number" &&
1130
+ typeof fresh.rpcFee === "number") {
1131
+ this._cachedNetworkParameters = fresh;
1132
+ this._cachedNetworkParametersAt = now;
1133
+ this._cachedNetworkParametersRpcUrl = this.rpc_url;
1134
+ return fresh;
1135
+ }
1136
+ return null;
1137
+ }
1138
+ // SECTION Fork detection (P4 commit 3)
1139
+ /**
1140
+ * Fetches the connected node's per-fork activation status.
1141
+ *
1142
+ * Mirrors the node's `getNetworkInfo` `nodeCall`
1143
+ * (`libs/network/handlers/forkHandlers.ts`). The response carries
1144
+ * activation height, current chain head, and the `activated` boolean
1145
+ * for every known fork.
1146
+ *
1147
+ * Caches the result on this `Demos` instance for the instance's
1148
+ * lifetime (no TTL). To re-fetch after a node upgrade, construct a
1149
+ * fresh `Demos` instance.
1150
+ *
1151
+ * On RPC failure (404, malformed response, network error), this
1152
+ * method returns `null` and the SDK assumes pre-fork wire format.
1153
+ * A `console.warn` is emitted exactly once per `Demos` instance
1154
+ * recommending the operator upgrade the target node.
1155
+ *
1156
+ * @returns The fork-status payload, or `null` if the RPC failed.
1157
+ */
1158
+ async getNetworkInfo() {
1159
+ // PR-86 myc#18: cache is keyed by rpc_url. A stale entry from a
1160
+ // previously-connected RPC must not satisfy a lookup against
1161
+ // the current one. Switch detection happens here (rather than
1162
+ // only in connect()) so callers that mutate rpc_url via other
1163
+ // paths still see correct behaviour.
1164
+ if (this._cachedNetworkInfo &&
1165
+ this._cachedNetworkInfoRpcUrl === this.rpc_url) {
1166
+ return this._cachedNetworkInfo;
1167
+ }
1168
+ if (this._cachedNetworkInfo &&
1169
+ this._cachedNetworkInfoRpcUrl !== this.rpc_url) {
1170
+ // RPC has changed since we cached — invalidate before re-fetching.
1171
+ this._cachedNetworkInfo = null;
1172
+ this._cachedNetworkInfoRpcUrl = null;
1173
+ this._cachedNetworkInfoFailed = false;
1174
+ this._cachedNetworkInfoFailedAt = 0;
1175
+ }
1176
+ // Honour the failed-cache TTL so a transient outage doesn't lock
1177
+ // the instance into pre-fork mode forever. After the TTL we'll
1178
+ // retry; the warn-once flag is sticky regardless.
1179
+ if (this._cachedNetworkInfoFailed &&
1180
+ this._cachedNetworkInfoRpcUrl === this.rpc_url &&
1181
+ Date.now() - this._cachedNetworkInfoFailedAt <
1182
+ Demos._NETWORK_INFO_FAILURE_TTL_MS) {
1183
+ return null;
1184
+ }
1185
+ let fresh = null;
1186
+ try {
1187
+ fresh = await this.nodeCall("getNetworkInfo");
1188
+ }
1189
+ catch {
1190
+ // call() already swallows transport errors, but be defensive
1191
+ // in case future nodeCall() rewrites surface them.
1192
+ fresh = null;
1193
+ }
1194
+ if (fresh &&
1195
+ typeof fresh === "object" &&
1196
+ fresh.forks &&
1197
+ fresh.forks.osDenomination &&
1198
+ typeof fresh.forks.osDenomination.activated ===
1199
+ "boolean") {
1200
+ this._cachedNetworkInfo = fresh;
1201
+ this._cachedNetworkInfoRpcUrl = this.rpc_url;
1202
+ // A successful detection clears any stale failure memo for
1203
+ // the current rpc_url.
1204
+ this._cachedNetworkInfoFailed = false;
1205
+ this._cachedNetworkInfoFailedAt = 0;
1206
+ return this._cachedNetworkInfo;
1207
+ }
1208
+ // Cache the failure (with TTL) so we don't hammer an unreachable /
1209
+ // pre-P3c node on every sign(). Warn once per instance.
1210
+ this._cachedNetworkInfoFailed = true;
1211
+ this._cachedNetworkInfoFailedAt = Date.now();
1212
+ this._cachedNetworkInfoRpcUrl = this.rpc_url;
1213
+ if (!this._cachedNetworkInfoWarned) {
1214
+ this._cachedNetworkInfoWarned = true;
1215
+ console.warn("getNetworkInfo unavailable on target node — assuming pre-fork wire format. Upgrade the node to a post-fork-aware version (>= the version that ships with forkHandlers). This fallback path is deprecated and will be removed in v4.");
1216
+ }
1217
+ return null;
1218
+ }
1219
+ /**
1220
+ * @internal
1221
+ * Cached fork-status accessor. Returns `osDenomination` activation
1222
+ * status as a boolean. `false` is the safe default (legacy wire
1223
+ * format) when the node is unreachable or pre-P3c.
1224
+ */
1225
+ async _isPostForkCached() {
1226
+ const info = await this.getNetworkInfo();
1227
+ return Boolean(info?.forks?.osDenomination?.activated);
1228
+ }
1229
+ /**
1230
+ * @internal
1231
+ * Reset the cached fork status. Intended for tests; production code
1232
+ * should construct a fresh `Demos` instance instead.
1233
+ */
1234
+ _resetForkStatusCacheForTesting() {
1235
+ this._cachedNetworkInfo = null;
1236
+ this._cachedNetworkInfoRpcUrl = null;
1237
+ this._cachedNetworkInfoFailed = false;
1238
+ this._cachedNetworkInfoFailedAt = 0;
1239
+ this._cachedNetworkInfoWarned = false;
1240
+ }
1241
+ /**
1242
+ * @internal
1243
+ * Sub-DEM precision guard. Run by every public-API entry point that
1244
+ * takes a user-supplied OS amount before tx construction. Throws
1245
+ * `SubDemPrecisionError` when the connected node is pre-fork and
1246
+ * the amount carries sub-DEM precision (would silently truncate on
1247
+ * the legacy DEM-`number` wire).
1248
+ *
1249
+ * @param amountOs - The OS amount the caller is sending.
1250
+ */
1251
+ async _assertAmountAcceptableOnTargetNode(amountOs) {
1252
+ const isPostFork = await this._isPostForkCached();
1253
+ if (isPostFork)
1254
+ return;
1255
+ const remainder = amountOs % OS_PER_DEM;
1256
+ if (remainder !== 0n) {
1257
+ throw new SubDemPrecisionError(amountOs, remainder);
1258
+ }
1259
+ }
1260
+ /**
1261
+ * Lists currently-open proposals (pending tally or activating after
1262
+ * approval). Rejected/active historical proposals are not included.
1263
+ */
1264
+ async getActiveProposals() {
1265
+ return ((await this.nodeCall("getActiveProposals")) ??
1266
+ []);
1267
+ }
1268
+ /**
1269
+ * Returns the live vote tally for a specific proposal — total snapshot
1270
+ * weight, approve/reject breakdowns, per-validator votes, threshold, and
1271
+ * a `passed` flag.
1272
+ */
1273
+ async getProposalVotes(proposalId) {
1274
+ return (await this.nodeCall("getProposalVotes", {
1275
+ proposalId,
1276
+ }));
1277
+ }
1278
+ /**
1279
+ * Returns the ordered history of proposals whose status has reached
1280
+ * `active`. Ordered by `effectiveAtBlock` ASC, then `proposalId` ASC.
1281
+ */
1282
+ async getUpgradeHistory() {
1283
+ return ((await this.nodeCall("getUpgradeHistory")) ??
1284
+ []);
1285
+ }
913
1286
  /**
914
1287
  * Disconnects from the RPC URL and the wallet.
915
1288
  */
@@ -996,4 +1369,11 @@ export class Demos {
996
1369
  }
997
1370
  }
998
1371
  Demos._instance = null;
1372
+ /**
1373
+ * TTL for the failed-detection memo. After this elapses we re-attempt
1374
+ * `getNetworkInfo` so a transient outage doesn't poison the instance
1375
+ * forever. The warn-once flag is sticky across retries — operators
1376
+ * still see the warning exactly once per instance lifetime.
1377
+ */
1378
+ Demos._NETWORK_INFO_FAILURE_TTL_MS = 30000;
999
1379
  //# sourceMappingURL=demosclass.js.map