@quantovainc/qcore 0.1.30 → 0.1.32
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 +1 -1
- package/browser.mjs +2 -2
- package/index.d.ts +21 -3
- package/index.js +2 -2
- package/package.json +3 -2
- package/pkg/package.json +1 -1
- package/pkg/qcore_js.d.ts +7 -7
- package/pkg/qcore_js_bg.js +34 -46
- package/pkg/qcore_js_bg.wasm +0 -0
- package/pkg/qcore_js_bg.wasm.d.ts +7 -7
- package/pkg-node/package.json +1 -1
- package/pkg-node/qcore_js.d.ts +7 -7
- package/pkg-node/qcore_js.js +34 -46
- package/pkg-node/qcore_js_bg.wasm +0 -0
- package/shared.js +151 -57
- package/shared.mjs +546 -0
package/shared.mjs
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
// Copyright 2026 Quantova Inc
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
|
+
|
|
4
|
+
const MAX_RESPONSE = 8 * 1024 * 1024;
|
|
5
|
+
const TIMEOUT_MS = 20000;
|
|
6
|
+
|
|
7
|
+
function feeCeiling(maxFeeQuon) {
|
|
8
|
+
if (typeof maxFeeQuon === 'number') {
|
|
9
|
+
throw new Error('pass the maximum fee as a decimal string or a BigInt, never a JavaScript number, because a number silently rounds above 2^53 and could set the ceiling higher than you intended');
|
|
10
|
+
}
|
|
11
|
+
let ceiling;
|
|
12
|
+
try {
|
|
13
|
+
ceiling = BigInt(maxFeeQuon);
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error('the maximum fee must be an integer number of Quon');
|
|
16
|
+
}
|
|
17
|
+
if (ceiling < 0n) throw new Error('the maximum fee cannot be negative');
|
|
18
|
+
return ceiling;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function checkAmount(amount) {
|
|
22
|
+
if (typeof amount === 'number') {
|
|
23
|
+
throw new Error('pass the amount as a decimal string or a BigInt, never a JavaScript number, because a number silently rounds above 2^53 and would sign a wrong amount');
|
|
24
|
+
}
|
|
25
|
+
if (typeof amount !== 'string' && typeof amount !== 'bigint') {
|
|
26
|
+
throw new Error('the amount must be a decimal string or a BigInt');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function accountIndex(index) {
|
|
31
|
+
const i = BigInt(index);
|
|
32
|
+
if (i < 0n || i > 0xffffffffffffffffn) {
|
|
33
|
+
throw new Error('the account index must fit in an unsigned 64 bit integer');
|
|
34
|
+
}
|
|
35
|
+
return i;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function accountNonce(nonce) {
|
|
39
|
+
if (typeof nonce === 'number' && !Number.isSafeInteger(nonce)) {
|
|
40
|
+
throw new Error('the gateway reported a nonce outside the safe integer range, a number that large silently rounds and would sign a different nonce');
|
|
41
|
+
}
|
|
42
|
+
let n;
|
|
43
|
+
try {
|
|
44
|
+
n = BigInt(nonce);
|
|
45
|
+
} catch {
|
|
46
|
+
throw new Error('the gateway reported a nonce that is not a whole number');
|
|
47
|
+
}
|
|
48
|
+
if (n < 0n || n > 0xffffffffffffffffn) {
|
|
49
|
+
throw new Error('the gateway reported a nonce outside the unsigned 64 bit range');
|
|
50
|
+
}
|
|
51
|
+
return n;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const VALIDITY_BLOCKS = 300n;
|
|
55
|
+
const MAX_PLAUSIBLE_HEAD = 1n << 40n;
|
|
56
|
+
const HEAD_BLOCKS_PER_SEC = 4n;
|
|
57
|
+
const HEAD_SLACK_SECS = 60n;
|
|
58
|
+
const TRANSFER_METER = 1210n;
|
|
59
|
+
|
|
60
|
+
function meterLimitOf(meterLimit) {
|
|
61
|
+
if (typeof meterLimit === 'number' && !Number.isSafeInteger(meterLimit)) {
|
|
62
|
+
throw new Error('the meter limit must be a whole number in the safe integer range');
|
|
63
|
+
}
|
|
64
|
+
let meter;
|
|
65
|
+
try {
|
|
66
|
+
meter = BigInt(meterLimit);
|
|
67
|
+
} catch {
|
|
68
|
+
throw new Error('the meter limit must be a whole number');
|
|
69
|
+
}
|
|
70
|
+
if (meter < 0n || meter > 0xffffffffffffffffn) {
|
|
71
|
+
throw new Error('the meter limit must fit in an unsigned 64 bit integer');
|
|
72
|
+
}
|
|
73
|
+
return meter;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function vmCallFee(transferFee, meterLimit) {
|
|
77
|
+
const meter = meterLimitOf(meterLimit);
|
|
78
|
+
let units = (meter + TRANSFER_METER - 1n) / TRANSFER_METER;
|
|
79
|
+
if (units < 1n) units = 1n;
|
|
80
|
+
return BigInt(transferFee) * units;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function validUntil(info) {
|
|
84
|
+
const head = info && info.head_height;
|
|
85
|
+
if (head == null) throw new Error('the gateway did not report a head height to bound the transaction to');
|
|
86
|
+
if (typeof head === 'number' && !Number.isSafeInteger(head)) {
|
|
87
|
+
throw new Error('the gateway reported a head height outside the safe integer range');
|
|
88
|
+
}
|
|
89
|
+
let h;
|
|
90
|
+
try {
|
|
91
|
+
h = BigInt(head);
|
|
92
|
+
} catch {
|
|
93
|
+
throw new Error('the gateway reported a head height that is not a whole number');
|
|
94
|
+
}
|
|
95
|
+
if (h < 0n || h > MAX_PLAUSIBLE_HEAD) {
|
|
96
|
+
throw new Error('the gateway reported a head height past any height this chain can have reached');
|
|
97
|
+
}
|
|
98
|
+
return h + VALIDITY_BLOCKS;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function gatewayFee(fee) {
|
|
102
|
+
if (typeof fee === 'number' && !Number.isSafeInteger(fee)) {
|
|
103
|
+
throw new Error('the gateway reported a fee outside the safe integer range, a number that large silently rounds and would sign a different fee');
|
|
104
|
+
}
|
|
105
|
+
let f;
|
|
106
|
+
try {
|
|
107
|
+
f = BigInt(fee);
|
|
108
|
+
} catch {
|
|
109
|
+
throw new Error('the gateway reported a fee that is not a whole number');
|
|
110
|
+
}
|
|
111
|
+
if (f < 0n) throw new Error('the gateway reported a negative fee');
|
|
112
|
+
return f;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function readBounded(res) {
|
|
116
|
+
if (!res.body || typeof res.body.getReader !== 'function') {
|
|
117
|
+
const header = res.headers.get('content-length');
|
|
118
|
+
const len = header == null ? NaN : Number(header);
|
|
119
|
+
if (!Number.isFinite(len) || len <= 0) {
|
|
120
|
+
throw new Error('the response has no content-length to bound it and cannot be read safely');
|
|
121
|
+
}
|
|
122
|
+
if (len > MAX_RESPONSE) throw new Error('the response is too large');
|
|
123
|
+
return await res.text();
|
|
124
|
+
}
|
|
125
|
+
const reader = res.body.getReader();
|
|
126
|
+
const chunks = [];
|
|
127
|
+
let total = 0;
|
|
128
|
+
for (;;) {
|
|
129
|
+
const { done, value } = await reader.read();
|
|
130
|
+
if (done) break;
|
|
131
|
+
total += value.byteLength;
|
|
132
|
+
if (total > MAX_RESPONSE) {
|
|
133
|
+
try { await reader.cancel(); } catch {}
|
|
134
|
+
throw new Error('the response is too large');
|
|
135
|
+
}
|
|
136
|
+
chunks.push(value);
|
|
137
|
+
}
|
|
138
|
+
const merged = new Uint8Array(total);
|
|
139
|
+
let at = 0;
|
|
140
|
+
for (const chunk of chunks) {
|
|
141
|
+
merged.set(chunk, at);
|
|
142
|
+
at += chunk.byteLength;
|
|
143
|
+
}
|
|
144
|
+
return new TextDecoder().decode(merged);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isLoopbackHost(hostname) {
|
|
148
|
+
if (!hostname) return false;
|
|
149
|
+
const host = hostname.toLowerCase();
|
|
150
|
+
if (host === 'localhost') return true;
|
|
151
|
+
if (host === '::1' || host === '[::1]') return true;
|
|
152
|
+
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
153
|
+
if (m) {
|
|
154
|
+
const octets = [m[1], m[2], m[3], m[4]].map(Number);
|
|
155
|
+
if (octets.every((o) => o <= 255) && octets[0] === 127) return true;
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function requireSafeTransport(base) {
|
|
161
|
+
let url;
|
|
162
|
+
try {
|
|
163
|
+
url = new URL(base);
|
|
164
|
+
} catch {
|
|
165
|
+
throw new Error('the gateway base must be an absolute http:// or https:// URL');
|
|
166
|
+
}
|
|
167
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
168
|
+
throw new Error('the gateway base must start with http:// or https://');
|
|
169
|
+
}
|
|
170
|
+
if (url.protocol === 'http:' && !isLoopbackHost(url.hostname)) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`refusing plaintext http to a non loopback gateway (${url.hostname}); its fee and nonce would be unauthenticated and rewritable to drain funds, use https or a loopback node`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return base;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const DENOMINATION = 'Quon';
|
|
179
|
+
const DECIMALS = 6;
|
|
180
|
+
|
|
181
|
+
class Network {
|
|
182
|
+
constructor(fields) {
|
|
183
|
+
this.name = fields.name;
|
|
184
|
+
this.chainId = fields.chainId;
|
|
185
|
+
this.rpcUrl = fields.rpcUrl || null;
|
|
186
|
+
this.explorerUrl = fields.explorerUrl || null;
|
|
187
|
+
this.denomination = fields.denomination || DENOMINATION;
|
|
188
|
+
this.decimals = fields.decimals == null ? DECIMALS : fields.decimals;
|
|
189
|
+
this.isMainnet = fields.isMainnet === true;
|
|
190
|
+
Object.freeze(this);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
static testnet() {
|
|
194
|
+
return new Network({
|
|
195
|
+
name: 'testnet',
|
|
196
|
+
chainId: 'Q-test-net-3',
|
|
197
|
+
rpcUrl: 'https://rpc-testnet.quantova.org',
|
|
198
|
+
explorerUrl: 'https://qvmscan.io',
|
|
199
|
+
isMainnet: false,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
static mainnet() {
|
|
204
|
+
return new Network({
|
|
205
|
+
name: 'mainnet',
|
|
206
|
+
chainId: 'Q-main-net-1',
|
|
207
|
+
rpcUrl: null,
|
|
208
|
+
explorerUrl: 'https://qvmscan.io',
|
|
209
|
+
isMainnet: true,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
static forUrl(base) {
|
|
214
|
+
return new Network({ name: 'custom', chainId: null, rpcUrl: base, isMainnet: false });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function generateSeed() {
|
|
219
|
+
const source = (typeof globalThis !== 'undefined' && globalThis.crypto) || (typeof crypto !== 'undefined' ? crypto : null);
|
|
220
|
+
if (!source || typeof source.getRandomValues !== 'function') {
|
|
221
|
+
throw new Error('no cryptographic random source is available; a secure context provides crypto.getRandomValues');
|
|
222
|
+
}
|
|
223
|
+
const bytes = new Uint8Array(32);
|
|
224
|
+
source.getRandomValues(bytes);
|
|
225
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function makeClient(core) {
|
|
229
|
+
return class Client {
|
|
230
|
+
constructor(target, options) {
|
|
231
|
+
const opts = options || {};
|
|
232
|
+
this.acknowledgeMainnet = opts.acknowledgeMainnet === true;
|
|
233
|
+
let base;
|
|
234
|
+
if (target instanceof Network) {
|
|
235
|
+
this.network = target;
|
|
236
|
+
base = target.rpcUrl;
|
|
237
|
+
if (!base) {
|
|
238
|
+
throw new Error(`the ${target.name} network has no rpc endpoint yet, pass the endpoint explicitly with new Client(url)`);
|
|
239
|
+
}
|
|
240
|
+
if (target.isMainnet && !this.acknowledgeMainnet) {
|
|
241
|
+
throw new Error('refusing to open a mainnet client without acknowledgeMainnet true, a mainnet transaction moves real value so the network must be chosen on purpose');
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
base = String(target);
|
|
245
|
+
this.network = opts.network instanceof Network ? opts.network : Network.forUrl(base);
|
|
246
|
+
}
|
|
247
|
+
this.base = requireSafeTransport(base).replace(/\/$/, '');
|
|
248
|
+
this.expectedChainId =
|
|
249
|
+
typeof opts.expectedChainId === 'string' && opts.expectedChainId.length > 0
|
|
250
|
+
? opts.expectedChainId
|
|
251
|
+
: null;
|
|
252
|
+
this._pinnedChainName = null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_guardMainnet() {
|
|
256
|
+
const onMainnet = this.network && this.network.isMainnet === true;
|
|
257
|
+
if (onMainnet && !this.acknowledgeMainnet) {
|
|
258
|
+
throw new Error(`refusing to sign for the mainnet network ${this.network.chainId || ''} without acknowledgeMainnet true, pass it when you mean to move real value`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
_signingChainId(info) {
|
|
263
|
+
const name = info && info.chain_id;
|
|
264
|
+
if (!name) throw new Error('the gateway did not report a chain id to bind the signature to');
|
|
265
|
+
if (typeof name !== 'string') throw new Error('the gateway reported a chain id that is not a string, refusing to bind a signature to it');
|
|
266
|
+
const id = BigInt(core.chainIdFromName(name));
|
|
267
|
+
const configured =
|
|
268
|
+
this.expectedChainId || (this.network && this.network.chainId) || null;
|
|
269
|
+
if (configured && name !== configured) {
|
|
270
|
+
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`);
|
|
271
|
+
}
|
|
272
|
+
if (!this.acknowledgeMainnet && this._isMainnetId(id)) {
|
|
273
|
+
throw new Error(`the gateway reports the mainnet chain ${name}; refusing to sign a mainnet transaction without acknowledgeMainnet`);
|
|
274
|
+
}
|
|
275
|
+
if (this._pinnedChainName === null) {
|
|
276
|
+
this._pinnedChainName = name;
|
|
277
|
+
} else if (this._pinnedChainName !== name) {
|
|
278
|
+
throw new Error(`the gateway reported chain ${this._pinnedChainName} earlier and now reports ${name}; refusing to sign, the endpoint is not naming one network`);
|
|
279
|
+
}
|
|
280
|
+
return id;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
_isMainnetId(id) {
|
|
284
|
+
return id === BigInt(core.mainnetChainId());
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async _call(method, body) {
|
|
288
|
+
const controller = new AbortController();
|
|
289
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
290
|
+
try {
|
|
291
|
+
const res = await fetch(this.base + '/v1/' + method, {
|
|
292
|
+
method: 'POST',
|
|
293
|
+
headers: { 'Content-Type': 'application/json' },
|
|
294
|
+
body: body || '{}',
|
|
295
|
+
signal: controller.signal,
|
|
296
|
+
redirect: 'error',
|
|
297
|
+
});
|
|
298
|
+
const text = await readBounded(res);
|
|
299
|
+
let data;
|
|
300
|
+
try { data = JSON.parse(text); } catch { throw new Error('the node returned a non JSON response'); }
|
|
301
|
+
if (!res.ok) {
|
|
302
|
+
const detail = data && typeof data === 'object' ? (data.message || data.error) : null;
|
|
303
|
+
throw new Error(detail || ('status ' + res.status));
|
|
304
|
+
}
|
|
305
|
+
return data;
|
|
306
|
+
} finally {
|
|
307
|
+
clearTimeout(timer);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
nodeInfo() { return this._call('node_info', '{}'); }
|
|
312
|
+
head() { return this._call('head', '{}'); }
|
|
313
|
+
async account(address) {
|
|
314
|
+
const acct = await this._call('get_account', core.account_body(address));
|
|
315
|
+
if (!acct || typeof acct.address !== 'string') {
|
|
316
|
+
throw new Error(`the gateway answered about ${address} without naming the account, refusing to trust it`);
|
|
317
|
+
}
|
|
318
|
+
if (acct.address !== address) {
|
|
319
|
+
throw new Error(`the gateway answered for ${acct.address} when asked about ${address}, refusing to trust it`);
|
|
320
|
+
}
|
|
321
|
+
return acct;
|
|
322
|
+
}
|
|
323
|
+
_checkedNonce(reported, expected, key) {
|
|
324
|
+
const n = accountNonce(reported);
|
|
325
|
+
if (!this._nextNonces) this._nextNonces = new Map();
|
|
326
|
+
const local = key != null ? this._nextNonces.get(key) : undefined;
|
|
327
|
+
const e = expected != null ? BigInt(expected) : local;
|
|
328
|
+
if (e == null) return n;
|
|
329
|
+
if (n > e) throw new Error(`the gateway reported nonce ${n} above the expected ${e}; refusing so a signature cannot be banked for a nonce the account has not reached`);
|
|
330
|
+
return expected != null ? e : n;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
_remember(key, used, outcome) {
|
|
334
|
+
if (!this._nextNonces) this._nextNonces = new Map();
|
|
335
|
+
if (outcome && outcome.verdict === 'accepted') this._nextNonces.set(key, BigInt(used) + 1n);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
_validity(info) {
|
|
339
|
+
const until = validUntil(info);
|
|
340
|
+
const head = until - VALIDITY_BLOCKS;
|
|
341
|
+
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
342
|
+
if (this._headFloor) {
|
|
343
|
+
const { height, at } = this._headFloor;
|
|
344
|
+
if (head < height) throw new Error(`the gateway reports head ${head} below the ${height} it reported earlier, refusing to sign`);
|
|
345
|
+
const allowed = (now - at + HEAD_SLACK_SECS) * HEAD_BLOCKS_PER_SEC;
|
|
346
|
+
if (head > height + allowed) throw new Error(`the gateway head leapt from ${height} to ${head} faster than blocks are made, refusing to sign`);
|
|
347
|
+
} else {
|
|
348
|
+
this._headFloor = { height: head, at: now };
|
|
349
|
+
}
|
|
350
|
+
return until;
|
|
351
|
+
}
|
|
352
|
+
transaction(txId) { return this._call('get_transaction', core.transaction_body(txId)); }
|
|
353
|
+
block(height) { return this._call('get_block', core.block_by_height_body(BigInt(height))); }
|
|
354
|
+
submit(txHex) { return this._call('submit_transaction', core.submit_body(txHex)); }
|
|
355
|
+
container(address) { return this._call('get_container', JSON.stringify({ address })); }
|
|
356
|
+
storage(address) { return this._call('get_storage', JSON.stringify({ address })); }
|
|
357
|
+
events(height) { return this._call('get_events', core.eventsBody(BigInt(height))); }
|
|
358
|
+
|
|
359
|
+
address(seedHex, index) { return core.address(seedHex, accountIndex(index)); }
|
|
360
|
+
|
|
361
|
+
async transfer(seedHex, index, to, amount, maxFeeQuon, expectedNonce) {
|
|
362
|
+
if (!core.valid_address(to)) throw new Error('the recipient is not a q1 address');
|
|
363
|
+
checkAmount(amount);
|
|
364
|
+
const ceiling = feeCeiling(maxFeeQuon);
|
|
365
|
+
const info = await this.nodeInfo();
|
|
366
|
+
this._guardMainnet();
|
|
367
|
+
const chainId = this._signingChainId(info);
|
|
368
|
+
const fee = info && info.fee && info.fee.transfer_quon;
|
|
369
|
+
if (fee == null) throw new Error('the gateway did not report a transfer fee');
|
|
370
|
+
if (gatewayFee(fee) > ceiling) {
|
|
371
|
+
throw new Error(`the gateway fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
372
|
+
}
|
|
373
|
+
const from = core.address(seedHex, accountIndex(index));
|
|
374
|
+
const acct = await this.account(from);
|
|
375
|
+
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
376
|
+
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
377
|
+
const signed = JSON.parse(
|
|
378
|
+
core.sign_transfer(seedHex, accountIndex(index), to, String(amount), nonce, String(fee), chainId, this._validity(info))
|
|
379
|
+
);
|
|
380
|
+
const outcome = await this.submit(signed.tx_hex);
|
|
381
|
+
this._remember(from, nonce, outcome);
|
|
382
|
+
return { signed, outcome };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async register(seedHex, index, maxFeeQuon, expectedNonce) {
|
|
386
|
+
const ceiling = feeCeiling(maxFeeQuon);
|
|
387
|
+
const info = await this.nodeInfo();
|
|
388
|
+
this._guardMainnet();
|
|
389
|
+
const chainId = this._signingChainId(info);
|
|
390
|
+
const fee = info && info.fee && info.fee.transfer_quon;
|
|
391
|
+
if (fee == null) throw new Error('the gateway did not report a transfer fee');
|
|
392
|
+
if (gatewayFee(fee) > ceiling) {
|
|
393
|
+
throw new Error(`the gateway fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
394
|
+
}
|
|
395
|
+
const from = core.address(seedHex, accountIndex(index));
|
|
396
|
+
const acct = await this.account(from);
|
|
397
|
+
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
398
|
+
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
399
|
+
const signed = JSON.parse(core.signRegister(seedHex, accountIndex(index), nonce, String(fee), chainId, this._validity(info)));
|
|
400
|
+
const outcome = await this.submit(signed.tx_hex);
|
|
401
|
+
this._remember(from, nonce, outcome);
|
|
402
|
+
return { signed, outcome };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async call(seedHex, index, target, argsHex, meterLimit, maxFeeQuon, expectedNonce) {
|
|
406
|
+
if (!core.valid_address(target)) throw new Error('the target is not a q1 address');
|
|
407
|
+
const ceiling = feeCeiling(maxFeeQuon);
|
|
408
|
+
const info = await this.nodeInfo();
|
|
409
|
+
this._guardMainnet();
|
|
410
|
+
const chainId = this._signingChainId(info);
|
|
411
|
+
const reported = info && info.fee && info.fee.transfer_quon;
|
|
412
|
+
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
413
|
+
const fee = vmCallFee(gatewayFee(reported), meterLimit);
|
|
414
|
+
if (fee > ceiling) {
|
|
415
|
+
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
416
|
+
}
|
|
417
|
+
const from = core.address(seedHex, accountIndex(index));
|
|
418
|
+
const acct = await this.account(from);
|
|
419
|
+
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
420
|
+
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
421
|
+
const signed = JSON.parse(
|
|
422
|
+
core.sign_call(seedHex, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
|
|
423
|
+
);
|
|
424
|
+
const outcome = await this.submit(signed.tx_hex);
|
|
425
|
+
this._remember(from, nonce, outcome);
|
|
426
|
+
return { signed, outcome };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async assetCall(seedHex, index, target, argsHex, assetIssuer, amount, meterLimit, maxFeeQuon, expectedNonce) {
|
|
430
|
+
if (!core.valid_address(target)) throw new Error('the target is not a q1 address');
|
|
431
|
+
if (!core.valid_address(assetIssuer)) throw new Error('the asset issuer is not a q1 address');
|
|
432
|
+
checkAmount(amount);
|
|
433
|
+
const ceiling = feeCeiling(maxFeeQuon);
|
|
434
|
+
const info = await this.nodeInfo();
|
|
435
|
+
this._guardMainnet();
|
|
436
|
+
const chainId = this._signingChainId(info);
|
|
437
|
+
const reported = info && info.fee && info.fee.transfer_quon;
|
|
438
|
+
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
439
|
+
const fee = vmCallFee(gatewayFee(reported), meterLimit);
|
|
440
|
+
if (fee > ceiling) {
|
|
441
|
+
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
442
|
+
}
|
|
443
|
+
const from = core.address(seedHex, accountIndex(index));
|
|
444
|
+
const acct = await this.account(from);
|
|
445
|
+
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
446
|
+
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
447
|
+
const signed = JSON.parse(
|
|
448
|
+
core.signAssetCall(seedHex, accountIndex(index), target, argsHex, assetIssuer, String(amount), nonce, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
|
|
449
|
+
);
|
|
450
|
+
const outcome = await this.submit(signed.tx_hex);
|
|
451
|
+
this._remember(from, nonce, outcome);
|
|
452
|
+
return { signed, outcome };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async payableCall(seedHex, index, target, argsHex, value, meterLimit, maxFeeQuon, expectedNonce) {
|
|
456
|
+
if (!core.valid_address(target)) throw new Error('the target is not a q1 address');
|
|
457
|
+
checkAmount(value);
|
|
458
|
+
const ceiling = feeCeiling(maxFeeQuon);
|
|
459
|
+
const info = await this.nodeInfo();
|
|
460
|
+
this._guardMainnet();
|
|
461
|
+
const chainId = this._signingChainId(info);
|
|
462
|
+
const reported = info && info.fee && info.fee.transfer_quon;
|
|
463
|
+
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
464
|
+
const fee = vmCallFee(gatewayFee(reported), meterLimit);
|
|
465
|
+
if (fee > ceiling) {
|
|
466
|
+
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
467
|
+
}
|
|
468
|
+
const from = core.address(seedHex, accountIndex(index));
|
|
469
|
+
const acct = await this.account(from);
|
|
470
|
+
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
471
|
+
const nonce = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
472
|
+
const signed = JSON.parse(
|
|
473
|
+
core.signPayableCall(seedHex, accountIndex(index), target, argsHex, nonce, meterLimitOf(meterLimit), String(fee), String(value), chainId, this._validity(info))
|
|
474
|
+
);
|
|
475
|
+
const outcome = await this.submit(signed.tx_hex);
|
|
476
|
+
this._remember(from, nonce, outcome);
|
|
477
|
+
return { signed, outcome };
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async _slotValue(contract, key) {
|
|
481
|
+
const resp = await this._call('get_storage_at', JSON.stringify({ address: contract, keys: [key] }));
|
|
482
|
+
return BigInt(core.storageValue(JSON.stringify(resp), key));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async contractNonce(contract, signerHex) {
|
|
486
|
+
return this._slotValue(contract, core.nonceSlotKey(signerHex));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async contractScalar(contract, slot) {
|
|
490
|
+
return this._slotValue(contract, core.scalarSlotKey(BigInt(slot)));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async callSignedOrder(callerSeedHex, callerIndex, contract, selectorHex, orderSpec, ownerSeedHex, ownerIndex, meterLimit, maxFeeQuon, expectedOrderNonce, expectedNonce) {
|
|
494
|
+
if (!core.valid_address(contract)) throw new Error('the contract is not a q1 address');
|
|
495
|
+
const ceiling = feeCeiling(maxFeeQuon);
|
|
496
|
+
const info = await this.nodeInfo();
|
|
497
|
+
this._guardMainnet();
|
|
498
|
+
const chainId = this._signingChainId(info);
|
|
499
|
+
const reported = info && info.fee && info.fee.transfer_quon;
|
|
500
|
+
if (reported == null) throw new Error('the gateway did not report a transfer fee');
|
|
501
|
+
const fee = vmCallFee(gatewayFee(reported), meterLimit);
|
|
502
|
+
if (fee > ceiling) {
|
|
503
|
+
throw new Error(`the fee ${fee} is above the maximum you allowed ${maxFeeQuon}, refusing to sign`);
|
|
504
|
+
}
|
|
505
|
+
const signer = core.orderSigner(ownerSeedHex, accountIndex(ownerIndex));
|
|
506
|
+
const orderKey = contract + '/' + signer;
|
|
507
|
+
const reportedOrder = await this.contractNonce(contract, signer);
|
|
508
|
+
if (expectedOrderNonce != null && BigInt(expectedOrderNonce) !== reportedOrder) {
|
|
509
|
+
throw new Error(
|
|
510
|
+
`the gateway reported order nonce ${reportedOrder} but you expected ${expectedOrderNonce}, refusing to sign`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
const nonce = this._checkedNonce(reportedOrder, expectedOrderNonce, orderKey);
|
|
514
|
+
const order = JSON.parse(core.buildTypedOrderCall(
|
|
515
|
+
chainId,
|
|
516
|
+
contract,
|
|
517
|
+
selectorHex,
|
|
518
|
+
BigInt(orderSpec.schemeOff),
|
|
519
|
+
BigInt(orderSpec.ptrOff),
|
|
520
|
+
BigInt(orderSpec.regionOff || 0),
|
|
521
|
+
JSON.stringify(orderSpec.fields || [], (k, v) => {
|
|
522
|
+
if (typeof v !== 'bigint') return v;
|
|
523
|
+
if (k === 'value') return v.toString();
|
|
524
|
+
if (v > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`the order field ${k} ${v} is too large to carry`);
|
|
525
|
+
return Number(v);
|
|
526
|
+
}),
|
|
527
|
+
ownerSeedHex,
|
|
528
|
+
accountIndex(ownerIndex),
|
|
529
|
+
nonce,
|
|
530
|
+
));
|
|
531
|
+
const from = core.address(callerSeedHex, accountIndex(callerIndex));
|
|
532
|
+
const acct = await this.account(from);
|
|
533
|
+
if (!acct || acct.nonce == null) throw new Error('the gateway did not report a nonce');
|
|
534
|
+
const accountNonceUsed = this._checkedNonce(acct.nonce, expectedNonce, from);
|
|
535
|
+
const signed = JSON.parse(
|
|
536
|
+
core.sign_call(callerSeedHex, accountIndex(callerIndex), contract, order.call_args, accountNonceUsed, meterLimitOf(meterLimit), String(fee), chainId, this._validity(info))
|
|
537
|
+
);
|
|
538
|
+
const outcome = await this.submit(signed.tx_hex);
|
|
539
|
+
this._remember(from, accountNonceUsed, outcome);
|
|
540
|
+
this._remember(orderKey, nonce, outcome);
|
|
541
|
+
return { order, signed, outcome, orderNonce: nonce };
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export { makeClient, feeCeiling, checkAmount, generateSeed, readBounded, requireSafeTransport, validUntil, vmCallFee, VALIDITY_BLOCKS, Network };
|