@quantovainc/qcore 0.4.3 → 0.5.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 +16 -2
- package/index.d.ts +42 -3
- package/package.json +1 -1
- package/pkg/README.md +16 -2
- 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/README.md +16 -2
- 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 +190 -183
- package/shared.mjs +190 -183
package/shared.js
CHANGED
|
@@ -29,12 +29,7 @@ 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_BLOCKS = 240n;
|
|
36
|
-
const GENESIS_FLOOR_SECS = 1735689600n;
|
|
37
|
-
const TESTNET_GENESIS_SECS = 1791082813n;
|
|
38
33
|
const U64_MAX = 0xffffffffffffffffn;
|
|
39
34
|
const U128_MAX = (1n << 128n) - 1n;
|
|
40
35
|
|
|
@@ -69,12 +64,17 @@ function coreArg(kind, label, value) {
|
|
|
69
64
|
if (typeof value !== 'string') throw new Error(`the ${label} must be a string`);
|
|
70
65
|
return value;
|
|
71
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
|
+
}
|
|
72
71
|
if (kind === 'seed') {
|
|
73
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`);
|
|
74
73
|
if (value.length !== 32) throw new Error(`the ${label} must be 32 bytes`);
|
|
75
74
|
return value;
|
|
76
75
|
}
|
|
77
76
|
if (kind === 'u64') return wholeNumber(value, label, U64_MAX);
|
|
77
|
+
if (kind === 'u64opt') return value == null ? undefined : wholeNumber(value, label, U64_MAX);
|
|
78
78
|
if (kind === 'u64s') return wholeNumber(value, label, U64_MAX).toString();
|
|
79
79
|
if (kind === 'u128s') return wholeNumber(value, label, U128_MAX).toString();
|
|
80
80
|
throw new Error(`unknown argument kind ${kind}`);
|
|
@@ -83,15 +83,21 @@ function coreArg(kind, label, value) {
|
|
|
83
83
|
const CORE_ARGS = {
|
|
84
84
|
address: [['seed', 'seed'], ['u64', 'account index']],
|
|
85
85
|
mnemonicFromSeed: [['seed', 'seed']],
|
|
86
|
+
mnemonicFromSeedText: [['seed', 'seed']],
|
|
86
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']],
|
|
87
93
|
orderSigner: [['seed', 'seed'], ['u64', 'account index']],
|
|
88
94
|
sign_transfer: [['seed', 'seed'], ['u64', 'account index'], ['text', 'recipient'], ['u64s', 'amount'], ['u64', 'nonce'], ['u128s', 'fee'], ['u64', 'chain id'], ['u64', 'validity deadline']],
|
|
89
95
|
signRegister: [['seed', 'seed'], ['u64', 'account index'], ['u64', 'nonce'], ['u128s', 'fee'], ['u64', 'chain id'], ['u64', 'validity deadline']],
|
|
90
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']],
|
|
91
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']],
|
|
92
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']],
|
|
93
|
-
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']],
|
|
94
|
-
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']],
|
|
95
101
|
contractAddress: [['text', 'deployer'], ['u64', 'nonce']],
|
|
96
102
|
scalarSlotKey: [['u64', 'slot']],
|
|
97
103
|
mapSlotKey: [['u64', 'map domain tag'], ['text', 'key address']],
|
|
@@ -112,9 +118,48 @@ function wrapCore(raw) {
|
|
|
112
118
|
}
|
|
113
119
|
const fee = core.vmCallFee;
|
|
114
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);
|
|
115
127
|
return Object.freeze(core);
|
|
116
128
|
}
|
|
117
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
|
+
|
|
118
163
|
function isMainnetChain(name) {
|
|
119
164
|
return name.startsWith('Q-main-net-');
|
|
120
165
|
}
|
|
@@ -128,25 +173,23 @@ function meterLimitOf(meterLimit) {
|
|
|
128
173
|
}
|
|
129
174
|
|
|
130
175
|
function validUntil(info) {
|
|
131
|
-
const
|
|
132
|
-
if (
|
|
133
|
-
if (typeof head === 'number' && !Number.isSafeInteger(head)) {
|
|
134
|
-
throw new Error('the gateway reported a head height outside the safe integer range');
|
|
135
|
-
}
|
|
136
|
-
let h;
|
|
137
|
-
try {
|
|
138
|
-
h = wholeNumber(head, 'head height', U64_MAX);
|
|
139
|
-
} catch {
|
|
140
|
-
throw new Error('the gateway reported a head height that is not a whole number');
|
|
141
|
-
}
|
|
142
|
-
if (h < 0n || h > MAX_PLAUSIBLE_HEAD) {
|
|
176
|
+
const h = gatewayInteger(info && info.head_height, 'head height');
|
|
177
|
+
if (h > MAX_PLAUSIBLE_HEAD) {
|
|
143
178
|
throw new Error('the gateway reported a head height past any height this chain can have reached');
|
|
144
179
|
}
|
|
145
180
|
return h + VALIDITY_BLOCKS;
|
|
146
181
|
}
|
|
147
182
|
|
|
148
183
|
function gatewayFee(fee) {
|
|
149
|
-
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
|
+
}
|
|
150
193
|
}
|
|
151
194
|
|
|
152
195
|
async function readBounded(res) {
|
|
@@ -157,7 +200,9 @@ async function readBounded(res) {
|
|
|
157
200
|
throw new Error('the response has no content-length to bound it and cannot be read safely');
|
|
158
201
|
}
|
|
159
202
|
if (len > MAX_RESPONSE) throw new Error('the response is too large');
|
|
160
|
-
|
|
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);
|
|
161
206
|
}
|
|
162
207
|
const reader = res.body.getReader();
|
|
163
208
|
const chunks = [];
|
|
@@ -178,7 +223,7 @@ async function readBounded(res) {
|
|
|
178
223
|
merged.set(chunk, at);
|
|
179
224
|
at += chunk.byteLength;
|
|
180
225
|
}
|
|
181
|
-
return
|
|
226
|
+
return decodeUtf8(merged);
|
|
182
227
|
}
|
|
183
228
|
|
|
184
229
|
function isLoopbackHost(hostname) {
|
|
@@ -228,10 +273,11 @@ class Network {
|
|
|
228
273
|
}
|
|
229
274
|
|
|
230
275
|
static testnet() {
|
|
276
|
+
const defaults = requireNetworkDefaults();
|
|
231
277
|
return new Network({
|
|
232
278
|
name: 'testnet',
|
|
233
|
-
chainId:
|
|
234
|
-
rpcUrl:
|
|
279
|
+
chainId: defaults.testnetChainName,
|
|
280
|
+
rpcUrl: defaults.testnetRpcUrl,
|
|
235
281
|
explorerUrl: 'https://qvmscan.io',
|
|
236
282
|
isMainnet: false,
|
|
237
283
|
});
|
|
@@ -263,6 +309,7 @@ function generateSeed() {
|
|
|
263
309
|
}
|
|
264
310
|
|
|
265
311
|
function makeClient(core) {
|
|
312
|
+
bindNetworkDefaults(core);
|
|
266
313
|
return class Client {
|
|
267
314
|
constructor(target, options) {
|
|
268
315
|
const opts = options || {};
|
|
@@ -286,7 +333,10 @@ function makeClient(core) {
|
|
|
286
333
|
typeof opts.expectedChainId === 'string' && opts.expectedChainId.length > 0
|
|
287
334
|
? opts.expectedChainId
|
|
288
335
|
: null;
|
|
336
|
+
const book = opts.addressBook == null ? [] : Array.from(opts.addressBook, String);
|
|
337
|
+
this._session = new core.SigningSession(book);
|
|
289
338
|
this._pinnedChainName = null;
|
|
339
|
+
this._headFloor = null;
|
|
290
340
|
}
|
|
291
341
|
|
|
292
342
|
_guardMainnet() {
|
|
@@ -321,7 +371,7 @@ function makeClient(core) {
|
|
|
321
371
|
return BigInt(core.chainIdFromName(name));
|
|
322
372
|
}
|
|
323
373
|
|
|
324
|
-
async _call(method, body) {
|
|
374
|
+
async _call(method, body, strict) {
|
|
325
375
|
const controller = new AbortController();
|
|
326
376
|
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
327
377
|
try {
|
|
@@ -333,9 +383,10 @@ function makeClient(core) {
|
|
|
333
383
|
redirect: 'error',
|
|
334
384
|
});
|
|
335
385
|
const text = await readBounded(res);
|
|
386
|
+
if (strict && res.status === 200) core.checkGatewayJson(text);
|
|
336
387
|
let data;
|
|
337
388
|
try { data = JSON.parse(text); } catch { throw new Error('the node returned a non JSON response'); }
|
|
338
|
-
if (
|
|
389
|
+
if (res.status !== 200) {
|
|
339
390
|
const detail = data && typeof data === 'object' ? (data.message || data.error) : null;
|
|
340
391
|
throw new Error(detail || ('status ' + res.status));
|
|
341
392
|
}
|
|
@@ -345,10 +396,10 @@ function makeClient(core) {
|
|
|
345
396
|
}
|
|
346
397
|
}
|
|
347
398
|
|
|
348
|
-
nodeInfo() { return this._call('node_info', '{}'); }
|
|
399
|
+
nodeInfo() { return this._call('node_info', '{}', true); }
|
|
349
400
|
head() { return this._call('head', '{}'); }
|
|
350
401
|
async account(address) {
|
|
351
|
-
const acct = await this._call('get_account', core.account_body(address));
|
|
402
|
+
const acct = await this._call('get_account', core.account_body(address), true);
|
|
352
403
|
if (!acct || typeof acct.address !== 'string') {
|
|
353
404
|
throw new Error(`the gateway answered about ${address} without naming the account, refusing to trust it`);
|
|
354
405
|
}
|
|
@@ -357,215 +408,172 @@ function makeClient(core) {
|
|
|
357
408
|
}
|
|
358
409
|
return acct;
|
|
359
410
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (e !== n) {
|
|
365
|
-
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`);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
if (!this._nextNonces) this._nextNonces = new Map();
|
|
369
|
-
if (key != null) {
|
|
370
|
-
const local = this._nextNonces.get(key);
|
|
371
|
-
if (local == null || local < n) this._nextNonces.set(key, n);
|
|
372
|
-
}
|
|
373
|
-
return n;
|
|
411
|
+
|
|
412
|
+
_vet(sender, targets) {
|
|
413
|
+
this._session.rememberAddress(sender);
|
|
414
|
+
for (const target of targets) this._session.checkTarget(target);
|
|
374
415
|
}
|
|
375
416
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (!this._signedNonces) this._signedNonces = new Map();
|
|
379
|
-
let held = this._signedNonces.get(key);
|
|
380
|
-
if (!held) { held = new Map(); this._signedNonces.set(key, held); }
|
|
381
|
-
const head = until - VALIDITY_BLOCKS;
|
|
382
|
-
const now = monotonicSecs();
|
|
383
|
-
for (const [slotKey, entry] of held) {
|
|
384
|
-
if (head > entry.until && (now - entry.at) * HEAD_BLOCKS_PER_SEC >= VALIDITY_BLOCKS) held.delete(slotKey);
|
|
385
|
-
}
|
|
386
|
-
const seen = held.get(String(slot));
|
|
387
|
-
if (!explicit && seen != null && seen.txHex !== txHex) {
|
|
388
|
-
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`);
|
|
389
|
-
}
|
|
390
|
-
held.set(String(slot), { txHex, until, at: now });
|
|
417
|
+
_rememberTargets(targets) {
|
|
418
|
+
for (const target of targets) this._session.rememberAddress(target);
|
|
391
419
|
}
|
|
392
420
|
|
|
393
|
-
|
|
394
|
-
|
|
421
|
+
_expected(value, label) {
|
|
422
|
+
return value == null ? undefined : wholeNumber(value, label, U64_MAX);
|
|
423
|
+
}
|
|
424
|
+
|
|
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);
|
|
395
434
|
const verdict = outcome && outcome.verdict;
|
|
396
|
-
if (verdict
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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);
|
|
401
442
|
}
|
|
402
443
|
|
|
403
444
|
_validity(info) {
|
|
404
|
-
const
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
if (this._headFloor) {
|
|
413
|
-
const { height, at } = this._headFloor;
|
|
414
|
-
if (head < height) throw new Error(`the gateway reports head ${head} below the ${height} it reported earlier, refusing to sign`);
|
|
415
|
-
const tick = monotonicSecs();
|
|
416
|
-
const elapsed = tick > at ? tick - at : 0n;
|
|
417
|
-
const allowed = elapsed * HEAD_BLOCKS_PER_SEC + HEAD_SLACK_BLOCKS;
|
|
418
|
-
if (head > height + allowed) throw new Error(`the gateway head leapt from ${height} to ${head} faster than blocks are made, refusing to sign`);
|
|
419
|
-
}
|
|
420
|
-
core.checkValidUntil(until, head);
|
|
421
|
-
if (!this._headFloor || head > this._headFloor.height) this._headFloor = { height: head, at: monotonicSecs() };
|
|
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 };
|
|
422
453
|
return until;
|
|
423
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
|
+
|
|
424
465
|
transaction(txId) { return this._call('get_transaction', core.transaction_body(txId)); }
|
|
425
466
|
block(height) { return this._call('get_block', core.block_by_height_body(wholeNumber(height, 'height', U64_MAX))); }
|
|
426
|
-
submit(txHex) { return this._call('submit_transaction', core.submit_body(txHex)); }
|
|
467
|
+
submit(txHex) { return this._call('submit_transaction', core.submit_body(txHex), true); }
|
|
427
468
|
container(address) { return this._call('get_container', JSON.stringify({ address })); }
|
|
428
469
|
storage(address) { return this._call('get_storage', JSON.stringify({ address })); }
|
|
429
470
|
events(height) { return this._call('get_events', core.eventsBody(wholeNumber(height, 'height', U64_MAX))); }
|
|
430
471
|
|
|
431
|
-
address(
|
|
472
|
+
address(seed, index) { return core.address(seed, accountIndex(index)); }
|
|
432
473
|
|
|
433
|
-
async transfer(
|
|
474
|
+
async transfer(seed, index, to, amount, maxFeeQuon, expectedNonce) {
|
|
434
475
|
if (!core.valid_address(to)) throw new Error('the recipient is not a q1 address');
|
|
435
476
|
checkAmount(amount);
|
|
436
477
|
const ceiling = feeCeiling(maxFeeQuon);
|
|
478
|
+
const from = core.address(seed, accountIndex(index));
|
|
479
|
+
this._vet(from, [to]);
|
|
437
480
|
const info = await this.nodeInfo();
|
|
438
481
|
this._guardMainnet();
|
|
439
482
|
const chainId = this._signingChainId(info);
|
|
440
|
-
const
|
|
441
|
-
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
442
|
-
const fee = gatewayFee(reported);
|
|
483
|
+
const fee = this._transferFee(info);
|
|
443
484
|
if (fee > ceiling) {
|
|
444
485
|
throw new Error(`the gateway fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
445
486
|
}
|
|
446
487
|
const until = this._validity(info);
|
|
447
|
-
const
|
|
448
|
-
const acct = await this.account(from);
|
|
449
|
-
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
450
|
-
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
488
|
+
const nonce = await this._accountSlot(from, expectedNonce);
|
|
451
489
|
const signed = JSON.parse(
|
|
452
|
-
core.sign_transfer(
|
|
490
|
+
core.sign_transfer(seed, accountIndex(index), to, String(amount), nonce, String(fee), chainId, until)
|
|
453
491
|
);
|
|
454
|
-
this.
|
|
455
|
-
const outcome = await this.
|
|
456
|
-
this._remember(from, nonce, outcome);
|
|
492
|
+
this._rememberTargets([to]);
|
|
493
|
+
const outcome = await this._submitHeld([[from, nonce]], signed.tx_hex);
|
|
457
494
|
return { signed, outcome };
|
|
458
495
|
}
|
|
459
496
|
|
|
460
|
-
async register(
|
|
497
|
+
async register(seed, index, maxFeeQuon, expectedNonce) {
|
|
461
498
|
const ceiling = feeCeiling(maxFeeQuon);
|
|
499
|
+
const from = core.address(seed, accountIndex(index));
|
|
500
|
+
this._vet(from, []);
|
|
462
501
|
const info = await this.nodeInfo();
|
|
463
502
|
this._guardMainnet();
|
|
464
503
|
const chainId = this._signingChainId(info);
|
|
465
|
-
const
|
|
466
|
-
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
467
|
-
const fee = gatewayFee(reported);
|
|
504
|
+
const fee = this._transferFee(info);
|
|
468
505
|
if (fee > ceiling) {
|
|
469
506
|
throw new Error(`the gateway fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
470
507
|
}
|
|
471
508
|
const until = this._validity(info);
|
|
472
|
-
const
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
476
|
-
const signed = JSON.parse(core.signRegister(seedHex, accountIndex(index), nonce, String(fee), chainId, until));
|
|
477
|
-
this._guardSigned(from, nonce, signed.tx_hex, until, expectedNonce != null);
|
|
478
|
-
const outcome = await this.submit(signed.tx_hex);
|
|
479
|
-
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);
|
|
480
512
|
return { signed, outcome };
|
|
481
513
|
}
|
|
482
514
|
|
|
483
|
-
async call(
|
|
515
|
+
async call(seed, index, target, argsHex, meterLimit, maxFeeQuon, expectedNonce) {
|
|
484
516
|
if (!core.valid_address(target)) throw new Error('the target is not a q1 address');
|
|
485
517
|
const ceiling = feeCeiling(maxFeeQuon);
|
|
518
|
+
const from = core.address(seed, accountIndex(index));
|
|
519
|
+
this._vet(from, [target]);
|
|
486
520
|
const info = await this.nodeInfo();
|
|
487
521
|
this._guardMainnet();
|
|
488
522
|
const chainId = this._signingChainId(info);
|
|
489
|
-
const
|
|
490
|
-
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
491
|
-
const transferFee = gatewayFee(reported);
|
|
492
|
-
const fee = BigInt(core.vmCallFee(String(transferFee), meterLimitOf(meterLimit)));
|
|
493
|
-
if (fee > ceiling) {
|
|
494
|
-
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
495
|
-
}
|
|
523
|
+
const { transferFee, fee } = this._callFee(info, meterLimit, ceiling, maxFeeQuon);
|
|
496
524
|
const until = this._validity(info);
|
|
497
|
-
const
|
|
498
|
-
const acct = await this.account(from);
|
|
499
|
-
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
500
|
-
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
525
|
+
const nonce = await this._accountSlot(from, expectedNonce);
|
|
501
526
|
const signed = JSON.parse(
|
|
502
|
-
core.sign_call(
|
|
527
|
+
core.sign_call(seed, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), chainId, until, String(transferFee))
|
|
503
528
|
);
|
|
504
|
-
this.
|
|
505
|
-
const outcome = await this.
|
|
506
|
-
this._remember(from, nonce, outcome);
|
|
529
|
+
this._rememberTargets([target]);
|
|
530
|
+
const outcome = await this._submitHeld([[from, nonce]], signed.tx_hex);
|
|
507
531
|
return { signed, outcome };
|
|
508
532
|
}
|
|
509
533
|
|
|
510
|
-
async assetCall(
|
|
534
|
+
async assetCall(seed, index, target, argsHex, assetIssuer, amount, meterLimit, maxFeeQuon, expectedNonce) {
|
|
511
535
|
if (!core.valid_address(target)) throw new Error('the target is not a q1 address');
|
|
512
536
|
if (!core.valid_address(assetIssuer)) throw new Error('the asset issuer is not a q1 address');
|
|
513
537
|
checkAmount(amount);
|
|
514
538
|
const ceiling = feeCeiling(maxFeeQuon);
|
|
539
|
+
const from = core.address(seed, accountIndex(index));
|
|
540
|
+
this._vet(from, [target, assetIssuer]);
|
|
515
541
|
const info = await this.nodeInfo();
|
|
516
542
|
this._guardMainnet();
|
|
517
543
|
const chainId = this._signingChainId(info);
|
|
518
|
-
const
|
|
519
|
-
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
520
|
-
const transferFee = gatewayFee(reported);
|
|
521
|
-
const fee = BigInt(core.vmCallFee(String(transferFee), meterLimitOf(meterLimit)));
|
|
522
|
-
if (fee > ceiling) {
|
|
523
|
-
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
524
|
-
}
|
|
544
|
+
const { transferFee, fee } = this._callFee(info, meterLimit, ceiling, maxFeeQuon);
|
|
525
545
|
const until = this._validity(info);
|
|
526
|
-
const
|
|
527
|
-
const acct = await this.account(from);
|
|
528
|
-
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
529
|
-
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
546
|
+
const nonce = await this._accountSlot(from, expectedNonce);
|
|
530
547
|
const signed = JSON.parse(
|
|
531
|
-
core.signAssetCall(
|
|
548
|
+
core.signAssetCall(seed, accountIndex(index), target, argsHex, assetIssuer, String(amount), nonce, meterLimitOf(meterLimit), String(fee), chainId, until, String(transferFee))
|
|
532
549
|
);
|
|
533
|
-
this.
|
|
534
|
-
const outcome = await this.
|
|
535
|
-
this._remember(from, nonce, outcome);
|
|
550
|
+
this._rememberTargets([target, assetIssuer]);
|
|
551
|
+
const outcome = await this._submitHeld([[from, nonce]], signed.tx_hex);
|
|
536
552
|
return { signed, outcome };
|
|
537
553
|
}
|
|
538
554
|
|
|
539
|
-
async payableCall(
|
|
555
|
+
async payableCall(seed, index, target, argsHex, value, meterLimit, maxFeeQuon, expectedNonce) {
|
|
540
556
|
if (!core.valid_address(target)) throw new Error('the target is not a q1 address');
|
|
541
557
|
checkAmount(value);
|
|
542
558
|
const ceiling = feeCeiling(maxFeeQuon);
|
|
559
|
+
const from = core.address(seed, accountIndex(index));
|
|
560
|
+
this._vet(from, [target]);
|
|
543
561
|
const info = await this.nodeInfo();
|
|
544
562
|
this._guardMainnet();
|
|
545
563
|
const chainId = this._signingChainId(info);
|
|
546
|
-
const
|
|
547
|
-
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
548
|
-
const transferFee = gatewayFee(reported);
|
|
549
|
-
const fee = BigInt(core.vmCallFee(String(transferFee), meterLimitOf(meterLimit)));
|
|
550
|
-
if (fee > ceiling) {
|
|
551
|
-
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
552
|
-
}
|
|
564
|
+
const { transferFee, fee } = this._callFee(info, meterLimit, ceiling, maxFeeQuon);
|
|
553
565
|
const until = this._validity(info);
|
|
554
|
-
const
|
|
555
|
-
const acct = await this.account(from);
|
|
556
|
-
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
557
|
-
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
566
|
+
const nonce = await this._accountSlot(from, expectedNonce);
|
|
558
567
|
const signed = JSON.parse(
|
|
559
|
-
core.signPayableCall(
|
|
568
|
+
core.signPayableCall(seed, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), String(value), chainId, until, String(transferFee))
|
|
560
569
|
);
|
|
561
|
-
this.
|
|
562
|
-
const outcome = await this.
|
|
563
|
-
this._remember(from, nonce, outcome);
|
|
570
|
+
this._rememberTargets([target]);
|
|
571
|
+
const outcome = await this._submitHeld([[from, nonce]], signed.tx_hex);
|
|
564
572
|
return { signed, outcome };
|
|
565
573
|
}
|
|
566
574
|
|
|
567
575
|
async _slotValue(contract, key) {
|
|
568
|
-
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);
|
|
569
577
|
return BigInt(core.storageValue(JSON.stringify(resp), key));
|
|
570
578
|
}
|
|
571
579
|
|
|
@@ -577,35 +585,39 @@ function makeClient(core) {
|
|
|
577
585
|
return this._slotValue(contract, core.scalarSlotKey(wholeNumber(slot, 'slot', U64_MAX)));
|
|
578
586
|
}
|
|
579
587
|
|
|
580
|
-
async
|
|
588
|
+
async contractMap(contract, mapDomainTag, keyHex) {
|
|
589
|
+
return this._slotValue(contract, core.mapSlotKey(wholeNumber(mapDomainTag, 'map domain tag', U64_MAX), keyHex));
|
|
590
|
+
}
|
|
591
|
+
|
|
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) {
|
|
581
601
|
if (!core.valid_address(contract)) throw new Error('the contract is not a q1 address');
|
|
582
602
|
const ceiling = feeCeiling(maxFeeQuon);
|
|
603
|
+
const from = core.address(callerSeed, accountIndex(callerIndex));
|
|
604
|
+
this._vet(from, [contract]);
|
|
583
605
|
const info = await this.nodeInfo();
|
|
584
606
|
this._guardMainnet();
|
|
585
607
|
const chainId = this._signingChainId(info);
|
|
586
|
-
const
|
|
587
|
-
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
588
|
-
const transferFee = gatewayFee(reported);
|
|
589
|
-
const fee = BigInt(core.vmCallFee(String(transferFee), meterLimitOf(meterLimit)));
|
|
590
|
-
if (fee > ceiling) {
|
|
591
|
-
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
592
|
-
}
|
|
608
|
+
const { transferFee, fee } = this._callFee(info, meterLimit, ceiling, maxFeeQuon);
|
|
593
609
|
const until = this._validity(info);
|
|
594
|
-
const signer = core.orderSigner(
|
|
610
|
+
const signer = core.orderSigner(ownerSeed, accountIndex(ownerIndex));
|
|
595
611
|
const orderKey = contract + '/' + signer;
|
|
596
612
|
const reportedOrder = await this.contractNonce(contract, signer);
|
|
597
|
-
|
|
598
|
-
throw new Error(
|
|
599
|
-
`the gateway reported order nonce ${reportedOrder} but you expected ${expectedOrderNonce}, refusing to sign`
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
const nonce = this._checkedNonce(reportedOrder, expectedOrderNonce, orderKey);
|
|
613
|
+
const nonce = BigInt(this._session.slot(orderKey, reportedOrder, this._expected(expectedOrderNonce, 'expected order nonce'), true));
|
|
603
614
|
const order = JSON.parse(core.buildTypedOrderCall(
|
|
604
615
|
chainId,
|
|
605
616
|
contract,
|
|
606
617
|
selectorHex,
|
|
607
618
|
wholeNumber(orderSpec.schemeOff, 'scheme offset', U64_MAX),
|
|
608
619
|
wholeNumber(orderSpec.ptrOff, 'pointer offset', U64_MAX),
|
|
620
|
+
wholeNumber(orderSpec.expiryOff, 'expiry offset', U64_MAX),
|
|
609
621
|
orderSpec.regionOff == null ? 0n : wholeNumber(orderSpec.regionOff, 'region offset', U64_MAX),
|
|
610
622
|
JSON.stringify(orderSpec.fields || [], (k, v) => {
|
|
611
623
|
if (typeof v !== 'bigint') return v;
|
|
@@ -613,22 +625,17 @@ function makeClient(core) {
|
|
|
613
625
|
if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`the order field ${k} ${v} is too large to carry`);
|
|
614
626
|
return Number(v);
|
|
615
627
|
}),
|
|
616
|
-
|
|
628
|
+
ownerSeed,
|
|
617
629
|
accountIndex(ownerIndex),
|
|
618
630
|
nonce,
|
|
631
|
+
wholeNumber(orderSpec.expiry, 'order expiry', U64_MAX),
|
|
619
632
|
));
|
|
620
|
-
const
|
|
621
|
-
const acct = await this.account(from);
|
|
622
|
-
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
623
|
-
const accountNonceUsed = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
633
|
+
const accountNonceUsed = await this._accountSlot(from, expectedNonce);
|
|
624
634
|
const signed = JSON.parse(
|
|
625
|
-
core.sign_call(
|
|
635
|
+
core.sign_call(callerSeed, accountIndex(callerIndex), contract, order.call_args, accountNonceUsed, meterLimitOf(meterLimit), String(fee), chainId, until, String(transferFee))
|
|
626
636
|
);
|
|
627
|
-
this.
|
|
628
|
-
this.
|
|
629
|
-
const outcome = await this.submit(signed.tx_hex);
|
|
630
|
-
this._remember(from, accountNonceUsed, outcome);
|
|
631
|
-
this._remember(orderKey, nonce, outcome);
|
|
637
|
+
this._rememberTargets([contract]);
|
|
638
|
+
const outcome = await this._submitHeld([[orderKey, nonce], [from, accountNonceUsed]], signed.tx_hex);
|
|
632
639
|
return { order, signed, outcome, orderNonce: nonce };
|
|
633
640
|
}
|
|
634
641
|
};
|