@quantovainc/qcore 0.1.33 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ npm install @quantovainc/qcore
16
16
  ## Quickstart
17
17
 
18
18
  ```js
19
- const { Client, core, generateSeed } = require('@quantovainc/qcore');
19
+ const { Client, Network, core, generateSeed } = require('@quantovainc/qcore');
20
20
 
21
21
  const GATEWAY = 'https://rpc-testnet.quantova.org';
22
22
 
@@ -32,7 +32,7 @@ async function main() {
32
32
 
33
33
  // Open a client against a node you trust. The address above is the public testnet
34
34
  // gateway. A remote gateway must be https, plaintext http is allowed only to a loopback node.
35
- const client = new Client(GATEWAY);
35
+ const client = new Client(GATEWAY, { network: Network.testnet() });
36
36
 
37
37
  // A fixed ceiling your app is willing to pay in fees, chosen here and never read back from the gateway.
38
38
  const MAX_FEE_QUON = '2000';
@@ -108,6 +108,11 @@ const seed = generateSeed(); // thirty two random bytes as hex f
108
108
  const phrase = core.mnemonicFromSeed(seed); // the only backup, shown once and kept on the device
109
109
  ```
110
110
 
111
+ `core.seedFromMnemonic(phrase)` restores the seed from the twenty four words. It ignores case and extra
112
+ spacing and applies Unicode NFKD before it looks a word up, so a phrase pasted with capitals, non breaking
113
+ spaces, or full width letters restores the same seed. A standard BIP-39 phrase from another wallet is
114
+ refused with its own error, since a Quantova phrase carries a SHA3 checksum and cannot be restored from it.
115
+
111
116
  ## Using it
112
117
 
113
118
  The last argument to a Client signing call, transfer, register, call, or callSignedOrder, is a fee
@@ -122,9 +127,23 @@ same reason as the amount, because a number silently rounds above 2^53 and could
122
127
  than you intended.
123
128
 
124
129
  This comparison lives in the Client and only in the Client. The raw core.* signing functions, such as
125
- core.sign_transfer, take the actual fee that goes on the wire as their last argument, not a ceiling, and
126
- do no gateway comparison at all. A developer who calls core.* directly has no fee cap, so reach for the
127
- Client whenever a gateway you do not control reports the fee.
130
+ core.sign_transfer, take the actual fee that goes on the wire, not a ceiling, and do no gateway
131
+ comparison at all. A developer who calls core.* directly has no fee cap, so reach for the Client whenever
132
+ a gateway you do not control reports the fee.
133
+
134
+ A contract call pays one transfer fee for every started 1210 units of its meter limit, and the chain
135
+ refuses a call that pays less. The raw call signers, core.sign_call, core.signPayableCall, and
136
+ core.signAssetCall, take the transfer fee the gateway reports as their last argument and refuse to sign a
137
+ fee below `vmCallFee(transferFee, meterLimit)`, a meter limit outside 1210 to 12500000, or call arguments
138
+ above 128 KiB. Every raw signer also refuses a validity deadline of zero, which would never expire, and
139
+ core.sign_transfer refuses a transfer to the sending account itself. Numbers passed to core.* are decimal
140
+ digit strings, non negative BigInt values, or non negative safe integers, and anything else is refused
141
+ before the seed reaches the compiled core.
142
+
143
+ A Client opened for a plain url with no network signs only for a private chain. It refuses the public
144
+ testnet and any `Q-main-net-` chain until you name the network, with `Network.testnet()`, a mainnet
145
+ network and `acknowledgeMainnet: true`, or `expectedChainId`. An expected nonce you pass must be the
146
+ nonce the gateway reports, because the chain admits only the next nonce of an account.
128
147
 
129
148
  ```js
130
149
  const { Client, generateSeed } = require('@quantovainc/qcore');
@@ -157,13 +176,18 @@ await client.register(seed, 0, MAX_FEE_QUON);
157
176
  A call that also moves value, such as a payable contract call, reaches for `core.signPayableCall`
158
177
  directly. It takes the value and the chain id as explicit arguments, the value as a decimal string or a
159
178
  BigInt for the same reason as an amount, and the chain id from `core.localChainId()`,
160
- `core.mainnetChainId()`, or `core.testnetChainId()` so the network is never a magic number.
179
+ `core.mainnetChainId()`, or `core.testnetChainId()` so the network is never a magic number. It also
180
+ takes the validity deadline, the height after which the chain drops the transaction, from `validUntil`,
181
+ and the transfer fee the gateway reports so the call fee can be checked against the meter limit.
161
182
 
162
183
  ```js
