@quantovainc/qcore 0.4.2 → 0.5.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/LICENSE-APACHE +1 -1
- package/README.md +26 -7
- package/index.d.ts +60 -21
- package/package.json +1 -1
- package/pkg/LICENSE-APACHE +202 -0
- package/pkg/LICENSE-MIT +21 -0
- package/pkg/README.md +261 -0
- package/pkg/package.json +1 -1
- package/pkg/qcore_js.d.ts +34 -3
- package/pkg/qcore_js.js +1 -1
- package/pkg/qcore_js_bg.js +241 -5
- package/pkg/qcore_js_bg.wasm +0 -0
- package/pkg/qcore_js_bg.wasm.d.ts +21 -3
- package/pkg-node/LICENSE-APACHE +202 -0
- package/pkg-node/LICENSE-MIT +21 -0
- package/pkg-node/README.md +261 -0
- package/pkg-node/package.json +1 -1
- package/pkg-node/qcore_js.d.ts +34 -3
- package/pkg-node/qcore_js.js +252 -6
- package/pkg-node/qcore_js_bg.wasm +0 -0
- package/shared.js +202 -185
- package/shared.mjs +202 -185
package/shared.js
CHANGED
|
@@ -29,14 +29,16 @@ function accountNonce(nonce) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const VALIDITY_BLOCKS = 300n;
|
|
32
|
-
const ORDER_HOLD_UNTIL = 1n << 62n;
|
|
33
32
|
const MAX_PLAUSIBLE_HEAD = 1n << 40n;
|
|
34
|
-
const HEAD_BLOCKS_PER_SEC = 20n;
|
|
35
|
-
const HEAD_SLACK_SECS = 60n;
|
|
36
|
-
const GENESIS_FLOOR_SECS = 1735689600n;
|
|
37
33
|
const U64_MAX = 0xffffffffffffffffn;
|
|
38
34
|
const U128_MAX = (1n << 128n) - 1n;
|
|
39
35
|
|
|
36
|
+
function monotonicSecs() {
|
|
37
|
+
const clock = globalThis.performance;
|
|
38
|
+
const ms = clock && typeof clock.now === 'function' ? clock.now() : Date.now();
|
|
39
|
+
return BigInt(Math.floor(ms / 1000));
|
|
40
|
+
}
|
|
41
|
+
|
|
40
42
|
function wholeNumber(value, label, max) {
|
|
41
43
|
let n;
|
|
42
44
|
if (typeof value === 'bigint') {
|
|
@@ -62,12 +64,17 @@ function coreArg(kind, label, value) {
|
|
|
62
64
|
if (typeof value !== 'string') throw new Error(`the ${label} must be a string`);
|
|
63
65
|
return value;
|
|
64
66
|
}
|
|
67
|
+
if (kind === 'phrase') {
|
|
68
|
+
if (!(value instanceof Uint8Array)) throw new Error(`the ${label} must be a Uint8Array of UTF-8 text`);
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
65
71
|
if (kind === 'seed') {
|
|
66
72
|
if (!(value instanceof Uint8Array)) throw new Error(`the ${label} must be a Uint8Array of 32 bytes; pass a handle you can wipe after use, not a hex string`);
|
|
67
73
|
if (value.length !== 32) throw new Error(`the ${label} must be 32 bytes`);
|
|
68
74
|
return value;
|
|
69
75
|
}
|
|
70
76
|
if (kind === 'u64') return wholeNumber(value, label, U64_MAX);
|
|
77
|
+
if (kind === 'u64opt') return value == null ? undefined : wholeNumber(value, label, U64_MAX);
|
|
71
78
|
if (kind === 'u64s') return wholeNumber(value, label, U64_MAX).toString();
|
|
72
79
|
if (kind === 'u128s') return wholeNumber(value, label, U128_MAX).toString();
|
|
73
80
|
throw new Error(`unknown argument kind ${kind}`);
|
|
@@ -76,15 +83,21 @@ function coreArg(kind, label, value) {
|
|
|
76
83
|
const CORE_ARGS = {
|
|
77
84
|
address: [['seed', 'seed'], ['u64', 'account index']],
|
|
78
85
|
mnemonicFromSeed: [['seed', 'seed']],
|
|
86
|
+
mnemonicFromSeedText: [['seed', 'seed']],
|
|
79
87
|
seedFromMnemonic: [['text', 'recovery phrase']],
|
|
88
|
+
seedFromMnemonicBytes: [['phrase', 'recovery phrase']],
|
|
89
|
+
genesisSecs: [['text', 'chain name']],
|
|
90
|
+
headCeiling: [['text', 'chain name'], ['u64', 'wall clock seconds']],
|
|
91
|
+
boundedValidUntil: [['text', 'chain name'], ['u64', 'head height'], ['u64', 'wall clock seconds'], ['u64opt', 'head floor'], ['u64', 'head floor age']],
|
|
92
|
+
checkGatewayJson: [['text', 'gateway response']],
|
|
80
93
|
orderSigner: [['seed', 'seed'], ['u64', 'account index']],
|
|
81
94
|
sign_transfer: [['seed', 'seed'], ['u64', 'account index'], ['text', 'recipient'], ['u64s', 'amount'], ['u64', 'nonce'], ['u128s', 'fee'], ['u64', 'chain id'], ['u64', 'validity deadline']],
|
|
82
95
|
signRegister: [['seed', 'seed'], ['u64', 'account index'], ['u64', 'nonce'], ['u128s', 'fee'], ['u64', 'chain id'], ['u64', 'validity deadline']],
|
|
83
96
|
sign_call: [['seed', 'seed'], ['u64', 'account index'], ['text', 'target'], ['text', 'call arguments'], ['u64', 'nonce'], ['u64', 'meter limit'], ['u128s', 'fee'], ['u64', 'chain id'], ['u64', 'validity deadline'], ['u128s', 'transfer fee']],
|
|
84
97
|
signPayableCall: [['seed', 'seed'], ['u64', 'account index'], ['text', 'target'], ['text', 'call arguments'], ['u64', 'nonce'], ['u64', 'meter limit'], ['u128s', 'fee'], ['u64s', 'value'], ['u64', 'chain id'], ['u64', 'validity deadline'], ['u128s', 'transfer fee']],
|
|
85
98
|
signAssetCall: [['seed', 'seed'], ['u64', 'account index'], ['text', 'target'], ['text', 'call arguments'], ['text', 'asset issuer'], ['u64s', 'amount'], ['u64', 'nonce'], ['u64', 'meter limit'], ['u128s', 'fee'], ['u64', 'chain id'], ['u64', 'validity deadline'], ['u128s', 'transfer fee']],
|
|
86
|
-
buildSignedOrderCall: [['u64', 'chain id'], ['text', 'contract'], ['text', 'selector'], ['u64', 'scheme offset'], ['u64', 'pointer offset'], ['text', 'field offsets'], ['text', 'fields'], ['u64', 'region offset'], ['seed', 'owner seed'], ['u64', 'owner index'], ['u64', 'order nonce']],
|
|
87
|
-
buildTypedOrderCall: [['u64', 'chain id'], ['text', 'contract'], ['text', 'selector'], ['u64', 'scheme offset'], ['u64', 'pointer offset'], ['u64', 'region offset'], ['text', 'fields'], ['seed', 'owner seed'], ['u64', 'owner index'], ['u64', 'order nonce']],
|
|
99
|
+
buildSignedOrderCall: [['u64', 'chain id'], ['text', 'contract'], ['text', 'selector'], ['u64', 'scheme offset'], ['u64', 'pointer offset'], ['u64', 'expiry offset'], ['text', 'field offsets'], ['text', 'fields'], ['u64', 'region offset'], ['seed', 'owner seed'], ['u64', 'owner index'], ['u64', 'order nonce'], ['u64', 'order expiry']],
|
|
100
|
+
buildTypedOrderCall: [['u64', 'chain id'], ['text', 'contract'], ['text', 'selector'], ['u64', 'scheme offset'], ['u64', 'pointer offset'], ['u64', 'expiry offset'], ['u64', 'region offset'], ['text', 'fields'], ['seed', 'owner seed'], ['u64', 'owner index'], ['u64', 'order nonce'], ['u64', 'order expiry']],
|
|
88
101
|
contractAddress: [['text', 'deployer'], ['u64', 'nonce']],
|
|
89
102
|
scalarSlotKey: [['u64', 'slot']],
|
|
90
103
|
mapSlotKey: [['u64', 'map domain tag'], ['text', 'key address']],
|
|
@@ -105,9 +118,48 @@ function wrapCore(raw) {
|
|
|
105
118
|
}
|
|
106
119
|
const fee = core.vmCallFee;
|
|
107
120
|
if (typeof fee === 'function') core.vmCallFee = (transferFee, meterLimit) => BigInt(fee(transferFee, meterLimit));
|
|
121
|
+
const text = core.seedFromMnemonic;
|
|
122
|
+
const bytes = core.seedFromMnemonicBytes;
|
|
123
|
+
if (typeof text === 'function' && typeof bytes === 'function') {
|
|
124
|
+
core.seedFromMnemonic = (phrase) => (phrase instanceof Uint8Array ? bytes(phrase) : text(phrase));
|
|
125
|
+
}
|
|
126
|
+
bindNetworkDefaults(raw);
|
|
108
127
|
return Object.freeze(core);
|
|
109
128
|
}
|
|
110
129
|
|
|
130
|
+
const NETWORK_DEFAULTS = { testnetChainName: null, testnetRpcUrl: null };
|
|
131
|
+
|
|
132
|
+
function bindNetworkDefaults(core) {
|
|
133
|
+
if (core && typeof core.testnetRpcUrl === 'function' && typeof core.testnetChainName === 'function') {
|
|
134
|
+
NETWORK_DEFAULTS.testnetRpcUrl = core.testnetRpcUrl();
|
|
135
|
+
NETWORK_DEFAULTS.testnetChainName = core.testnetChainName();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function requireNetworkDefaults() {
|
|
140
|
+
if (!NETWORK_DEFAULTS.testnetRpcUrl || !NETWORK_DEFAULTS.testnetChainName) {
|
|
141
|
+
throw new Error('load the core before naming the testnet, its endpoint and chain come from the core');
|
|
142
|
+
}
|
|
143
|
+
return NETWORK_DEFAULTS;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function gatewayInteger(value, label) {
|
|
147
|
+
if (value == null) throw new Error(`the gateway did not report a ${label}`);
|
|
148
|
+
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) return BigInt(value);
|
|
149
|
+
if (typeof value === 'bigint' && value >= 0n && value <= U64_MAX) return value;
|
|
150
|
+
throw new Error(`the gateway reported a ${label} that is not a whole number`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function gatewayDecimal(value, label) {
|
|
154
|
+
if (value == null) throw new Error(`the gateway did not report a ${label}`);
|
|
155
|
+
if (typeof value !== 'string' || !/^[0-9]+$/.test(value)) {
|
|
156
|
+
throw new Error(`the gateway reported a ${label} that is not a string of decimal digits`);
|
|
157
|
+
}
|
|
158
|
+
const n = BigInt(value);
|
|
159
|
+
if (n > U128_MAX) throw new Error(`the gateway reported a ${label} outside the unsigned 128 bit range`);
|
|
160
|
+
return n;
|
|
161
|
+
}
|
|
162
|
+
|
|
111
163
|
function isMainnetChain(name) {
|
|
112
164
|
return name.startsWith('Q-main-net-');
|
|
113
165
|
}
|
|
@@ -121,25 +173,23 @@ function meterLimitOf(meterLimit) {
|
|
|
121
173
|
}
|
|
122
174
|
|
|
123
175
|
function validUntil(info) {
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
126
|
-
if (typeof head === 'number' && !Number.isSafeInteger(head)) {
|
|
127
|
-
throw new Error('the gateway reported a head height outside the safe integer range');
|
|
128
|
-
}
|
|
129
|
-
let h;
|
|
130
|
-
try {
|
|
131
|
-
h = BigInt(head);
|
|
132
|
-
} catch {
|
|
133
|
-
throw new Error('the gateway reported a head height that is not a whole number');
|
|
134
|
-
}
|
|
135
|
-
if (h < 0n || h > MAX_PLAUSIBLE_HEAD) {
|
|
176
|
+
const h = gatewayInteger(info && info.head_height, 'head height');
|
|
177
|
+
if (h > MAX_PLAUSIBLE_HEAD) {
|
|
136
178
|
throw new Error('the gateway reported a head height past any height this chain can have reached');
|
|
137
179
|
}
|
|
138
180
|
return h + VALIDITY_BLOCKS;
|
|
139
181
|
}
|
|
140
182
|
|
|
141
183
|
function gatewayFee(fee) {
|
|
142
|
-
return
|
|
184
|
+
return gatewayDecimal(fee, 'transfer fee');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function decodeUtf8(bytes) {
|
|
188
|
+
try {
|
|
189
|
+
return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
190
|
+
} catch {
|
|
191
|
+
throw new Error('the response is not UTF-8');
|
|
192
|
+
}
|
|
143
193
|
}
|
|
144
194
|
|
|
145
195
|
async function readBounded(res) {
|
|
@@ -150,7 +200,9 @@ async function readBounded(res) {
|
|
|
150
200
|
throw new Error('the response has no content-length to bound it and cannot be read safely');
|
|
151
201
|
}
|
|
152
202
|
if (len > MAX_RESPONSE) throw new Error('the response is too large');
|
|
153
|
-
|
|
203
|
+
const whole = new Uint8Array(await res.arrayBuffer());
|
|
204
|
+
if (whole.byteLength > MAX_RESPONSE) throw new Error('the response is too large');
|
|
205
|
+
return decodeUtf8(whole);
|
|
154
206
|
}
|
|
155
207
|
const reader = res.body.getReader();
|
|
156
208
|
const chunks = [];
|
|
@@ -171,7 +223,7 @@ async function readBounded(res) {
|
|
|
171
223
|
merged.set(chunk, at);
|
|
172
224
|
at += chunk.byteLength;
|
|
173
225
|
}
|
|
174
|
-
return
|
|
226
|
+
return decodeUtf8(merged);
|
|
175
227
|
}
|
|
176
228
|
|
|
177
229
|
function isLoopbackHost(hostname) {
|
|
@@ -221,10 +273,11 @@ class Network {
|
|
|
221
273
|
}
|
|
222
274
|
|
|
223
275
|
static testnet() {
|
|
276
|
+
const defaults = requireNetworkDefaults();
|
|
224
277
|
return new Network({
|
|
225
278
|
name: 'testnet',
|
|
226
|
-
chainId:
|
|
227
|
-
rpcUrl:
|
|
279
|
+
chainId: defaults.testnetChainName,
|
|
280
|
+
rpcUrl: defaults.testnetRpcUrl,
|
|
228
281
|
explorerUrl: 'https://qvmscan.io',
|
|
229
282
|
isMainnet: false,
|
|
230
283
|
});
|
|
@@ -256,6 +309,7 @@ function generateSeed() {
|
|
|
256
309
|
}
|
|
257
310
|
|
|
258
311
|
function makeClient(core) {
|
|
312
|
+
bindNetworkDefaults(core);
|
|
259
313
|
return class Client {
|
|
260
314
|
constructor(target, options) {
|
|
261
315
|
const opts = options || {};
|
|
@@ -279,7 +333,10 @@ function makeClient(core) {
|
|
|
279
333
|
typeof opts.expectedChainId === 'string' && opts.expectedChainId.length > 0
|
|
280
334
|
? opts.expectedChainId
|
|
281
335
|
: null;
|
|
336
|
+
const book = opts.addressBook == null ? [] : Array.from(opts.addressBook, String);
|
|
337
|
+
this._session = new core.SigningSession(book);
|
|
282
338
|
this._pinnedChainName = null;
|
|
339
|
+
this._headFloor = null;
|
|
283
340
|
}
|
|
284
341
|
|
|
285
342
|
_guardMainnet() {
|
|
@@ -314,7 +371,7 @@ function makeClient(core) {
|
|
|
314
371
|
return BigInt(core.chainIdFromName(name));
|
|
315
372
|
}
|
|
316
373
|
|
|
317
|
-
async _call(method, body) {
|
|
374
|
+
async _call(method, body, strict) {
|
|
318
375
|
const controller = new AbortController();
|
|
319
376
|
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
320
377
|
try {
|
|
@@ -326,9 +383,10 @@ function makeClient(core) {
|
|
|
326
383
|
redirect: 'error',
|
|
327
384
|
});
|
|
328
385
|
const text = await readBounded(res);
|
|
386
|
+
if (strict && res.status === 200) core.checkGatewayJson(text);
|
|
329
387
|
let data;
|
|
330
388
|
try { data = JSON.parse(text); } catch { throw new Error('the node returned a non JSON response'); }
|
|
331
|
-
if (
|
|
389
|
+
if (res.status !== 200) {
|
|
332
390
|
const detail = data && typeof data === 'object' ? (data.message || data.error) : null;
|
|
333
391
|
throw new Error(detail || ('status ' + res.status));
|
|
334
392
|
}
|
|
@@ -338,10 +396,10 @@ function makeClient(core) {
|
|
|
338
396
|
}
|
|
339
397
|
}
|
|
340
398
|
|
|
341
|
-
nodeInfo() { return this._call('node_info', '{}'); }
|
|
399
|
+
nodeInfo() { return this._call('node_info', '{}', true); }
|
|
342
400
|
head() { return this._call('head', '{}'); }
|
|
343
401
|
async account(address) {
|
|
344
|
-
const acct = await this._call('get_account', core.account_body(address));
|
|
402
|
+
const acct = await this._call('get_account', core.account_body(address), true);
|
|
345
403
|
if (!acct || typeof acct.address !== 'string') {
|
|
346
404
|
throw new Error(`the gateway answered about ${address} without naming the account, refusing to trust it`);
|
|
347
405
|
}
|
|
@@ -350,212 +408,172 @@ function makeClient(core) {
|
|
|
350
408
|
}
|
|
351
409
|
return acct;
|
|
352
410
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (e !== n) {
|
|
358
|
-
throw new Error(`the gateway reported nonce ${n} but you expected ${e}; the chain admits only the nonce the account has reached, refusing to sign`);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (!this._nextNonces) this._nextNonces = new Map();
|
|
362
|
-
if (key != null) {
|
|
363
|
-
const local = this._nextNonces.get(key);
|
|
364
|
-
if (local == null || local < n) this._nextNonces.set(key, n);
|
|
365
|
-
}
|
|
366
|
-
return n;
|
|
411
|
+
|
|
412
|
+
_vet(sender, targets) {
|
|
413
|
+
this._session.rememberAddress(sender);
|
|
414
|
+
for (const target of targets) this._session.checkTarget(target);
|
|
367
415
|
}
|
|
368
416
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
for (const [slotKey, entry] of held) {
|
|
376
|
-
if (head > entry.until) held.delete(slotKey);
|
|
377
|
-
}
|
|
378
|
-
const seen = held.get(String(slot));
|
|
379
|
-
if (!explicit && seen != null && seen.txHex !== txHex) {
|
|
380
|
-
throw new Error(`a different transaction was already signed for nonce ${slot} in this session and was neither rejected nor expired; one nonce carries one signature, pass the nonce explicitly to replace one that never landed`);
|
|
381
|
-
}
|
|
382
|
-
held.set(String(slot), { txHex, until });
|
|
417
|
+
_rememberTargets(targets) {
|
|
418
|
+
for (const target of targets) this._session.rememberAddress(target);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
_expected(value, label) {
|
|
422
|
+
return value == null ? undefined : wholeNumber(value, label, U64_MAX);
|
|
383
423
|
}
|
|
384
424
|
|
|
385
|
-
|
|
386
|
-
|
|
425
|
+
async _accountSlot(from, expectedNonce) {
|
|
426
|
+
const acct = await this.account(from);
|
|
427
|
+
const reported = gatewayInteger(acct.nonce, 'nonce');
|
|
428
|
+
return BigInt(this._session.slot(from, reported, this._expected(expectedNonce, 'expected nonce'), false));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async _submitHeld(slots, txHex) {
|
|
432
|
+
for (const [key, slot] of slots) this._session.signed(key, slot);
|
|
433
|
+
const outcome = await this.submit(txHex);
|
|
387
434
|
const verdict = outcome && outcome.verdict;
|
|
388
|
-
if (verdict
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
435
|
+
if (verdict !== 'accepted' && verdict !== 'rejected') throw new Error(`unknown verdict ${verdict}`);
|
|
436
|
+
for (const [key, slot] of slots) this._session.outcome(key, slot, verdict === 'accepted');
|
|
437
|
+
return outcome;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
_transferFee(info) {
|
|
441
|
+
return gatewayFee(info && info.fee && info.fee.transfer_quon);
|
|
393
442
|
}
|
|
394
443
|
|
|
395
444
|
_validity(info) {
|
|
396
|
-
const
|
|
397
|
-
const
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (this._headFloor
|
|
404
|
-
const { height, at } = this._headFloor;
|
|
405
|
-
if (head < height) throw new Error(`the gateway reports head ${head} below the ${height} it reported earlier, refusing to sign`);
|
|
406
|
-
const elapsed = now > at ? now - at : 0n;
|
|
407
|
-
const allowed = (elapsed + HEAD_SLACK_SECS) * HEAD_BLOCKS_PER_SEC;
|
|
408
|
-
if (head > height + allowed) throw new Error(`the gateway head leapt from ${height} to ${head} faster than blocks are made, refusing to sign`);
|
|
409
|
-
}
|
|
410
|
-
core.checkValidUntil(until, head);
|
|
411
|
-
if (!this._headFloor || head > this._headFloor.height) this._headFloor = { height: head, at: now };
|
|
445
|
+
const head = gatewayInteger(info && info.head_height, 'head height');
|
|
446
|
+
const chain = info && typeof info.chain_id === 'string' ? info.chain_id : '';
|
|
447
|
+
const wall = BigInt(Math.floor(Date.now() / 1000));
|
|
448
|
+
const tick = monotonicSecs();
|
|
449
|
+
const floor = this._headFloor;
|
|
450
|
+
const age = floor && tick > floor.at ? tick - floor.at : 0n;
|
|
451
|
+
const until = BigInt(core.boundedValidUntil(chain, head, wall, floor ? floor.height : undefined, age));
|
|
452
|
+
if (!floor || head > floor.height) this._headFloor = { height: head, at: tick };
|
|
412
453
|
return until;
|
|
413
454
|
}
|
|
455
|
+
|
|
456
|
+
_callFee(info, meterLimit, ceiling, maxFeeQuon) {
|
|
457
|
+
const transferFee = this._transferFee(info);
|
|
458
|
+
const fee = BigInt(core.vmCallFee(String(transferFee), meterLimitOf(meterLimit)));
|
|
459
|
+
if (fee > ceiling) {
|
|
460
|
+
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
461
|
+
}
|
|
462
|
+
return { transferFee, fee };
|
|
463
|
+
}
|
|
464
|
+
|
|
414
465
|
transaction(txId) { return this._call('get_transaction', core.transaction_body(txId)); }
|
|
415
|
-
block(height) { return this._call('get_block', core.block_by_height_body(
|
|
416
|
-
submit(txHex) { return this._call('submit_transaction', core.submit_body(txHex)); }
|
|
466
|
+
block(height) { return this._call('get_block', core.block_by_height_body(wholeNumber(height, 'height', U64_MAX))); }
|
|
467
|
+
submit(txHex) { return this._call('submit_transaction', core.submit_body(txHex), true); }
|
|
417
468
|
container(address) { return this._call('get_container', JSON.stringify({ address })); }
|
|
418
469
|
storage(address) { return this._call('get_storage', JSON.stringify({ address })); }
|
|
419
|
-
events(height) { return this._call('get_events', core.eventsBody(
|
|
470
|
+
events(height) { return this._call('get_events', core.eventsBody(wholeNumber(height, 'height', U64_MAX))); }
|
|
420
471
|
|
|
421
|
-
address(
|
|
472
|
+
address(seed, index) { return core.address(seed, accountIndex(index)); }
|
|
422
473
|
|
|
423
|
-
async transfer(
|
|
474
|
+
async transfer(seed, index, to, amount, maxFeeQuon, expectedNonce) {
|
|
424
475
|
if (!core.valid_address(to)) throw new Error('the recipient is not a q1 address');
|
|
425
476
|
checkAmount(amount);
|
|
426
477
|
const ceiling = feeCeiling(maxFeeQuon);
|
|
478
|
+
const from = core.address(seed, accountIndex(index));
|
|
479
|
+
this._vet(from, [to]);
|
|
427
480
|
const info = await this.nodeInfo();
|
|
428
481
|
this._guardMainnet();
|
|
429
482
|
const chainId = this._signingChainId(info);
|
|
430
|
-
const
|
|
431
|
-
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
432
|
-
const fee = gatewayFee(reported);
|
|
483
|
+
const fee = this._transferFee(info);
|
|
433
484
|
if (fee > ceiling) {
|
|
434
485
|
throw new Error(`the gateway fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
435
486
|
}
|
|
436
487
|
const until = this._validity(info);
|
|
437
|
-
const
|
|
438
|
-
const acct = await this.account(from);
|
|
439
|
-
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
440
|
-
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
488
|
+
const nonce = await this._accountSlot(from, expectedNonce);
|
|
441
489
|
const signed = JSON.parse(
|
|
442
|
-
core.sign_transfer(
|
|
490
|
+
core.sign_transfer(seed, accountIndex(index), to, String(amount), nonce, String(fee), chainId, until)
|
|
443
491
|
);
|
|
444
|
-
this.
|
|
445
|
-
const outcome = await this.
|
|
446
|
-
this._remember(from, nonce, outcome);
|
|
492
|
+
this._rememberTargets([to]);
|
|
493
|
+
const outcome = await this._submitHeld([[from, nonce]], signed.tx_hex);
|
|
447
494
|
return { signed, outcome };
|
|
448
495
|
}
|
|
449
496
|
|
|
450
|
-
async register(
|
|
497
|
+
async register(seed, index, maxFeeQuon, expectedNonce) {
|
|
451
498
|
const ceiling = feeCeiling(maxFeeQuon);
|
|
499
|
+
const from = core.address(seed, accountIndex(index));
|
|
500
|
+
this._vet(from, []);
|
|
452
501
|
const info = await this.nodeInfo();
|
|
453
502
|
this._guardMainnet();
|
|
454
503
|
const chainId = this._signingChainId(info);
|
|
455
|
-
const
|
|
456
|
-
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
457
|
-
const fee = gatewayFee(reported);
|
|
504
|
+
const fee = this._transferFee(info);
|
|
458
505
|
if (fee > ceiling) {
|
|
459
506
|
throw new Error(`the gateway fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
460
507
|
}
|
|
461
508
|
const until = this._validity(info);
|
|
462
|
-
const
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
466
|
-
const signed = JSON.parse(core.signRegister(seedHex, accountIndex(index), nonce, String(fee), chainId, until));
|
|
467
|
-
this._guardSigned(from, nonce, signed.tx_hex, until, expectedNonce != null);
|
|
468
|
-
const outcome = await this.submit(signed.tx_hex);
|
|
469
|
-
this._remember(from, nonce, outcome);
|
|
509
|
+
const nonce = await this._accountSlot(from, expectedNonce);
|
|
510
|
+
const signed = JSON.parse(core.signRegister(seed, accountIndex(index), nonce, String(fee), chainId, until));
|
|
511
|
+
const outcome = await this._submitHeld([[from, nonce]], signed.tx_hex);
|
|
470
512
|
return { signed, outcome };
|
|
471
513
|
}
|
|
472
514
|
|
|
473
|
-
async call(
|
|
515
|
+
async call(seed, index, target, argsHex, meterLimit, maxFeeQuon, expectedNonce) {
|
|
474
516
|
if (!core.valid_address(target)) throw new Error('the target is not a q1 address');
|
|
475
517
|
const ceiling = feeCeiling(maxFeeQuon);
|
|
518
|
+
const from = core.address(seed, accountIndex(index));
|
|
519
|
+
this._vet(from, [target]);
|
|
476
520
|
const info = await this.nodeInfo();
|
|
477
521
|
this._guardMainnet();
|
|
478
522
|
const chainId = this._signingChainId(info);
|
|
479
|
-
const
|
|
480
|
-
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
481
|
-
const transferFee = gatewayFee(reported);
|
|
482
|
-
const fee = BigInt(core.vmCallFee(String(transferFee), meterLimitOf(meterLimit)));
|
|
483
|
-
if (fee > ceiling) {
|
|
484
|
-
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
485
|
-
}
|
|
523
|
+
const { transferFee, fee } = this._callFee(info, meterLimit, ceiling, maxFeeQuon);
|
|
486
524
|
const until = this._validity(info);
|
|
487
|
-
const
|
|
488
|
-
const acct = await this.account(from);
|
|
489
|
-
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
490
|
-
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
525
|
+
const nonce = await this._accountSlot(from, expectedNonce);
|
|
491
526
|
const signed = JSON.parse(
|
|
492
|
-
core.sign_call(
|
|
527
|
+
core.sign_call(seed, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), chainId, until, String(transferFee))
|
|
493
528
|
);
|
|
494
|
-
this.
|
|
495
|
-
const outcome = await this.
|
|
496
|
-
this._remember(from, nonce, outcome);
|
|
529
|
+
this._rememberTargets([target]);
|
|
530
|
+
const outcome = await this._submitHeld([[from, nonce]], signed.tx_hex);
|
|
497
531
|
return { signed, outcome };
|
|
498
532
|
}
|
|
499
533
|
|
|
500
|
-
async assetCall(
|
|
534
|
+
async assetCall(seed, index, target, argsHex, assetIssuer, amount, meterLimit, maxFeeQuon, expectedNonce) {
|
|
501
535
|
if (!core.valid_address(target)) throw new Error('the target is not a q1 address');
|
|
502
536
|
if (!core.valid_address(assetIssuer)) throw new Error('the asset issuer is not a q1 address');
|
|
503
537
|
checkAmount(amount);
|
|
504
538
|
const ceiling = feeCeiling(maxFeeQuon);
|
|
539
|
+
const from = core.address(seed, accountIndex(index));
|
|
540
|
+
this._vet(from, [target, assetIssuer]);
|
|
505
541
|
const info = await this.nodeInfo();
|
|
506
542
|
this._guardMainnet();
|
|
507
543
|
const chainId = this._signingChainId(info);
|
|
508
|
-
const
|
|
509
|
-
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
510
|
-
const transferFee = gatewayFee(reported);
|
|
511
|
-
const fee = BigInt(core.vmCallFee(String(transferFee), meterLimitOf(meterLimit)));
|
|
512
|
-
if (fee > ceiling) {
|
|
513
|
-
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
514
|
-
}
|
|
544
|
+
const { transferFee, fee } = this._callFee(info, meterLimit, ceiling, maxFeeQuon);
|
|
515
545
|
const until = this._validity(info);
|
|
516
|
-
const
|
|
517
|
-
const acct = await this.account(from);
|
|
518
|
-
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
519
|
-
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
546
|
+
const nonce = await this._accountSlot(from, expectedNonce);
|
|
520
547
|
const signed = JSON.parse(
|
|
521
|
-
core.signAssetCall(
|
|
548
|
+
core.signAssetCall(seed, accountIndex(index), target, argsHex, assetIssuer, String(amount), nonce, meterLimitOf(meterLimit), String(fee), chainId, until, String(transferFee))
|
|
522
549
|
);
|
|
523
|
-
this.
|
|
524
|
-
const outcome = await this.
|
|
525
|
-
this._remember(from, nonce, outcome);
|
|
550
|
+
this._rememberTargets([target, assetIssuer]);
|
|
551
|
+
const outcome = await this._submitHeld([[from, nonce]], signed.tx_hex);
|
|
526
552
|
return { signed, outcome };
|
|
527
553
|
}
|
|
528
554
|
|
|
529
|
-
async payableCall(
|
|
555
|
+
async payableCall(seed, index, target, argsHex, value, meterLimit, maxFeeQuon, expectedNonce) {
|
|
530
556
|
if (!core.valid_address(target)) throw new Error('the target is not a q1 address');
|
|
531
557
|
checkAmount(value);
|
|
532
558
|
const ceiling = feeCeiling(maxFeeQuon);
|
|
559
|
+
const from = core.address(seed, accountIndex(index));
|
|
560
|
+
this._vet(from, [target]);
|
|
533
561
|
const info = await this.nodeInfo();
|
|
534
562
|
this._guardMainnet();
|
|
535
563
|
const chainId = this._signingChainId(info);
|
|
536
|
-
const
|
|
537
|
-
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
538
|
-
const transferFee = gatewayFee(reported);
|
|
539
|
-
const fee = BigInt(core.vmCallFee(String(transferFee), meterLimitOf(meterLimit)));
|
|
540
|
-
if (fee > ceiling) {
|
|
541
|
-
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
542
|
-
}
|
|
564
|
+
const { transferFee, fee } = this._callFee(info, meterLimit, ceiling, maxFeeQuon);
|
|
543
565
|
const until = this._validity(info);
|
|
544
|
-
const
|
|
545
|
-
const acct = await this.account(from);
|
|
546
|
-
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
547
|
-
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
566
|
+
const nonce = await this._accountSlot(from, expectedNonce);
|
|
548
567
|
const signed = JSON.parse(
|
|
549
|
-
core.signPayableCall(
|
|
568
|
+
core.signPayableCall(seed, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), String(value), chainId, until, String(transferFee))
|
|
550
569
|
);
|
|
551
|
-
this.
|
|
552
|
-
const outcome = await this.
|
|
553
|
-
this._remember(from, nonce, outcome);
|
|
570
|
+
this._rememberTargets([target]);
|
|
571
|
+
const outcome = await this._submitHeld([[from, nonce]], signed.tx_hex);
|
|
554
572
|
return { signed, outcome };
|
|
555
573
|
}
|
|
556
574
|
|
|
557
575
|
async _slotValue(contract, key) {
|
|
558
|
-
const resp = await this._call('get_storage_at', JSON.stringify({ address: contract, keys: [key] }));
|
|
576
|
+
const resp = await this._call('get_storage_at', JSON.stringify({ address: contract, keys: [key] }), true);
|
|
559
577
|
return BigInt(core.storageValue(JSON.stringify(resp), key));
|
|
560
578
|
}
|
|
561
579
|
|
|
@@ -564,61 +582,60 @@ function makeClient(core) {
|
|
|
564
582
|
}
|
|
565
583
|
|
|
566
584
|
async contractScalar(contract, slot) {
|
|
567
|
-
return this._slotValue(contract, core.scalarSlotKey(
|
|
585
|
+
return this._slotValue(contract, core.scalarSlotKey(wholeNumber(slot, 'slot', U64_MAX)));
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async contractMap(contract, mapDomainTag, keyHex) {
|
|
589
|
+
return this._slotValue(contract, core.mapSlotKey(wholeNumber(mapDomainTag, 'map domain tag', U64_MAX), keyHex));
|
|
568
590
|
}
|
|
569
591
|
|
|
570
|
-
async
|
|
592
|
+
async assetBalance(issuer, holder) {
|
|
593
|
+
if (!core.valid_address(issuer) || !core.valid_address(holder)) {
|
|
594
|
+
throw new Error('the issuer or holder is not a q1 address');
|
|
595
|
+
}
|
|
596
|
+
const resp = await this._call('get_asset_balance', core.assetBalanceBody(issuer, holder), true);
|
|
597
|
+
return gatewayDecimal(resp && resp.balance, 'balance');
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async callSignedOrder(callerSeed, callerIndex, contract, selectorHex, orderSpec, ownerSeed, ownerIndex, meterLimit, maxFeeQuon, expectedOrderNonce, expectedNonce) {
|
|
571
601
|
if (!core.valid_address(contract)) throw new Error('the contract is not a q1 address');
|
|
572
602
|
const ceiling = feeCeiling(maxFeeQuon);
|
|
603
|
+
const from = core.address(callerSeed, accountIndex(callerIndex));
|
|
604
|
+
this._vet(from, [contract]);
|
|
573
605
|
const info = await this.nodeInfo();
|
|
574
606
|
this._guardMainnet();
|
|
575
607
|
const chainId = this._signingChainId(info);
|
|
576
|
-
const
|
|
577
|
-
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
578
|
-
const transferFee = gatewayFee(reported);
|
|
579
|
-
const fee = BigInt(core.vmCallFee(String(transferFee), meterLimitOf(meterLimit)));
|
|
580
|
-
if (fee > ceiling) {
|
|
581
|
-
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
582
|
-
}
|
|
608
|
+
const { transferFee, fee } = this._callFee(info, meterLimit, ceiling, maxFeeQuon);
|
|
583
609
|
const until = this._validity(info);
|
|
584
|
-
const signer = core.orderSigner(
|
|
610
|
+
const signer = core.orderSigner(ownerSeed, accountIndex(ownerIndex));
|
|
585
611
|
const orderKey = contract + '/' + signer;
|
|
586
612
|
const reportedOrder = await this.contractNonce(contract, signer);
|
|
587
|
-
|
|
588
|
-
throw new Error(
|
|
589
|
-
`the gateway reported order nonce ${reportedOrder} but you expected ${expectedOrderNonce}, refusing to sign`
|
|
590
|
-
);
|
|
591
|
-
}
|
|
592
|
-
const nonce = this._checkedNonce(reportedOrder, expectedOrderNonce, orderKey);
|
|
613
|
+
const nonce = BigInt(this._session.slot(orderKey, reportedOrder, this._expected(expectedOrderNonce, 'expected order nonce'), true));
|
|
593
614
|
const order = JSON.parse(core.buildTypedOrderCall(
|
|
594
615
|
chainId,
|
|
595
616
|
contract,
|
|
596
617
|
selectorHex,
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
618
|
+
wholeNumber(orderSpec.schemeOff, 'scheme offset', U64_MAX),
|
|
619
|
+
wholeNumber(orderSpec.ptrOff, 'pointer offset', U64_MAX),
|
|
620
|
+
wholeNumber(orderSpec.expiryOff, 'expiry offset', U64_MAX),
|
|
621
|
+
orderSpec.regionOff == null ? 0n : wholeNumber(orderSpec.regionOff, 'region offset', U64_MAX),
|
|
600
622
|
JSON.stringify(orderSpec.fields || [], (k, v) => {
|
|
601
623
|
if (typeof v !== 'bigint') return v;
|
|
602
624
|
if (k === 'value') return v.toString();
|
|
603
625
|
if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`the order field ${k} ${v} is too large to carry`);
|
|
604
626
|
return Number(v);
|
|
605
627
|
}),
|
|
606
|
-
|
|
628
|
+
ownerSeed,
|
|
607
629
|
accountIndex(ownerIndex),
|
|
608
630
|
nonce,
|
|
631
|
+
wholeNumber(orderSpec.expiry, 'order expiry', U64_MAX),
|
|
609
632
|
));
|
|
610
|
-
const
|
|
611
|
-
const acct = await this.account(from);
|
|
612
|
-
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
613
|
-
const accountNonceUsed = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
633
|
+
const accountNonceUsed = await this._accountSlot(from, expectedNonce);
|
|
614
634
|
const signed = JSON.parse(
|
|
615
|
-
core.sign_call(
|
|
635
|
+
core.sign_call(callerSeed, accountIndex(callerIndex), contract, order.call_args, accountNonceUsed, meterLimitOf(meterLimit), String(fee), chainId, until, String(transferFee))
|
|
616
636
|
);
|
|
617
|
-
this.
|
|
618
|
-
this.
|
|
619
|
-
const outcome = await this.submit(signed.tx_hex);
|
|
620
|
-
this._remember(from, accountNonceUsed, outcome);
|
|
621
|
-
this._remember(orderKey, nonce, outcome);
|
|
637
|
+
this._rememberTargets([contract]);
|
|
638
|
+
const outcome = await this._submitHeld([[orderKey, nonce], [from, accountNonceUsed]], signed.tx_hex);
|
|
622
639
|
return { order, signed, outcome, orderNonce: nonce };
|
|
623
640
|
}
|
|
624
641
|
};
|