@quantovainc/qcore 0.5.5 → 0.5.7

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
@@ -134,6 +134,13 @@ Pass `addressBook`, a list of Q1 addresses you trust, in the Client options, and
134
134
  a target that shares two or more leading and trailing characters with a known address, eight in all, but is a
135
135
  different address. The sending accounts and every target signed for in the session are known as well.
136
136
 
137
+ Pass `fetch`, a function with the fetch signature, in the Client options to route every gateway request
138
+ through your own transport, for example one that enforces an address allow list. Without it the Client
139
+ uses the platform fetch, which on Node follows HTTPS_PROXY only when NODE_USE_ENV_PROXY is set. The
140
+ Client refuses to start when NODE_TLS_REJECT_UNAUTHORIZED is 0. Signing calls for one account run one at a
141
+ time across every Client in the process, and each Client keeps its own record of signed nonces, so keep
142
+ one Client per account to have every nonce tracked in one place.
143
+
137
144
  The last argument to a Client signing call, transfer, register, call, or callSignedOrder, is a fee
138
145
  ceiling, the most you are ever willing to pay in fees for one transaction. You choose it yourself, as a
139
146
  fixed number your app decides on its own, before you sign. The Client reads the gateway's reported fee,
@@ -148,7 +155,10 @@ than you intended.
148
155
  This comparison lives in the Client and only in the Client. The raw core.* signing functions, such as
149
156
  core.sign_transfer, take the actual fee that goes on the wire, not a ceiling, and do no gateway
150
157
  comparison at all. A developer who calls core.* directly has no fee cap, so reach for the Client whenever
151
- a gateway you do not control reports the fee.
158
+ a gateway you do not control reports the fee. The raw signing functions also take the validity deadline
159
+ ready made and do not compare it with the chain head, so call core.checkValidUntil with a head you trust
160
+ first. A SigningSession reserves each nonce it hands out until it is signed, and a flow that stops early
161
+ frees its nonce with release.
152
162
 
153
163
  A contract call pays one transfer fee for every started 1210 units of its meter limit, and the chain
154
164
  refuses a call that pays less. The raw call signers, core.sign_call, core.signPayableCall, and
