@zerc20/wallet-sdk 0.3.0 → 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/README.md +1 -1
- package/dist/accountId.d.ts +5 -0
- package/dist/accountId.js +19 -0
- package/dist/accountId.js.map +1 -0
- package/dist/chain.d.ts +85 -9
- package/dist/chain.js +60 -33
- package/dist/chain.js.map +1 -1
- package/dist/context.d.ts +14 -6
- package/dist/context.js +17 -3
- package/dist/context.js.map +1 -1
- package/dist/core.d.ts +35 -5
- package/dist/core.js +95 -23
- package/dist/core.js.map +1 -1
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/pos.d.ts +7 -1
- package/dist/pos.js +213 -55
- package/dist/pos.js.map +1 -1
- package/dist/rageExit.d.ts +27 -4
- package/dist/rageExit.js +160 -49
- package/dist/rageExit.js.map +1 -1
- package/dist/seed.d.ts +4 -0
- package/dist/seed.js +13 -1
- package/dist/seed.js.map +1 -1
- package/dist/servers.d.ts +2 -0
- package/dist/servers.js +1 -0
- package/dist/servers.js.map +1 -1
- package/dist/services.d.ts +11 -4
- package/dist/services.js +26 -7
- package/dist/services.js.map +1 -1
- package/dist/spend.d.ts +30 -9
- package/dist/spend.js +46 -17
- package/dist/spend.js.map +1 -1
- package/dist/state.d.ts +21 -0
- package/dist/state.js.map +1 -1
- package/dist/sync.d.ts +10 -5
- package/dist/sync.js +53 -19
- package/dist/sync.js.map +1 -1
- package/dist/viewKey.d.ts +14 -0
- package/dist/viewKey.js +45 -0
- package/dist/viewKey.js.map +1 -0
- package/dist/wallet.d.ts +109 -16
- package/dist/wallet.js +291 -85
- package/dist/wallet.js.map +1 -1
- package/package.json +3 -2
- package/src/accountId.ts +22 -0
- package/src/chain.ts +95 -49
- package/src/context.ts +35 -8
- package/src/core.ts +109 -24
- package/src/errors.ts +2 -0
- package/src/index.ts +18 -2
- package/src/pos.ts +240 -61
- package/src/rageExit.ts +237 -61
- package/src/seed.ts +24 -1
- package/src/servers.ts +4 -1
- package/src/services.ts +28 -15
- package/src/spend.ts +58 -23
- package/src/state.ts +25 -1
- package/src/sync.ts +57 -20
- package/src/viewKey.ts +49 -0
- package/src/wallet.ts +387 -106
- package/wasm/zerc20_wallet_core.d.ts +8 -1
- package/wasm/zerc20_wallet_core.js +31 -1
- package/wasm/zerc20_wallet_core_bg.wasm +0 -0
- package/wasm/zerc20_wallet_core_bg.wasm.d.ts +1 -0
package/src/core.ts
CHANGED
|
@@ -28,21 +28,22 @@ export interface Burn {
|
|
|
28
28
|
|
|
29
29
|
/** The `index`-th burn address of `publicKey` on a chain and token, and its salt, from `burnKey`: the wallet's own burn
|
|
30
30
|
* key, or a point of sale's (`wallet_keys::derive_burn_address`). About 2^16 hashes, spread over the device's cores
|
|
31
|
-
* (Web Workers in a browser, worker threads in Node) so that the app stays responsive; `signal` stops the search.
|
|
31
|
+
* (Web Workers in a browser, worker threads in Node) so that the app stays responsive; `signal` stops the search. One
|
|
32
|
+
* that nobody waits for, made ahead, is `urgent: false`: it gives way to those someone waits for. */
|
|
32
33
|
export const burnAddress = (
|
|
33
34
|
burnKey: Uint8Array,
|
|
34
35
|
chainId: bigint,
|
|
35
36
|
tokenId: number,
|
|
36
37
|
index: number,
|
|
37
38
|
publicKey: Hex,
|
|
38
|
-
signal?: AbortSignal,
|
|
39
|
-
): Promise<Burn> => search({ burnKey, chainId, tokenId, index, publicKey }, signal);
|
|
39
|
+
opts: { signal?: AbortSignal; urgent?: boolean } = {},
|
|
40
|
+
): Promise<Burn> => search({ burnKey, chainId, tokenId, index, publicKey }, opts.urgent ?? true, opts.signal);
|
|
40
41
|
|
|
41
42
|
/** A burn address of someone else's `publicKey` (one they published to be paid), with a random salt that has the proof
|
|
42
43
|
* of work: about 2^16 hashes too, searched the same way. Only who holds the salt finds it: the payer, and the owner once
|
|
43
44
|
* told (`planSend` with `burn`). */
|
|
44
45
|
export const burnAddressFor = (chainId: bigint, publicKey: Hex, signal?: AbortSignal): Promise<Burn> =>
|
|
45
|
-
search({ chainId, publicKey }, signal);
|
|
46
|
+
search({ chainId, publicKey }, true, signal);
|
|
46
47
|
|
|
47
48
|
export type BurnSearch =
|
|
48
49
|
| { burnKey: Uint8Array; chainId: bigint; tokenId: number; index: number; publicKey: Hex }
|
|
@@ -60,16 +61,27 @@ export interface BurnBlock {
|
|
|
60
61
|
|
|
61
62
|
export type BurnAnswer = { burn: Burn | null } | { error: string };
|
|
62
63
|
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
/** What a block asks of the protocol code: `burnBlock`'s arguments, or `burnBlockFor`'s. */
|
|
65
|
+
export type BurnCall =
|
|
66
|
+
| { burnKey: Uint8Array; chainId: bigint; tokenId: number; index: number; publicKey: Hex; from: number; to: number }
|
|
67
|
+
| { chainId: bigint; publicKey: Hex; start: Hex; count: number };
|
|
68
|
+
|
|
69
|
+
/** A block's call. Its salts count up from a start: one the burn key derives, or for someone else's key a random one
|
|
70
|
+
* below 2^253, so that every salt is a field element. */
|
|
71
|
+
export function blockCall({ search: s, from, to }: BurnBlock): BurnCall {
|
|
72
|
+
if ('burnKey' in s) return { ...s, from, to };
|
|
73
|
+
const start = crypto.getRandomValues(new Uint8Array(32));
|
|
74
|
+
start[0] = start[0] & 0x1f;
|
|
75
|
+
return { chainId: s.chainId, publicKey: s.publicKey, start: bytesToHex(start), count: to - from + 1 };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The address a block holds, if any. */
|
|
79
|
+
export function runBlock(b: BurnBlock): Burn | null {
|
|
80
|
+
const c = blockCall(b);
|
|
81
|
+
const burn: unknown =
|
|
82
|
+
'burnKey' in c
|
|
83
|
+
? core.burnBlock(c.burnKey, c.chainId, c.tokenId, c.index, c.publicKey, c.from, c.to)
|
|
84
|
+
: core.burnBlockFor(c.chainId, c.publicKey, c.start, c.count);
|
|
73
85
|
return (burn as Burn | undefined) ?? null;
|
|
74
86
|
}
|
|
75
87
|
|
|
@@ -155,8 +167,17 @@ type NodeWorker = new (url: URL) => {
|
|
|
155
167
|
terminate(): Promise<number>;
|
|
156
168
|
};
|
|
157
169
|
|
|
158
|
-
/** Searches in progress
|
|
159
|
-
|
|
170
|
+
/** Searches in progress by owner (the burn key, or the key paid): those someone waits for, and those made ahead. Each
|
|
171
|
+
* owner in turn gets a block of its oldest search that has one, so that one owner's many searches hold up no other's,
|
|
172
|
+
* and each address it waits for comes as soon as its cores allow. */
|
|
173
|
+
// ponytail: fair per owner, so per point of sale; a merchant opening many accounts gets many turns, so cap each
|
|
174
|
+
// merchant's sales if that happens
|
|
175
|
+
const queues = { urgent: new Map<string, Search[]>(), later: new Map<string, Search[]>() };
|
|
176
|
+
/** One block in this many goes to the searches made ahead while some are waited for: a flood of urgent searches slows
|
|
177
|
+
* the making ahead, which keeps everyone else's sales from waiting, but does not stop it. */
|
|
178
|
+
const LATER_EVERY = 4;
|
|
179
|
+
/** Urgent blocks handed out in a row. */
|
|
180
|
+
let urgentRun = 0;
|
|
160
181
|
let threads: Thread[] | null = null;
|
|
161
182
|
let starting = false;
|
|
162
183
|
/** Workers do not run here (a page whose CSP forbids them, a bundler that left burnWorker.js out, tests run from
|
|
@@ -167,27 +188,51 @@ let idle: ReturnType<typeof setTimeout> | undefined;
|
|
|
167
188
|
/** An idle pool stops after this long, freeing a wasm instance per core. */
|
|
168
189
|
const IDLE_MS = 30_000;
|
|
169
190
|
|
|
170
|
-
async function search(s: BurnSearch, signal?: AbortSignal): Promise<Burn> {
|
|
191
|
+
async function search(s: BurnSearch, urgent: boolean, signal?: AbortSignal): Promise<Burn> {
|
|
171
192
|
await initCore();
|
|
172
193
|
signal?.throwIfAborted();
|
|
194
|
+
const queue = urgent ? queues.urgent : queues.later;
|
|
195
|
+
const owner = 'burnKey' in s ? bytesToHex(s.burnKey) : s.publicKey;
|
|
173
196
|
return new Promise((resolve, reject) => {
|
|
174
197
|
const abort = () => one.fail(signal!.reason);
|
|
175
198
|
const one = new Search(s, (r) => {
|
|
176
199
|
signal?.removeEventListener('abort', abort);
|
|
177
|
-
|
|
200
|
+
const mine = queue.get(owner)!;
|
|
201
|
+
mine.splice(mine.indexOf(one), 1);
|
|
202
|
+
if (!mine.length) queue.delete(owner);
|
|
178
203
|
if ('burn' in r) resolve(r.burn);
|
|
179
204
|
else reject(r.error);
|
|
180
205
|
});
|
|
181
206
|
signal?.addEventListener('abort', abort, { once: true });
|
|
182
|
-
|
|
207
|
+
const mine = queue.get(owner);
|
|
208
|
+
if (mine) mine.push(one);
|
|
209
|
+
else queue.set(owner, [one]);
|
|
183
210
|
dispatch();
|
|
184
211
|
});
|
|
185
212
|
}
|
|
186
213
|
|
|
187
214
|
function nextJob(): Job | null {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
215
|
+
const order = urgentRun >= LATER_EVERY - 1 ? [queues.later, queues.urgent] : [queues.urgent, queues.later];
|
|
216
|
+
for (const queue of order) {
|
|
217
|
+
const job = takeFrom(queue);
|
|
218
|
+
if (!job) continue;
|
|
219
|
+
urgentRun = queue === queues.urgent ? urgentRun + 1 : 0;
|
|
220
|
+
return job;
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** A block of the first owner in line that has one, from its oldest search that does; that owner then goes to the back
|
|
226
|
+
* of the line. */
|
|
227
|
+
function takeFrom(queue: Map<string, Search[]>): Job | null {
|
|
228
|
+
for (const [owner, mine] of queue) {
|
|
229
|
+
for (const search of mine) {
|
|
230
|
+
const block = search.take();
|
|
231
|
+
if (block === undefined) continue;
|
|
232
|
+
queue.delete(owner);
|
|
233
|
+
queue.set(owner, mine);
|
|
234
|
+
return { search, block };
|
|
235
|
+
}
|
|
191
236
|
}
|
|
192
237
|
return null;
|
|
193
238
|
}
|
|
@@ -203,9 +248,49 @@ function deliver({ search, block }: Job, a: BurnAnswer) {
|
|
|
203
248
|
else search.finish(block, a.burn);
|
|
204
249
|
}
|
|
205
250
|
|
|
206
|
-
/**
|
|
251
|
+
/** Runs blocks somewhere other than this module's wasm: a server's native process, say. */
|
|
252
|
+
export interface BurnRunner {
|
|
253
|
+
/** How many blocks it runs at once. */
|
|
254
|
+
slots: number;
|
|
255
|
+
run(call: BurnCall): Promise<Burn | null>;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let runner: BurnRunner | null = null;
|
|
259
|
+
/** Blocks the runner has and has not answered. */
|
|
260
|
+
const onRunner = new Set<Job>();
|
|
261
|
+
|
|
262
|
+
/** Runs every block from now on on `r`, or with null in this module's wasm again. The blocks the runner had are handed
|
|
263
|
+
* out again, and what it answers for them later is dropped. */
|
|
264
|
+
export function runBurnsOn(r: BurnRunner | null): void {
|
|
265
|
+
for (const job of onRunner) job.search.drop(job.block);
|
|
266
|
+
onRunner.clear();
|
|
267
|
+
runner = r;
|
|
268
|
+
dispatch();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function feed(r: BurnRunner) {
|
|
272
|
+
while (onRunner.size < r.slots) {
|
|
273
|
+
const job = nextJob();
|
|
274
|
+
if (!job) return;
|
|
275
|
+
onRunner.add(job);
|
|
276
|
+
void r
|
|
277
|
+
.run(blockCall(blockOf(job)))
|
|
278
|
+
.then(
|
|
279
|
+
(burn): BurnAnswer => ({ burn }),
|
|
280
|
+
(e: unknown): BurnAnswer => ({ error: e instanceof Error ? e.message : String(e) }),
|
|
281
|
+
)
|
|
282
|
+
.then((a) => {
|
|
283
|
+
if (!onRunner.delete(job)) return; // handed out again when the runner was taken away
|
|
284
|
+
deliver(job, a);
|
|
285
|
+
dispatch();
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Hands blocks to the runner, or to idle threads, starting the pool if it is not running. */
|
|
207
291
|
function dispatch() {
|
|
208
292
|
clearTimeout(idle);
|
|
293
|
+
if (runner) return feed(runner);
|
|
209
294
|
if (here) return void runHere();
|
|
210
295
|
if (!threads) return void start();
|
|
211
296
|
for (const t of threads) {
|
|
@@ -216,7 +301,7 @@ function dispatch() {
|
|
|
216
301
|
t.busy(true);
|
|
217
302
|
t.send(blockOf(job));
|
|
218
303
|
}
|
|
219
|
-
if (!
|
|
304
|
+
if (!queues.urgent.size && !queues.later.size && threads.every((t) => !t.job)) {
|
|
220
305
|
idle = setTimeout(stop, IDLE_MS);
|
|
221
306
|
(idle as { unref?: () => void }).unref?.(); // a process need not stay up for it
|
|
222
307
|
}
|
package/src/errors.ts
CHANGED
|
@@ -23,6 +23,8 @@ export type ErrorCode =
|
|
|
23
23
|
| 'user-rejected'
|
|
24
24
|
/** The wallet is open in another tab or window: close it there (`close()`), then open it here. */
|
|
25
25
|
| 'wallet-in-use'
|
|
26
|
+
/** This wallet watches its account by its view key (`Wallet.watch`): it spends and writes nothing. */
|
|
27
|
+
| 'read-only'
|
|
26
28
|
/** This wallet object was closed (`close()`): open the wallet again to go on. */
|
|
27
29
|
| 'wallet-closed'
|
|
28
30
|
/** A server answered something that does not match the chain. */
|
package/src/index.ts
CHANGED
|
@@ -5,13 +5,25 @@
|
|
|
5
5
|
// wallet.balances(); // spendable, and what is pending and why
|
|
6
6
|
// await (await wallet.deposit({ token, amount })).wait();
|
|
7
7
|
// const q = await wallet.fees({ kind: 'send', token, amount });
|
|
8
|
-
// await (await wallet.send({ to: '
|
|
8
|
+
// await (await wallet.send({ to: 'zerc20id1…', token, amount, maxFee: q.fee })).wait();
|
|
9
|
+
// const watching = await Wallet.watch({ network, viewKey: wallet.viewKey(), persist }); // elsewhere, spends nothing
|
|
9
10
|
//
|
|
10
11
|
// Nothing secret leaves the device unencrypted: JoinSplit proofs are made here, a merge's by the merge prover, and the
|
|
11
12
|
// attester and the prover get the witness sealed to an enclave whose measurement the wallet checks. Servers are only
|
|
12
13
|
// ever asked for what every wallet downloads.
|
|
13
14
|
|
|
14
|
-
export {
|
|
15
|
+
export { accountId, idAccount } from './accountId.js';
|
|
16
|
+
export {
|
|
17
|
+
Chain,
|
|
18
|
+
type Referrer,
|
|
19
|
+
type Refund,
|
|
20
|
+
type Token,
|
|
21
|
+
MAX_REFERRAL_BPS,
|
|
22
|
+
NATIVE,
|
|
23
|
+
UNWRAP_INTENT,
|
|
24
|
+
rpcTransport,
|
|
25
|
+
} from './chain.js';
|
|
26
|
+
export { type Burn, type BurnCall, type BurnRunner, runBurnsOn } from './core.js';
|
|
15
27
|
export { type ErrorCode, WalletError } from './errors.js';
|
|
16
28
|
export {
|
|
17
29
|
type AttesterTrust,
|
|
@@ -24,18 +36,22 @@ export {
|
|
|
24
36
|
} from './network.js';
|
|
25
37
|
export { type Payment, PointOfSale, type PosToken, type Sale, type SaleStatus } from './pos.js';
|
|
26
38
|
export { accountKey, seedFromSignature } from './seed.js';
|
|
39
|
+
export { viewKeyAccount } from './viewKey.js';
|
|
27
40
|
export {
|
|
28
41
|
type Activity,
|
|
29
42
|
type Balance,
|
|
30
43
|
type CoinInfo,
|
|
31
44
|
type CoinState,
|
|
45
|
+
type DepositAddressInfo,
|
|
32
46
|
type DepositInfo,
|
|
33
47
|
type DepositStep,
|
|
34
48
|
type FeeQuote,
|
|
49
|
+
type MaxAmount,
|
|
35
50
|
type OpenOptions,
|
|
36
51
|
type Operation,
|
|
37
52
|
type OperationKind,
|
|
38
53
|
type OperationStatus,
|
|
39
54
|
type PendingReason,
|
|
40
55
|
Wallet,
|
|
56
|
+
type WatchOptions,
|
|
41
57
|
} from './wallet.js';
|