163
- const { core } = require('@quantovainc/qcore');
184
+ const { core, validUntil, vmCallFee } = require('@quantovainc/qcore');
164
185
 
186
+ const info = await client.nodeInfo();
187
+ const transferFee = info.fee.transfer_quon;
188
+ const fee = vmCallFee(transferFee, meterLimit);
165
189
  const signed = JSON.parse(
166
- core.signPayableCall(seed, 0n, contract, argsHex, nonce, meterLimit, fee, '1000', core.testnetChainId()),
190
+ core.signPayableCall(seed, 0n, contract, argsHex, nonce, meterLimit, fee, '1000', core.testnetChainId(), validUntil(info), transferFee),
167
191
  );
168
192
  ```
169
193
 
@@ -197,7 +221,7 @@ The offline test scripts run with `npm test`, and they need no network and no ru
197
221
 
198
222
  ## Conformance vectors
199
223
 
200
- The frozen conformance vectors live in the [conformance](conformance) folder of this repository. The address derivation is in `conformance/address.derivation.json`, the transfer in `conformance/transaction.transfer.json`, and the payable call in `conformance/transaction.payable.json`. The offline test `test-conformance.js` signs from these vectors and checks the binding matches them byte for byte, so a change that would move a signed transaction fails the test.
224
+ The frozen conformance vectors live in the [conformance](conformance) folder of this repository. The address derivation is in `conformance/address.derivation.json`, the transfer in `conformance/transaction.transfer.json`, and the payable call in `conformance/transaction.payable.json`. The frozen transactions were signed with a validity deadline of zero, which the binding now refuses, so each transaction vector also carries a `bounded` companion signed at a real deadline. The offline test `test-conformance.js` signs from these vectors, checks the binding matches the bounded transaction byte for byte, and checks its body is the frozen body with only the deadline moved, so a change that would move a signed transaction fails the test.
201
225
 
202
226
  ## Examples
203
227
 
package/browser.mjs CHANGED
@@ -1,9 +1,11 @@
1
1
  // Copyright 2026 Quantova Inc
2
2
  // SPDX-License-Identifier: Apache-2.0 OR MIT
3
3
 
4
- import * as core from './pkg/qcore_js.js';
5
- import { makeClient, generateSeed, validUntil, Network } from './shared.mjs';
4
+ import * as raw from './pkg/qcore_js.js';
5
+ import { makeClient, wrapCore, generateSeed, validUntil, Network } from './shared.mjs';
6
6
 
7
+ const core = wrapCore(raw);
7
8
  const Client = makeClient(core);
9
+ const vmCallFee = core.vmCallFee;
8
10
 
9
- export { Client, core, generateSeed, validUntil, Network };
11
+ export { Client, core, generateSeed, validUntil, vmCallFee, Network };
package/index.d.ts CHANGED
@@ -47,106 +47,113 @@ export interface SignedOrderResult {
47
47
  outcome: any;
48
48
  }
49
49
 
50
+ export type WholeNumber = bigint | number | string;
51
+
50
52
  export interface QCore {
51
53
  account_body(address: string): string;
52
- address(seed_hex: string, index: bigint): string;
53
- block_by_height_body(height: bigint): string;
54
+ address(seed_hex: string, index: WholeNumber): string;
55
+ block_by_height_body(height: WholeNumber): string;
54
56
  buildSignedOrderCall(
55
- chain_id: bigint,
57
+ chain_id: WholeNumber,
56
58
  contract: string,
57
59
  selector_hex: string,
58
- scheme_off: bigint,
59
- ptr_off: bigint,
60
+ scheme_off: WholeNumber,
61
+ ptr_off: WholeNumber,
60
62
  field_offs_csv: string,
61
63
  fields_csv: string,
62
- region_off: bigint,
64
+ region_off: WholeNumber,
63
65
  owner_seed_hex: string,
64
- owner_index: bigint,
65
- nonce: bigint,
66
+ owner_index: WholeNumber,
67
+ nonce: WholeNumber,
66
68
  ): string;
67
69
  buildTypedOrderCall(
68
- chain_id: bigint,
70
+ chain_id: WholeNumber,
69
71
  contract: string,
70
72
  selector_hex: string,
71
- scheme_off: bigint,
72
- ptr_off: bigint,
73
- region_off: bigint,
73
+ scheme_off: WholeNumber,
74
+ ptr_off: WholeNumber,
75
+ region_off: WholeNumber,
74
76
  fields_json: string,
75
77
  owner_seed_hex: string,
76
- owner_index: bigint,
77
- nonce: bigint,
78
+ owner_index: WholeNumber,
79
+ nonce: WholeNumber,
78
80
  ): string;
79
- contractAddress(deployer: string, nonce: bigint): string | undefined;
81
+ checkValidUntil(valid_until: WholeNumber, head: WholeNumber): void;
82
+ contractAddress(deployer: string, nonce: WholeNumber): string | undefined;
80
83
  chainIdFromName(name: string): bigint;
81
- eventsBody(height: bigint): string;
84
+ eventsBody(height: WholeNumber): string;
82
85
  localChainId(): bigint;
83
86
  mainnetChainId(): bigint;
84
- mapSlotKey(map_domain_tag: bigint, key_address_hex: string): string;
87
+ mapAddrWordKey(map_domain_tag: WholeNumber, key32_hex: string, word: WholeNumber): string;
88
+ mapSlotKey(map_domain_tag: WholeNumber, key_address_hex: string): string;
85
89
  mnemonicFromSeed(seed_hex: string): string;
86
90
  nonceSlotKey(signer_hex: string): string;
87
- orderSigner(seed_hex: string, index: bigint): string;
91
+ orderSigner(seed_hex: string, index: WholeNumber): string;
88
92
  parseEvents(response: string): string;
89
- scalarSlotKey(slot: bigint): string;
93
+ scalarSlotKey(slot: WholeNumber): string;
90
94
  tokenDecimalsSlot(): bigint;
91
95
  tokenSymbolSlot(): bigint;
92
96
  packSymbol(symbol: string): bigint;
93
- unpackSymbol(word: bigint): string;
97
+ unpackSymbol(word: WholeNumber): string;
94
98
  assetBalanceBody(issuer: string, holder: string): string;
95
99
  assetSupplyBody(issuer: string): string;
96
100
  seedFromMnemonic(phrase: string): string;
97
101
  signPayableCall(
98
102
  seed_hex: string,
99
- index: bigint,
103
+ index: WholeNumber,
100
104
  target: string,
101
105
  args_hex: string,
102
- nonce: bigint,
103
- meter_limit: bigint,
104
- fee: string,
105
- value: string,
106
- chain_id: bigint,
107
- valid_until: bigint,
106
+ nonce: WholeNumber,
107
+ meter_limit: WholeNumber,
108
+ fee: WholeNumber,
109
+ value: WholeNumber,
110
+ chain_id: WholeNumber,
111
+ valid_until: WholeNumber,
112
+ transfer_fee: WholeNumber,
108
113
  ): string;
109
114
  signAssetCall(
110
115
  seed_hex: string,
111
- index: bigint,
116
+ index: WholeNumber,
112
117
  target: string,
113
118
  args_hex: string,
114
119
  asset_issuer: string,
115
- amount: string,
116
- nonce: bigint,
117
- meter_limit: bigint,
118
- fee: string,
119
- chain_id: bigint,
120
- valid_until: bigint,
120
+ amount: WholeNumber,
121
+ nonce: WholeNumber,
122
+ meter_limit: WholeNumber,
123
+ fee: WholeNumber,
124
+ chain_id: WholeNumber,
125
+ valid_until: WholeNumber,
126
+ transfer_fee: WholeNumber,
121
127
  ): string;
122
128
  signRegister(
123
129
  seed_hex: string,
124
- index: bigint,
125
- nonce: bigint,
126
- fee: string,
127
- chain_id: bigint,
128
- valid_until: bigint,
130
+ index: WholeNumber,
131
+ nonce: WholeNumber,
132
+ fee: WholeNumber,
133
+ chain_id: WholeNumber,
134
+ valid_until: WholeNumber,
129
135
  ): string;
130
136
  sign_call(
131
137
  seed_hex: string,
132
- index: bigint,
138
+ index: WholeNumber,
133
139
  target: string,
134
140
  args_hex: string,
135
- nonce: bigint,
136
- meter_limit: bigint,
137
- fee: string,
138
- chain_id: bigint,
139
- valid_until: bigint,
141
+ nonce: WholeNumber,
142
+ meter_limit: WholeNumber,
143
+ fee: WholeNumber,
144
+ chain_id: WholeNumber,
145
+ valid_until: WholeNumber,
146
+ transfer_fee: WholeNumber,
140
147
  ): string;
141
148
  sign_transfer(
142
149
  seed_hex: string,
143
- index: bigint,
150
+ index: WholeNumber,
144
151
  to: string,
145
- amount: string,
146
- nonce: bigint,
147
- fee: string,
148
- chain_id: bigint,
149
- valid_until: bigint,
152
+ amount: WholeNumber,
153
+ nonce: WholeNumber,
154
+ fee: WholeNumber,
155
+ chain_id: WholeNumber,
156
+ valid_until: WholeNumber,
150
157
  ): string;
151
158
  storageBody(contract: string): string;
152
159
  storageValue(response: string, slot_key_hex: string): string;
@@ -154,6 +161,9 @@ export interface QCore {
154
161
  testnetChainId(): bigint;
155
162
  transaction_body(tx_id: string): string;
156
163
  valid_address(address: string): boolean;
164
+ sameAddress(a: string, b: string): boolean;
165
+ lookalikeOf(candidate: string, known: string[]): string | undefined;
166
+ vmCallFee(transfer_fee: WholeNumber, meter_limit: WholeNumber): bigint;
157
167
  vmDeployAddress(): string;
158
168
  }
159
169
 
@@ -255,3 +265,5 @@ export const core: QCore;
255
265
  export function generateSeed(): string;
256
266
 
257
267
  export function validUntil(nodeInfo: { head_height?: number | string | bigint }): bigint;
268
+
269
+ export function vmCallFee(transferFee: WholeNumber, meterLimit: WholeNumber): bigint;
package/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  // Copyright 2026 Quantova Inc
2
2
  // SPDX-License-Identifier: Apache-2.0 OR MIT
3
3
 
4
- const core = require('./pkg-node/qcore_js.js');
5
- const { makeClient, generateSeed, validUntil, Network } = require('./shared.js');
4
+ const raw = require('./pkg-node/qcore_js.js');
5
+ const { makeClient, wrapCore, generateSeed, validUntil, Network } = require('./shared.js');
6
6
 
7
+ const core = wrapCore(raw);
7
8
  const Client = makeClient(core);
9
+ const vmCallFee = core.vmCallFee;
8
10
 
9
- module.exports = { Client, core, generateSeed, validUntil, Network };
11
+ module.exports = { Client, core, generateSeed, validUntil, vmCallFee, Network };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@quantovainc/qcore",
3
- "version": "0.1.33",
3
+ "version": "0.2.1",
4
4
  "description": "Quantova's post quantum client core for JavaScript. Signs with ML-DSA-65 and builds Q1 addresses.",
5
5
  "scripts": {
6
6
  "build": "bash scripts/build.sh",
7
- "test": "node test-types.js && node test-conformance.js && node test-order-nonce.js && node test-chain-binding.js && node test-sign-validate.js && node test-transport.js && node test-fee-cap.js && node test-network.js && node test-typed-order.js && node test-asset-guards.js && node test-account-index.js"
7
+ "test": "node test-entry-parity.mjs && node --experimental-wasm-modules test-browser.mjs && node test-types.js && node test-conformance.js && node test-order-nonce.js && node test-chain-binding.js && node test-sign-validate.js && node test-transport.js && node test-fee-cap.js && node test-network.js && node test-typed-order.js && node test-asset-guards.js && node test-account-index.js"
8
8
  },
9
9
  "keywords": [
10
10
  "post-quantum",
package/pkg/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "qcore-js",
3
3
  "type": "module",
4
4
  "description": "The JavaScript binding for the Quantova client core, generated over QCore with wasm-bindgen. The core does the key derivation, the post quantum signing, and the wire, the page does the fetch.",
5
- "version": "0.1.33",
5
+ "version": "0.2.1",
6
6
  "license": "Apache-2.0 OR MIT",
7
7
  "repository": {
8
8
  "type": "git",
package/pkg/qcore_js.d.ts CHANGED
@@ -17,12 +17,16 @@ export function buildTypedOrderCall(chain_id: bigint, contract: string, selector
17
17
 
18
18
  export function chainIdFromName(name: string): bigint;
19
19
 
20
+ export function checkValidUntil(valid_until: bigint, head: bigint): void;
21
+
20
22
  export function contractAddress(deployer: string, nonce: bigint): string | undefined;
21
23
 
22
24
  export function eventsBody(height: bigint): string;
23
25
 
24
26
  export function localChainId(): bigint;
25
27
 
28
+ export function lookalikeOf(candidate: string, known: string[]): string | undefined;
29
+
26
30
  export function mainnetChainId(): bigint;
27
31
 
28
32
  export function mapAddrWordKey(map_domain_tag: bigint, key32_hex: string, word: bigint): string;
@@ -41,17 +45,19 @@ export function packSymbol(symbol: string): bigint;
41
45
 
42
46
  export function parseEvents(response: string): string;
43
47
 
48
+ export function sameAddress(a: string, b: string): boolean;
49
+
44
50
  export function scalarSlotKey(slot: bigint): string;
45
51
 
46
52
  export function seedFromMnemonic(phrase: string): any;
47
53
 
48
- export function signAssetCall(seed_hex: string, index: bigint, target: string, args_hex: string, asset_issuer: string, amount: string, nonce: bigint, meter_limit: bigint, fee: string, chain_id: bigint, valid_until: bigint): string;
54
+ export function signAssetCall(seed_hex: string, index: bigint, target: string, args_hex: string, asset_issuer: string, amount: string, nonce: bigint, meter_limit: bigint, fee: string, chain_id: bigint, valid_until: bigint, transfer_fee?: string | null): string;
49
55
 
50
- export function signPayableCall(seed_hex: string, index: bigint, target: string, args_hex: string, nonce: bigint, meter_limit: bigint, fee: string, value: string, chain_id: bigint, valid_until: bigint): string;
56
+ export function signPayableCall(seed_hex: string, index: bigint, target: string, args_hex: string, nonce: bigint, meter_limit: bigint, fee: string, value: string, chain_id: bigint, valid_until: bigint, transfer_fee?: string | null): string;
51
57
 
52
58
  export function signRegister(seed_hex: string, index: bigint, nonce: bigint, fee: string, chain_id: bigint, valid_until: bigint): string;
53
59
 
54
- export function sign_call(seed_hex: string, index: bigint, target: string, args_hex: string, nonce: bigint, meter_limit: bigint, fee: string, chain_id: bigint, valid_until: bigint): string;
60
+ export function sign_call(seed_hex: string, index: bigint, target: string, args_hex: string, nonce: bigint, meter_limit: bigint, fee: string, chain_id: bigint, valid_until: bigint, transfer_fee?: string | null): string;
55
61
 
56
62
  export function sign_transfer(seed_hex: string, index: bigint, to: string, amount: string, nonce: bigint, fee: string, chain_id: bigint, valid_until: bigint): string;
57
63
 
@@ -73,4 +79,6 @@ export function unpackSymbol(word: bigint): string;
73
79
 
74
80
  export function valid_address(address: string): boolean;
75
81
 
82
+ export function vmCallFee(transfer_fee: string, meter_limit: bigint): string;
83
+
76
84
  export function vmDeployAddress(): string;
package/pkg/qcore_js.js CHANGED
@@ -5,5 +5,5 @@ import { __wbg_set_wasm } from "./qcore_js_bg.js";
5
5
  __wbg_set_wasm(wasm);
6
6
  wasm.__wbindgen_start();
7
7
  export {
8
- account_body, address, assetBalanceBody, assetSupplyBody, block_by_height_body, buildSignedOrderCall, buildTypedOrderCall, chainIdFromName, contractAddress, eventsBody, localChainId, mainnetChainId, mapAddrWordKey, mapSlotKey, mnemonicFromSeed, nameKey, nonceSlotKey, orderSigner, packSymbol, parseEvents, scalarSlotKey, seedFromMnemonic, signAssetCall, signPayableCall, signRegister, sign_call, sign_transfer, storageBody, storageValue, submit_body, testnetChainId, tokenDecimalsSlot, tokenSymbolSlot, transaction_body, unpackSymbol, valid_address, vmDeployAddress
8
+ account_body, address, assetBalanceBody, assetSupplyBody, block_by_height_body, buildSignedOrderCall, buildTypedOrderCall, chainIdFromName, checkValidUntil, contractAddress, eventsBody, localChainId, lookalikeOf, mainnetChainId, mapAddrWordKey, mapSlotKey, mnemonicFromSeed, nameKey, nonceSlotKey, orderSigner, packSymbol, parseEvents, sameAddress, scalarSlotKey, seedFromMnemonic, signAssetCall, signPayableCall, signRegister, sign_call, sign_transfer, storageBody, storageValue, submit_body, testnetChainId, tokenDecimalsSlot, tokenSymbolSlot, transaction_body, unpackSymbol, valid_address, vmCallFee, vmDeployAddress
9
9
  } from "./qcore_js_bg.js";