@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.
- package/build/abstraction/EvmCoinFinder.js +14 -2
- package/build/abstraction/EvmCoinFinder.js.map +1 -1
- package/build/abstraction/Identities.js +3 -1
- package/build/abstraction/Identities.js.map +1 -1
- package/build/bridge/nativeBridgeTypes.d.ts +4 -3
- package/build/bridge/nativeBridgeTypes.js.map +1 -1
- package/build/d402/client/types.d.ts +6 -3
- package/build/d402/server/D402Server.d.ts +25 -0
- package/build/d402/server/D402Server.js +63 -2
- package/build/d402/server/D402Server.js.map +1 -1
- package/build/d402/server/middleware.d.ts +7 -4
- package/build/d402/server/middleware.js.map +1 -1
- package/build/d402/server/types.d.ts +15 -7
- package/build/denomination/conversion.test.js +1 -1
- package/build/denomination/conversion.test.js.map +1 -1
- package/build/denomination/index.d.ts +3 -0
- package/build/denomination/index.js +6 -0
- package/build/denomination/index.js.map +1 -1
- package/build/denomination/networkInfo.d.ts +69 -0
- package/build/denomination/networkInfo.js +38 -0
- package/build/denomination/networkInfo.js.map +1 -0
- package/build/denomination/networkInfo.test.d.ts +1 -0
- package/build/denomination/networkInfo.test.js +28 -0
- package/build/denomination/networkInfo.test.js.map +1 -0
- package/build/denomination/roundTripHash.test.d.ts +1 -0
- package/build/denomination/roundTripHash.test.js +227 -0
- package/build/denomination/roundTripHash.test.js.map +1 -0
- package/build/denomination/serializerGate.d.ts +46 -0
- package/build/denomination/serializerGate.js +283 -0
- package/build/denomination/serializerGate.js.map +1 -0
- package/build/denomination/serializerGate.test.d.ts +1 -0
- package/build/denomination/serializerGate.test.js +245 -0
- package/build/denomination/serializerGate.test.js.map +1 -0
- package/build/encryption/zK/interactive/index.js +5 -1
- package/build/encryption/zK/interactive/index.js.map +1 -1
- package/build/escrow/EscrowTransaction.d.ts +36 -5
- package/build/escrow/EscrowTransaction.js +91 -10
- package/build/escrow/EscrowTransaction.js.map +1 -1
- package/build/instant_messaging/L2PSMessagingPeer.js +4 -1
- package/build/instant_messaging/L2PSMessagingPeer.js.map +1 -1
- package/build/ipfs/IPFSOperations.d.ts +37 -10
- package/build/ipfs/IPFSOperations.js +38 -8
- package/build/ipfs/IPFSOperations.js.map +1 -1
- package/build/storage/StorageProgram.d.ts +16 -8
- package/build/storage/StorageProgram.js +16 -8
- package/build/storage/StorageProgram.js.map +1 -1
- package/build/tlsnotary/TLSNotaryService.d.ts +19 -8
- package/build/tlsnotary/TLSNotaryService.js +22 -7
- package/build/tlsnotary/TLSNotaryService.js.map +1 -1
- package/build/tlsnotary/helpers.d.ts +11 -5
- package/build/tlsnotary/helpers.js +17 -5
- package/build/tlsnotary/helpers.js.map +1 -1
- package/build/types/blockchain/CustomCharges.d.ts +28 -16
- package/build/types/blockchain/CustomCharges.js +15 -5
- package/build/types/blockchain/CustomCharges.js.map +1 -1
- package/build/types/blockchain/GCREdit.d.ts +80 -3
- package/build/types/blockchain/NetworkParameters.d.ts +54 -0
- package/build/types/blockchain/NetworkParameters.js +9 -0
- package/build/types/blockchain/NetworkParameters.js.map +1 -0
- package/build/types/blockchain/Transaction.d.ts +21 -3
- package/build/types/blockchain/Transaction.js.map +1 -1
- package/build/types/blockchain/TransactionSubtypes/D402PaymentTransaction.d.ts +8 -3
- package/build/types/blockchain/TransactionSubtypes/NetworkUpgradeTransaction.d.ts +20 -0
- package/build/types/blockchain/TransactionSubtypes/NetworkUpgradeTransaction.js +2 -0
- package/build/types/blockchain/TransactionSubtypes/NetworkUpgradeTransaction.js.map +1 -0
- package/build/types/blockchain/TransactionSubtypes/NetworkUpgradeVoteTransaction.d.ts +13 -0
- package/build/types/blockchain/TransactionSubtypes/NetworkUpgradeVoteTransaction.js +2 -0
- package/build/types/blockchain/TransactionSubtypes/NetworkUpgradeVoteTransaction.js.map +1 -0
- package/build/types/blockchain/TransactionSubtypes/StorageProgramTransaction.d.ts +6 -5
- package/build/types/blockchain/TransactionSubtypes/StorageProgramTransaction.js +15 -3
- package/build/types/blockchain/TransactionSubtypes/StorageProgramTransaction.js.map +1 -1
- package/build/types/blockchain/TransactionSubtypes/ValidatorExitTransaction.d.ts +10 -0
- package/build/types/blockchain/TransactionSubtypes/ValidatorExitTransaction.js +2 -0
- package/build/types/blockchain/TransactionSubtypes/ValidatorExitTransaction.js.map +1 -0
- package/build/types/blockchain/TransactionSubtypes/ValidatorStakeTransaction.d.ts +15 -0
- package/build/types/blockchain/TransactionSubtypes/ValidatorStakeTransaction.js +2 -0
- package/build/types/blockchain/TransactionSubtypes/ValidatorStakeTransaction.js.map +1 -0
- package/build/types/blockchain/TransactionSubtypes/ValidatorUnstakeTransaction.d.ts +10 -0
- package/build/types/blockchain/TransactionSubtypes/ValidatorUnstakeTransaction.js +2 -0
- package/build/types/blockchain/TransactionSubtypes/ValidatorUnstakeTransaction.js.map +1 -0
- package/build/types/blockchain/TransactionSubtypes/index.d.ts +11 -1
- package/build/types/blockchain/TransactionSubtypes/index.js +7 -0
- package/build/types/blockchain/TransactionSubtypes/index.js.map +1 -1
- package/build/types/blockchain/TxFee.d.ts +16 -3
- package/build/types/blockchain/address.d.ts +23 -0
- package/build/types/blockchain/rawTransaction.d.ts +13 -4
- package/build/types/blockchain/statusNative.d.ts +9 -1
- package/build/types/bridge/bridgeTradePayload.d.ts +1 -1
- package/build/types/gls/StateChange.d.ts +29 -3
- package/build/types/index.d.ts +4 -2
- package/build/types/index.js.map +1 -1
- package/build/types/native/INativePayload.d.ts +6 -1
- package/build/types/validator/ValidatorTypes.d.ts +13 -0
- package/build/types/validator/ValidatorTypes.js +3 -0
- package/build/types/validator/ValidatorTypes.js.map +1 -0
- package/build/wallet/Wallet.d.ts +27 -1
- package/build/wallet/Wallet.js +30 -17
- package/build/wallet/Wallet.js.map +1 -1
- package/build/websdk/DemosTransactions.d.ts +78 -4
- package/build/websdk/DemosTransactions.js +253 -9
- package/build/websdk/DemosTransactions.js.map +1 -1
- package/build/websdk/GCRGeneration.d.ts +11 -2
- package/build/websdk/GCRGeneration.js +143 -14
- package/build/websdk/GCRGeneration.js.map +1 -1
- package/build/websdk/demosclass.d.ts +238 -26
- package/build/websdk/demosclass.js +434 -54
- package/build/websdk/demosclass.js.map +1 -1
- 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
|
-
*
|
|
125
|
-
*
|
|
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
|
-
*
|
|
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
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
*
|
|
304
|
-
*
|
|
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
|
-
|
|
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
|
-
* @
|
|
316
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
436
|
-
//
|
|
437
|
-
|
|
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
|
-
|
|
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
|
|
442
|
-
|
|
443
|
-
console.warn("[demos] fee derivation skipped:", e);
|
|
511
|
+
catch {
|
|
512
|
+
/* fall through */
|
|
444
513
|
}
|
|
445
|
-
|
|
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
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
|
558
|
-
(
|
|
559
|
-
(
|
|
560
|
-
// Only set the fee if no fees are already applied
|
|
561
|
-
|
|
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:
|
|
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
|
-
//
|
|
882
|
-
|
|
883
|
-
|
|
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(
|
|
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
|