package/index.d.ts CHANGED
@@ -212,6 +212,8 @@ export interface SigningSession {
212
212
  rememberAddress(address: string): void;
213
213
  checkTarget(address: string): void;
214
214
  slot(key: string, reported: bigint, expected: bigint | undefined, order: boolean): bigint;
215
+ release(key: string, slot: bigint): void;
216
+ releasePending(key: string): void;
215
217
  signed(key: string, slot: bigint): void;
216
218
  outcome(key: string, slot: bigint, accepted: boolean): void;
217
219
  nextNonce(key: string): bigint | undefined;
@@ -237,6 +239,7 @@ export interface ClientOptions {
237
239
  network?: Network;
238
240
  expectedChainId?: string;
239
241
  addressBook?: ReadonlyArray<string>;
242
+ fetch?: (url: string, init: { method: string; headers: Record<string, string>; body: string; signal: AbortSignal; redirect: 'error' }) => Promise<Response>;
240
243
  }
241
244
 
242
245
  export class Client {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantovainc/qcore",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
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",
package/pkg/README.md CHANGED
@@ -134,6 +134,13 @@ Pass `addressBook`, a list of Q1 addresses you trust, in the Client options, and
134
134
  a target that shares two or more leading and trailing characters with a known address, eight in all, but is a
135
135
  different address. The sending accounts and every target signed for in the session are known as well.
136
136
 
137
+ Pass `fetch`, a function with the fetch signature, in the Client options to route every gateway request
138
+ through your own transport, for example one that enforces an address allow list. Without it the Client
139
+ uses the platform fetch, which on Node follows HTTPS_PROXY only when NODE_USE_ENV_PROXY is set. The
140
+ Client refuses to start when NODE_TLS_REJECT_UNAUTHORIZED is 0. Signing calls for one account run one at a
141
+ time across every Client in the process, and each Client keeps its own record of signed nonces, so keep
142
+ one Client per account to have every nonce tracked in one place.
143
+
137
144
  The last argument to a Client signing call, transfer, register, call, or callSignedOrder, is a fee
138
145
  ceiling, the most you are ever willing to pay in fees for one transaction. You choose it yourself, as a
139
146
  fixed number your app decides on its own, before you sign. The Client reads the gateway's reported fee,
@@ -148,7 +155,10 @@ than you intended.
148
155
  This comparison lives in the Client and only in the Client. The raw core.* signing functions, such as
149
156
  core.sign_transfer, take the actual fee that goes on the wire, not a ceiling, and do no gateway
150
157
  comparison at all. A developer who calls core.* directly has no fee cap, so reach for the Client whenever
151
- a gateway you do not control reports the fee.
158
+ a gateway you do not control reports the fee. The raw signing functions also take the validity deadline
159
+ ready made and do not compare it with the chain head, so call core.checkValidUntil with a head you trust
160
+ first. A SigningSession reserves each nonce it hands out until it is signed, and a flow that stops early
161
+ frees its nonce with release.
152
162
 
153
163
  A contract call pays one transfer fee for every started 1210 units of its meter limit, and the chain
154
164
  refuses a call that pays less. The raw call signers, core.sign_call, core.signPayableCall, and
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.5.5",
5
+ "version": "0.5.7",
6
6
  "license": "Apache-2.0 OR MIT",
7
7
  "repository": {
8
8
  "type": "git",
package/pkg/qcore_js.d.ts CHANGED
@@ -9,6 +9,8 @@ export class SigningSession {
9
9
  constructor(book: string[]);
10
10
  nextNonce(key: string): bigint | undefined;
11
11
  outcome(key: string, slot: bigint, accepted: boolean): void;
12
+ release(key: string, slot: bigint): void;
13
+ releasePending(key: string): void;
12
14
  rememberAddress(address: string): void;
13
15
  signed(key: string, slot: bigint): void;
14
16
  slot(key: string, reported: bigint, expected: bigint | null | undefined, order: boolean): bigint;
@@ -65,6 +65,23 @@ export class SigningSession {
65
65
  const len0 = WASM_VECTOR_LEN;
66
66
  wasm.signingsession_outcome(this.__wbg_ptr, ptr0, len0, slot, accepted);
67
67
  }
68
+ /**
69
+ * @param {string} key
70
+ * @param {bigint} slot
71
+ */
72
+ release(key, slot) {
73
+ const ptr0 = passStringToWasm0(key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
74
+ const len0 = WASM_VECTOR_LEN;
75
+ wasm.signingsession_release(this.__wbg_ptr, ptr0, len0, slot);
76
+ }
77
+ /**
78
+ * @param {string} key
79
+ */
80
+ releasePending(key) {
81
+ const ptr0 = passStringToWasm0(key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
82
+ const len0 = WASM_VECTOR_LEN;
83
+ wasm.signingsession_releasePending(this.__wbg_ptr, ptr0, len0);
84
+ }
68
85
  /**
69
86
  * @param {string} address
70
87
  */
Binary file
@@ -46,6 +46,8 @@ export const signingsession_isHeld: (a: number, b: number, c: number, d: bigint)
46
46
  export const signingsession_new: (a: number, b: number) => [number, number, number];
47
47
  export const signingsession_nextNonce: (a: number, b: number, c: number) => [number, bigint];
48
48
  export const signingsession_outcome: (a: number, b: number, c: number, d: bigint, e: number) => void;
49
+ export const signingsession_release: (a: number, b: number, c: number, d: bigint) => void;
50
+ export const signingsession_releasePending: (a: number, b: number, c: number) => void;
49
51
  export const signingsession_rememberAddress: (a: number, b: number, c: number) => void;
50
52
  export const signingsession_signed: (a: number, b: number, c: number, d: bigint) => void;
51
53
  export const signingsession_slot: (a: number, b: number, c: number, d: bigint, e: number, f: bigint, g: number) => [bigint, number, number];
@@ -134,6 +134,13 @@ Pass `addressBook`, a list of Q1 addresses you trust, in the Client options, and
134
134
  a target that shares two or more leading and trailing characters with a known address, eight in all, but is a
135
135
  different address. The sending accounts and every target signed for in the session are known as well.
136
136
 
137
+ Pass `fetch`, a function with the fetch signature, in the Client options to route every gateway request
138
+ through your own transport, for example one that enforces an address allow list. Without it the Client
139
+ uses the platform fetch, which on Node follows HTTPS_PROXY only when NODE_USE_ENV_PROXY is set. The
140
+ Client refuses to start when NODE_TLS_REJECT_UNAUTHORIZED is 0. Signing calls for one account run one at a
141
+ time across every Client in the process, and each Client keeps its own record of signed nonces, so keep
142
+ one Client per account to have every nonce tracked in one place.
143
+
137
144
  The last argument to a Client signing call, transfer, register, call, or callSignedOrder, is a fee
138
145
  ceiling, the most you are ever willing to pay in fees for one transaction. You choose it yourself, as a
139
146
  fixed number your app decides on its own, before you sign. The Client reads the gateway's reported fee,
@@ -148,7 +155,10 @@ than you intended.
148
155
  This comparison lives in the Client and only in the Client. The raw core.* signing functions, such as
149
156
  core.sign_transfer, take the actual fee that goes on the wire, not a ceiling, and do no gateway
150
157
  comparison at all. A developer who calls core.* directly has no fee cap, so reach for the Client whenever
151
- a gateway you do not control reports the fee.
158
+ a gateway you do not control reports the fee. The raw signing functions also take the validity deadline
159
+ ready made and do not compare it with the chain head, so call core.checkValidUntil with a head you trust
160
+ first. A SigningSession reserves each nonce it hands out until it is signed, and a flow that stops early
161
+ frees its nonce with release.
152
162
 
153
163
  A contract call pays one transfer fee for every started 1210 units of its meter limit, and the chain
154
164
  refuses a call that pays less. The raw call signers, core.sign_call, core.signPayableCall, and
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qcore-js",
3
3
  "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.",
4
- "version": "0.5.5",
4
+ "version": "0.5.7",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -9,6 +9,8 @@ export class SigningSession {
9
9
  constructor(book: string[]);
10
10
  nextNonce(key: string): bigint | undefined;
11
11
  outcome(key: string, slot: bigint, accepted: boolean): void;
12
+ release(key: string, slot: bigint): void;
13
+ releasePending(key: string): void;
12
14
  rememberAddress(address: string): void;
13
15
  signed(key: string, slot: bigint): void;
14
16
  slot(key: string, reported: bigint, expected: bigint | null | undefined, order: boolean): bigint;
@@ -67,6 +67,23 @@ class SigningSession {
67
67
  const len0 = WASM_VECTOR_LEN;
68
68
  wasm.signingsession_outcome(this.__wbg_ptr, ptr0, len0, slot, accepted);
69
69
  }
70
+ /**
71
+ * @param {string} key
72
+ * @param {bigint} slot
73
+ */
74
+ release(key, slot) {
75
+ const ptr0 = passStringToWasm0(key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
76
+ const len0 = WASM_VECTOR_LEN;
77
+ wasm.signingsession_release(this.__wbg_ptr, ptr0, len0, slot);
78
+ }
79
+ /**
80
+ * @param {string} key
81
+ */
82
+ releasePending(key) {
83
+ const ptr0 = passStringToWasm0(key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
84
+ const len0 = WASM_VECTOR_LEN;
85
+ wasm.signingsession_releasePending(this.__wbg_ptr, ptr0, len0);
86
+ }
70
87
  /**
71
88
  * @param {string} address
72
89
  */
Binary file
package/shared.js CHANGED
@@ -59,6 +59,20 @@ function wholeNumber(value, label, max) {
59
59
  return n;
60
60
  }
61
61
 
62
+ const ACCOUNT_LOCKS = new Map();
63
+
64
+ function requireVerifiedTls() {
65
+ const env = typeof process !== 'undefined' && process && process.env ? process.env : null;
66
+ if (env && env.NODE_TLS_REJECT_UNAUTHORIZED === '0') {
67
+ throw new Error('this process has TLS certificate checks turned off with NODE_TLS_REJECT_UNAUTHORIZED=0, refusing to talk to a gateway');
68
+ }
69
+ }
70
+
71
+ function shown(value) {
72
+ const text = String(value).replace(/[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u2028-\u202e\u2066-\u2069\ufeff]/g, '');
73
+ return text.length > 96 ? text.slice(0, 96) + '...' : text;
74
+ }
75
+
62
76
  function coreArg(kind, label, value) {
63
77
  if (kind === 'text') {
64
78
  if (typeof value !== 'string') throw new Error(`the ${label} must be a string`);
@@ -343,7 +357,10 @@ function makeClient(core) {
343
357
  this._session = new core.SigningSession(book);
344
358
  this._pinnedChainName = null;
345
359
  this._headFloor = null;
346
- this._locks = new Map();
360
+ if (opts.fetch != null && typeof opts.fetch !== 'function') throw new Error('the fetch option must be a function with the fetch signature');
361
+ this._fetch = opts.fetch || ((url, init) => fetch(url, init));
362
+ requireVerifiedTls();
363
+ this._locks = ACCOUNT_LOCKS;
347
364
  for (const name of ['transfer', 'register', 'call', 'assetCall', 'payableCall']) {
348
365
  const inner = this[name].bind(this);
349
366
  this[name] = (seed, index, ...rest) => {
@@ -392,18 +409,18 @@ function makeClient(core) {
392
409
  const mainnet = isMainnetChain(name);
393
410
  if (configured) {
394
411
  if (name !== configured) {
395
- throw new Error(`the gateway reports chain ${name} but this client is configured for ${configured}; refusing to sign a transaction that would be valid on a network you did not choose`);
412
+ throw new Error(`the gateway reports chain ${shown(name)} but this client is configured for ${configured}; refusing to sign a transaction that would be valid on a network you did not choose`);
396
413
  }
397
414
  } else if (isPublicChain(name) && !(mainnet && this.acknowledgeMainnet)) {
398
- throw new Error(`the gateway reports the public chain ${name} but this client was opened for an unnamed network; configure the testnet or mainnet network before signing for it`);
415
+ throw new Error(`the gateway reports the public chain ${shown(name)} but this client was opened for an unnamed network; configure the testnet or mainnet network before signing for it`);
399
416
  }
400
417
  if (mainnet && !this.acknowledgeMainnet) {
401
- throw new Error(`the gateway reports the mainnet chain ${name}; refusing to sign a mainnet transaction without acknowledgeMainnet`);
418
+ throw new Error(`the gateway reports the mainnet chain ${shown(name)}; refusing to sign a mainnet transaction without acknowledgeMainnet`);
402
419
  }
403
420
  if (this._pinnedChainName === null) {
404
421
  this._pinnedChainName = name;
405
422
  } else if (this._pinnedChainName !== name) {
406
- throw new Error(`the gateway reported chain ${this._pinnedChainName} earlier and now reports ${name}; refusing to sign, the endpoint is not naming one network`);
423
+ throw new Error(`the gateway reported chain ${shown(this._pinnedChainName)} earlier and now reports ${shown(name)}; refusing to sign, the endpoint is not naming one network`);
407
424
  }
408
425
  return BigInt(core.chainIdFromName(name));
409
426
  }
@@ -412,7 +429,7 @@ function makeClient(core) {
412
429
  const controller = new AbortController();
413
430
  const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
414
431
  try {
415
- const res = await fetch(this.base + '/v1/' + method, {
432
+ const res = await this._fetch(this.base + '/v1/' + method, {
416
433
  method: 'POST',
417
434
  headers: { 'Content-Type': 'application/json' },
418
435
  body: body || '{}',
@@ -425,7 +442,7 @@ function makeClient(core) {
425
442
  try { data = JSON.parse(text); } catch { throw new Error('the node returned a non JSON response'); }
426
443
  if (res.status !== 200) {
427
444
  const detail = data && typeof data === 'object' ? (data.message || data.error) : null;
428
- throw new Error(detail || ('status ' + res.status));
445
+ throw new Error(detail ? shown(detail) : ('status ' + res.status));
429
446
  }
430
447
  return data;
431
448
  } finally {
@@ -441,7 +458,7 @@ function makeClient(core) {
441
458
  throw new Error(`the gateway answered about ${address} without naming the account, refusing to trust it`);
442
459
  }
443
460
  if (acct.address !== address) {
444
- throw new Error(`the gateway answered for ${acct.address} when asked about ${address}, refusing to trust it`);
461
+ throw new Error(`the gateway answered for ${shown(acct.address)} when asked about ${address}, refusing to trust it`);
445
462
  }
446
463
  return acct;
447
464
  }
@@ -462,6 +479,7 @@ function makeClient(core) {
462
479
  async _accountSlot(from, expectedNonce) {
463
480
  const acct = await this.account(from);
464
481
  const reported = gatewayInteger(acct.nonce, 'nonce');
482
+ this._session.releasePending(from);
465
483
  return BigInt(this._session.slot(from, reported, this._expected(expectedNonce, 'expected nonce'), false));
466
484
  }
467
485
 
@@ -469,7 +487,7 @@ function makeClient(core) {
469
487
  for (const [key, slot] of slots) this._session.signed(key, slot);
470
488
  const outcome = await this.submit(txHex);
471
489
  const verdict = outcome && outcome.verdict;
472
- if (verdict !== 'accepted' && verdict !== 'rejected') throw new Error(`unknown verdict ${verdict}`);
490
+ if (verdict !== 'accepted' && verdict !== 'rejected') throw new Error(`unknown verdict ${shown(verdict)}`);
473
491
  for (const [key, slot] of slots) this._session.outcome(key, slot, verdict === 'accepted');
474
492
  return outcome;
475
493
  }
@@ -649,6 +667,7 @@ function makeClient(core) {
649
667
  const signer = core.orderSigner(ownerSeed, accountIndex(ownerIndex));
650
668
  const orderKey = contract + '/' + signer;
651
669
  const reportedOrder = await this.contractNonce(contract, signer);
670
+ this._session.releasePending(orderKey);
652
671
  const nonce = BigInt(this._session.slot(orderKey, reportedOrder, this._expected(expectedOrderNonce, 'expected order nonce'), true));
653
672
  if (typeof orderSpec.layoutHash !== 'string') throw new Error('the order spec needs the layout hash the compiler emits for this entry');
654
673
  const orderExpiry = wholeNumber(orderSpec.expiry, 'order expiry', U64_MAX);
package/shared.mjs CHANGED
@@ -59,6 +59,20 @@ function wholeNumber(value, label, max) {
59
59
  return n;
60
60
  }
61
61
 
62
+ const ACCOUNT_LOCKS = new Map();
63
+
64
+ function requireVerifiedTls() {
65
+ const env = typeof process !== 'undefined' && process && process.env ? process.env : null;
66
+ if (env && env.NODE_TLS_REJECT_UNAUTHORIZED === '0') {
67
+ throw new Error('this process has TLS certificate checks turned off with NODE_TLS_REJECT_UNAUTHORIZED=0, refusing to talk to a gateway');
68
+ }
69
+ }
70
+
71
+ function shown(value) {
72
+ const text = String(value).replace(/[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u2028-\u202e\u2066-\u2069\ufeff]/g, '');
73
+ return text.length > 96 ? text.slice(0, 96) + '...' : text;
74
+ }
75
+
62
76
  function coreArg(kind, label, value) {
63
77
  if (kind === 'text') {
64
78
  if (typeof value !== 'string') throw new Error(`the ${label} must be a string`);
@@ -343,7 +357,10 @@ function makeClient(core) {
343
357
  this._session = new core.SigningSession(book);
344
358
  this._pinnedChainName = null;
345
359
  this._headFloor = null;
346
- this._locks = new Map();
360
+ if (opts.fetch != null && typeof opts.fetch !== 'function') throw new Error('the fetch option must be a function with the fetch signature');
361
+ this._fetch = opts.fetch || ((url, init) => fetch(url, init));
362
+ requireVerifiedTls();
363
+ this._locks = ACCOUNT_LOCKS;
347
364
  for (const name of ['transfer', 'register', 'call', 'assetCall', 'payableCall']) {
348
365
  const inner = this[name].bind(this);
349
366
  this[name] = (seed, index, ...rest) => {
@@ -392,18 +409,18 @@ function makeClient(core) {
392
409
  const mainnet = isMainnetChain(name);
393
410
  if (configured) {
394
411
  if (name !== configured) {
395
- throw new Error(`the gateway reports chain ${name} but this client is configured for ${configured}; refusing to sign a transaction that would be valid on a network you did not choose`);
412
+ throw new Error(`the gateway reports chain ${shown(name)} but this client is configured for ${configured}; refusing to sign a transaction that would be valid on a network you did not choose`);
396
413
  }
397
414
  } else if (isPublicChain(name) && !(mainnet && this.acknowledgeMainnet)) {
398
- throw new Error(`the gateway reports the public chain ${name} but this client was opened for an unnamed network; configure the testnet or mainnet network before signing for it`);
415
+ throw new Error(`the gateway reports the public chain ${shown(name)} but this client was opened for an unnamed network; configure the testnet or mainnet network before signing for it`);
399
416
  }
400
417
  if (mainnet && !this.acknowledgeMainnet) {
401
- throw new Error(`the gateway reports the mainnet chain ${name}; refusing to sign a mainnet transaction without acknowledgeMainnet`);
418
+ throw new Error(`the gateway reports the mainnet chain ${shown(name)}; refusing to sign a mainnet transaction without acknowledgeMainnet`);
402
419
  }
403
420
  if (this._pinnedChainName === null) {
404
421
  this._pinnedChainName = name;
405
422
  } else if (this._pinnedChainName !== name) {
406
- throw new Error(`the gateway reported chain ${this._pinnedChainName} earlier and now reports ${name}; refusing to sign, the endpoint is not naming one network`);
423
+ throw new Error(`the gateway reported chain ${shown(this._pinnedChainName)} earlier and now reports ${shown(name)}; refusing to sign, the endpoint is not naming one network`);
407
424
  }
408
425
  return BigInt(core.chainIdFromName(name));
409
426
  }
@@ -412,7 +429,7 @@ function makeClient(core) {
412
429
  const controller = new AbortController();
413
430
  const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
414
431
  try {
415
- const res = await fetch(this.base + '/v1/' + method, {
432
+ const res = await this._fetch(this.base + '/v1/' + method, {
416
433
  method: 'POST',
417
434
  headers: { 'Content-Type': 'application/json' },
418
435
  body: body || '{}',
@@ -425,7 +442,7 @@ function makeClient(core) {
425
442
  try { data = JSON.parse(text); } catch { throw new Error('the node returned a non JSON response'); }
426
443
  if (res.status !== 200) {
427
444
  const detail = data && typeof data === 'object' ? (data.message || data.error) : null;
428
- throw new Error(detail || ('status ' + res.status));
445
+ throw new Error(detail ? shown(detail) : ('status ' + res.status));
429
446
  }
430
447
  return data;
431
448
  } finally {
@@ -441,7 +458,7 @@ function makeClient(core) {
441
458
  throw new Error(`the gateway answered about ${address} without naming the account, refusing to trust it`);
442
459
  }
443
460
  if (acct.address !== address) {
444
- throw new Error(`the gateway answered for ${acct.address} when asked about ${address}, refusing to trust it`);
461
+ throw new Error(`the gateway answered for ${shown(acct.address)} when asked about ${address}, refusing to trust it`);
445
462
  }
446
463
  return acct;
447
464
  }
@@ -462,6 +479,7 @@ function makeClient(core) {
462
479
  async _accountSlot(from, expectedNonce) {
463
480
  const acct = await this.account(from);
464
481
  const reported = gatewayInteger(acct.nonce, 'nonce');
482
+ this._session.releasePending(from);
465
483
  return BigInt(this._session.slot(from, reported, this._expected(expectedNonce, 'expected nonce'), false));
466
484
  }
467
485
 
@@ -469,7 +487,7 @@ function makeClient(core) {
469
487
  for (const [key, slot] of slots) this._session.signed(key, slot);
470
488
  const outcome = await this.submit(txHex);
471
489
  const verdict = outcome && outcome.verdict;
472
- if (verdict !== 'accepted' && verdict !== 'rejected') throw new Error(`unknown verdict ${verdict}`);
490
+ if (verdict !== 'accepted' && verdict !== 'rejected') throw new Error(`unknown verdict ${shown(verdict)}`);
473
491
  for (const [key, slot] of slots) this._session.outcome(key, slot, verdict === 'accepted');
474
492
  return outcome;
475
493
  }
@@ -649,6 +667,7 @@ function makeClient(core) {
649
667
  const signer = core.orderSigner(ownerSeed, accountIndex(ownerIndex));
650
668
  const orderKey = contract + '/' + signer;
651
669
  const reportedOrder = await this.contractNonce(contract, signer);
670
+ this._session.releasePending(orderKey);
652
671
  const nonce = BigInt(this._session.slot(orderKey, reportedOrder, this._expected(expectedOrderNonce, 'expected order nonce'), true));
653
672
  if (typeof orderSpec.layoutHash !== 'string') throw new Error('the order spec needs the layout hash the compiler emits for this entry');
654
673
  const orderExpiry = wholeNumber(orderSpec.expiry, 'order expiry', U64_MAX);