@unlink-xyz/core 0.1.3-canary.fd5dddf → 0.1.4
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 +9 -0
- package/dist/account/account.d.ts +31 -2
- package/dist/account/account.d.ts.map +1 -1
- package/dist/account/accounts.d.ts +42 -0
- package/dist/account/accounts.d.ts.map +1 -0
- package/dist/account/seed.d.ts +45 -0
- package/dist/account/seed.d.ts.map +1 -0
- package/dist/account/serialization.d.ts +6 -0
- package/dist/account/serialization.d.ts.map +1 -0
- package/dist/browser/index.js +34424 -86406
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/wallet/index.js +55942 -0
- package/dist/browser/wallet/index.js.map +1 -0
- package/dist/clients/broadcaster.d.ts +1 -0
- package/dist/clients/broadcaster.d.ts.map +1 -1
- package/dist/clients/indexer.d.ts +5 -0
- package/dist/clients/indexer.d.ts.map +1 -1
- package/dist/config.d.ts +6 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/core.d.ts.map +1 -1
- package/dist/crypto/adapters/index.d.ts +17 -0
- package/dist/crypto/adapters/index.d.ts.map +1 -0
- package/dist/crypto/adapters/polyfills.d.ts +5 -0
- package/dist/crypto/adapters/polyfills.d.ts.map +1 -0
- package/dist/crypto/encrypt.d.ts +33 -0
- package/dist/crypto/encrypt.d.ts.map +1 -0
- package/dist/crypto/secure-memory.d.ts.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6721 -23
- package/dist/index.js.map +1 -0
- package/dist/keys/derive.d.ts +2 -2
- package/dist/keys/derive.d.ts.map +1 -1
- package/dist/keys/hex.d.ts +1 -4
- package/dist/keys/hex.d.ts.map +1 -1
- package/dist/keys/mnemonic.d.ts +0 -2
- package/dist/keys/mnemonic.d.ts.map +1 -1
- package/dist/keys.d.ts +1 -0
- package/dist/keys.d.ts.map +1 -1
- package/dist/prover/config.d.ts +54 -9
- package/dist/prover/config.d.ts.map +1 -1
- package/dist/prover/integrity.d.ts +20 -0
- package/dist/prover/integrity.d.ts.map +1 -0
- package/dist/prover/prover.d.ts +16 -31
- package/dist/prover/prover.d.ts.map +1 -1
- package/dist/state/merkle/hydrator.d.ts +21 -19
- package/dist/state/merkle/hydrator.d.ts.map +1 -1
- package/dist/state/merkle/index.d.ts +1 -1
- package/dist/state/merkle/index.d.ts.map +1 -1
- package/dist/state/store/ciphertext-store.d.ts +7 -0
- package/dist/state/store/ciphertext-store.d.ts.map +1 -1
- package/dist/state/store/index.d.ts +1 -1
- package/dist/state/store/index.d.ts.map +1 -1
- package/dist/state/store/job-store.d.ts.map +1 -1
- package/dist/state/store/jobs.d.ts +14 -16
- package/dist/state/store/jobs.d.ts.map +1 -1
- package/dist/state/store/leaf-store.d.ts +4 -0
- package/dist/state/store/leaf-store.d.ts.map +1 -1
- package/dist/state/store/nullifier-store.d.ts.map +1 -1
- package/dist/state/store/records.d.ts +8 -0
- package/dist/state/store/records.d.ts.map +1 -1
- package/dist/state/store/store.d.ts +18 -0
- package/dist/state/store/store.d.ts.map +1 -1
- package/dist/storage/indexeddb.d.ts.map +1 -1
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/transactions/adapter.d.ts +31 -0
- package/dist/transactions/adapter.d.ts.map +1 -0
- package/dist/transactions/deposit.d.ts +1 -1
- package/dist/transactions/deposit.d.ts.map +1 -1
- package/dist/transactions/index.d.ts +4 -2
- package/dist/transactions/index.d.ts.map +1 -1
- package/dist/transactions/note-sync.d.ts +3 -3
- package/dist/transactions/note-sync.d.ts.map +1 -1
- package/dist/transactions/reconcile.d.ts +1 -1
- package/dist/transactions/reconcile.d.ts.map +1 -1
- package/dist/transactions/transact.d.ts +21 -2
- package/dist/transactions/transact.d.ts.map +1 -1
- package/dist/transactions/transaction-planner.d.ts +1 -1
- package/dist/transactions/transaction-planner.d.ts.map +1 -1
- package/dist/transactions/transfer-planner.d.ts +2 -1
- package/dist/transactions/transfer-planner.d.ts.map +1 -1
- package/dist/transactions/types/deposit.d.ts +3 -3
- package/dist/transactions/types/deposit.d.ts.map +1 -1
- package/dist/transactions/types/domain.d.ts +3 -0
- package/dist/transactions/types/domain.d.ts.map +1 -1
- package/dist/transactions/types/options.d.ts +14 -5
- package/dist/transactions/types/options.d.ts.map +1 -1
- package/dist/transactions/types/planning.d.ts +2 -0
- package/dist/transactions/types/planning.d.ts.map +1 -1
- package/dist/transactions/types/state-stores.d.ts +53 -5
- package/dist/transactions/types/state-stores.d.ts.map +1 -1
- package/dist/transactions/types/transact.d.ts +10 -3
- package/dist/transactions/types/transact.d.ts.map +1 -1
- package/dist/transactions/withdrawal-planner.d.ts +1 -1
- package/dist/transactions/withdrawal-planner.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsup.config.d.ts +8 -0
- package/dist/tsup.config.d.ts.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/amounts.d.ts +0 -13
- package/dist/utils/amounts.d.ts.map +1 -1
- package/dist/utils/async.js +37 -34
- package/dist/utils/async.js.map +1 -0
- package/dist/utils/bigint.d.ts +0 -2
- package/dist/utils/bigint.d.ts.map +1 -1
- package/dist/utils/random.d.ts +5 -0
- package/dist/utils/random.d.ts.map +1 -1
- package/dist/utils/validators.d.ts.map +1 -1
- package/dist/vitest.config.d.ts.map +1 -1
- package/dist/wallet/adapter.d.ts +21 -0
- package/dist/wallet/adapter.d.ts.map +1 -0
- package/dist/wallet/burner/service.d.ts +32 -0
- package/dist/wallet/burner/service.d.ts.map +1 -0
- package/dist/wallet/burner/types.d.ts +47 -0
- package/dist/wallet/burner/types.d.ts.map +1 -0
- package/dist/wallet/index.d.ts +20 -0
- package/dist/wallet/index.d.ts.map +1 -0
- package/dist/wallet/index.js +6462 -0
- package/dist/wallet/index.js.map +1 -0
- package/dist/wallet/sdk.d.ts +48 -0
- package/dist/wallet/sdk.d.ts.map +1 -0
- package/dist/wallet/types.d.ts +457 -0
- package/dist/wallet/types.d.ts.map +1 -0
- package/dist/wallet/unlink-wallet.d.ts +187 -0
- package/dist/wallet/unlink-wallet.d.ts.map +1 -0
- package/package.json +16 -6
- package/dist/account/account.js +0 -142
- package/dist/circuits.json +0 -74
- package/dist/clients/broadcaster.js +0 -30
- package/dist/clients/http.js +0 -72
- package/dist/clients/indexer.js +0 -94
- package/dist/config.js +0 -36
- package/dist/constants.js +0 -5
- package/dist/core.js +0 -15
- package/dist/crypto-adapters/auto-init.d.ts +0 -2
- package/dist/crypto-adapters/auto-init.d.ts.map +0 -1
- package/dist/crypto-adapters/auto-init.js +0 -7
- package/dist/crypto-adapters/index.d.ts +0 -22
- package/dist/crypto-adapters/index.d.ts.map +0 -1
- package/dist/crypto-adapters/index.js +0 -47
- package/dist/crypto-adapters/polyfills.d.ts +0 -5
- package/dist/crypto-adapters/polyfills.d.ts.map +0 -1
- package/dist/crypto-adapters/polyfills.js +0 -8
- package/dist/errors.js +0 -36
- package/dist/history/index.js +0 -2
- package/dist/history/service.js +0 -354
- package/dist/history/types.js +0 -1
- package/dist/keys/address.js +0 -55
- package/dist/keys/derive.js +0 -112
- package/dist/keys/hex.js +0 -66
- package/dist/keys/index.js +0 -4
- package/dist/keys/mnemonic.js +0 -23
- package/dist/keys.js +0 -45
- package/dist/prover/config.js +0 -70
- package/dist/prover/index.js +0 -1
- package/dist/prover/prover.js +0 -291
- package/dist/prover/registry.js +0 -18
- package/dist/schema.js +0 -14
- package/dist/state/index.js +0 -2
- package/dist/state/merkle/hydrator.js +0 -37
- package/dist/state/merkle/index.js +0 -2
- package/dist/state/merkle/merkle-tree.js +0 -113
- package/dist/state/store/ciphertext-store.js +0 -37
- package/dist/state/store/history-store.js +0 -53
- package/dist/state/store/index.js +0 -9
- package/dist/state/store/job-store.js +0 -144
- package/dist/state/store/jobs.js +0 -1
- package/dist/state/store/leaf-store.js +0 -32
- package/dist/state/store/note-store.js +0 -146
- package/dist/state/store/nullifier-store.js +0 -60
- package/dist/state/store/records.js +0 -1
- package/dist/state/store/root-store.js +0 -26
- package/dist/state/store/store.js +0 -113
- package/dist/storage/index.js +0 -2
- package/dist/storage/indexeddb.js +0 -205
- package/dist/storage/memory.js +0 -91
- package/dist/transactions/deposit.js +0 -220
- package/dist/transactions/index.js +0 -9
- package/dist/transactions/note-selection.js +0 -201
- package/dist/transactions/note-sync.js +0 -485
- package/dist/transactions/reconcile.js +0 -85
- package/dist/transactions/transact.js +0 -450
- package/dist/transactions/transaction-planner.js +0 -116
- package/dist/transactions/transfer-planner.js +0 -85
- package/dist/transactions/types/deposit.js +0 -1
- package/dist/transactions/types/domain.js +0 -4
- package/dist/transactions/types/index.js +0 -17
- package/dist/transactions/types/options.js +0 -1
- package/dist/transactions/types/planning.js +0 -1
- package/dist/transactions/types/state-stores.js +0 -1
- package/dist/transactions/types/transact.js +0 -1
- package/dist/transactions/withdrawal-planner.js +0 -128
- package/dist/tsup.browser.config.js +0 -34
- package/dist/types.js +0 -1
- package/dist/utils/amounts.js +0 -89
- package/dist/utils/bigint.js +0 -29
- package/dist/utils/crypto.d.ts +0 -18
- package/dist/utils/crypto.d.ts.map +0 -1
- package/dist/utils/crypto.js +0 -45
- package/dist/utils/format.js +0 -33
- package/dist/utils/json-codec.js +0 -25
- package/dist/utils/notes.js +0 -14
- package/dist/utils/polling.js +0 -11
- package/dist/utils/random.js +0 -27
- package/dist/utils/secure-memory.d.ts.map +0 -1
- package/dist/utils/secure-memory.js +0 -28
- package/dist/utils/signature.js +0 -14
- package/dist/utils/validators.js +0 -96
- package/dist/vitest.config.js +0 -13
- /package/dist/{utils → crypto}/secure-memory.d.ts +0 -0
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { keys } from "../../keys.js";
|
|
2
|
-
import { encodeJson } from "../../utils/json-codec.js";
|
|
3
|
-
import { createCiphertextStore } from "./ciphertext-store.js";
|
|
4
|
-
import { createHistoryStore } from "./history-store.js";
|
|
5
|
-
import { createJobStore } from "./job-store.js";
|
|
6
|
-
import { createLeafStore } from "./leaf-store.js";
|
|
7
|
-
import { createNoteStore, emptyBytes } from "./note-store.js";
|
|
8
|
-
import { createNullifierStore } from "./nullifier-store.js";
|
|
9
|
-
import { createRootStore } from "./root-store.js";
|
|
10
|
-
export function createStateStore(storage) {
|
|
11
|
-
const notes = createNoteStore(storage);
|
|
12
|
-
const nullifiers = createNullifierStore(storage);
|
|
13
|
-
const leaves = createLeafStore(storage);
|
|
14
|
-
const roots = createRootStore(storage);
|
|
15
|
-
const ciphertexts = createCiphertextStore(storage);
|
|
16
|
-
const jobs = createJobStore(storage);
|
|
17
|
-
const history = createHistoryStore(storage);
|
|
18
|
-
/**
|
|
19
|
-
* Atomically store a commitment's leaf, ciphertext, root, and optionally note.
|
|
20
|
-
* Uses storage.batch() for all-or-nothing semantics.
|
|
21
|
-
*/
|
|
22
|
-
async function syncCommitment(params) {
|
|
23
|
-
const { leaf, ciphertext, root, note } = params;
|
|
24
|
-
const ops = [
|
|
25
|
-
// Leaf
|
|
26
|
-
{
|
|
27
|
-
put: [
|
|
28
|
-
keys.leaf(leaf.chainId, leaf.index),
|
|
29
|
-
encodeJson(leaf),
|
|
30
|
-
],
|
|
31
|
-
},
|
|
32
|
-
// Ciphertext (raw bytes, not JSON)
|
|
33
|
-
{
|
|
34
|
-
put: [
|
|
35
|
-
keys.ciphertext(ciphertext.chainId, ciphertext.index),
|
|
36
|
-
ciphertext.payload,
|
|
37
|
-
],
|
|
38
|
-
},
|
|
39
|
-
// Root
|
|
40
|
-
{
|
|
41
|
-
put: [
|
|
42
|
-
keys.root(root.chainId, root.root),
|
|
43
|
-
encodeJson(root),
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
];
|
|
47
|
-
// Note (if decryption succeeded)
|
|
48
|
-
if (note) {
|
|
49
|
-
ops.push({
|
|
50
|
-
put: [
|
|
51
|
-
keys.note(note.chainId, note.index),
|
|
52
|
-
encodeJson(note),
|
|
53
|
-
],
|
|
54
|
-
});
|
|
55
|
-
// Maintain unspent index
|
|
56
|
-
if (note.spentAt === undefined) {
|
|
57
|
-
ops.push({
|
|
58
|
-
put: [keys.unspent(note.chainId, note.mpk, note.index), emptyBytes()],
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
await storage.batch(ops);
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Clear notes, leaves, ciphertexts, and unspent indices for indices >= fromIndex.
|
|
66
|
-
* Used by partial resync to remove stale tail data while preserving the valid prefix.
|
|
67
|
-
*
|
|
68
|
-
* Key formats (see keys.ts): notes:${c}:${i}, leaves:${c}:${i},
|
|
69
|
-
* ciphertexts:${c}:${i}, idx:notes:unspent:${c}:${mpk}:${i}.
|
|
70
|
-
* The index is always the last colon-separated segment.
|
|
71
|
-
*/
|
|
72
|
-
async function clearChainDataFromIndex(chainId, fromIndex) {
|
|
73
|
-
const prefixes = [
|
|
74
|
-
`notes:${chainId}:`,
|
|
75
|
-
`leaves:${chainId}:`,
|
|
76
|
-
`ciphertexts:${chainId}:`,
|
|
77
|
-
];
|
|
78
|
-
const ops = [];
|
|
79
|
-
for (const prefix of prefixes) {
|
|
80
|
-
const entries = await storage.iter({ prefix });
|
|
81
|
-
for (const { key } of entries) {
|
|
82
|
-
// Index is always the last colon-segment (see keys.ts key builders)
|
|
83
|
-
const idx = parseInt(key.slice(key.lastIndexOf(":") + 1), 10);
|
|
84
|
-
if (!isNaN(idx) && idx >= fromIndex) {
|
|
85
|
-
ops.push({ del: key });
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// Clear unspent index entries (key: idx:notes:unspent:${c}:${mpk}:${index})
|
|
90
|
-
const unspentPrefix = `idx:notes:unspent:${chainId}:`;
|
|
91
|
-
const unspentEntries = await storage.iter({ prefix: unspentPrefix });
|
|
92
|
-
for (const { key } of unspentEntries) {
|
|
93
|
-
// Index is always the last colon-segment (see keys.ts key builders)
|
|
94
|
-
const idx = parseInt(key.slice(key.lastIndexOf(":") + 1), 10);
|
|
95
|
-
if (!isNaN(idx) && idx >= fromIndex) {
|
|
96
|
-
ops.push({ del: key });
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (ops.length > 0)
|
|
100
|
-
await storage.batch(ops);
|
|
101
|
-
}
|
|
102
|
-
return {
|
|
103
|
-
...notes,
|
|
104
|
-
...nullifiers,
|
|
105
|
-
...leaves,
|
|
106
|
-
...roots,
|
|
107
|
-
...ciphertexts,
|
|
108
|
-
...jobs,
|
|
109
|
-
...history,
|
|
110
|
-
syncCommitment,
|
|
111
|
-
clearChainDataFromIndex,
|
|
112
|
-
};
|
|
113
|
-
}
|
package/dist/storage/index.js
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IndexedDB-backed key/value storage for Unlink Core.
|
|
3
|
-
*
|
|
4
|
-
* What this is
|
|
5
|
-
* - A durable, transactional KV store implemented on top of the browser's IndexedDB.
|
|
6
|
-
* - Two object stores: "kv" for app data, "meta" for schema/versioning.
|
|
7
|
-
*
|
|
8
|
-
* Why we implement it
|
|
9
|
-
* - We need persistent client-side state (notes, leaves, nullifiers, cfg, etc.) that survives reloads
|
|
10
|
-
* and works in both web apps and browser extensions without introducing a new backend.
|
|
11
|
-
*
|
|
12
|
-
* When to use
|
|
13
|
-
* - Browser UIs and extensions (MV2/MV3) where persistence is required.
|
|
14
|
-
* - Any environment with a real IndexedDB (tests use `fake-indexeddb`).
|
|
15
|
-
*
|
|
16
|
-
* Trade-offs
|
|
17
|
-
* - 👍 Durable, quota-managed, non-blocking, transactional writes (atomic batch).
|
|
18
|
-
* - 👍 Works offline; no server dependency.
|
|
19
|
-
* - ⚠️ Not natively available in Node/SSR; needs a polyfill for tests only.
|
|
20
|
-
*
|
|
21
|
-
*/
|
|
22
|
-
import { CoreError } from "../errors.js";
|
|
23
|
-
import { validateKey } from "../keys.js";
|
|
24
|
-
const DEFAULT_DB_NAME = "unlink-core";
|
|
25
|
-
const DB_VERSION = 1;
|
|
26
|
-
const KV_STORE = "kv";
|
|
27
|
-
const META_STORE = "meta";
|
|
28
|
-
const SCHEMA_KEY = "schema_version";
|
|
29
|
-
const copy = (value) => new Uint8Array(value);
|
|
30
|
-
function assertIndexedDb() {
|
|
31
|
-
if (typeof indexedDB === "undefined") {
|
|
32
|
-
throw new CoreError("indexedDB is not available in this environment");
|
|
33
|
-
}
|
|
34
|
-
return indexedDB;
|
|
35
|
-
}
|
|
36
|
-
// Translate IndexedDB's event-based transaction API into a promise that resolves
|
|
37
|
-
// when the transaction completes or rejects on failure/abort.
|
|
38
|
-
function txDone(tx) {
|
|
39
|
-
return new Promise((resolve, reject) => {
|
|
40
|
-
tx.oncomplete = () => resolve();
|
|
41
|
-
tx.onerror = () => reject(tx.error ?? new CoreError("indexedDB transaction failed"));
|
|
42
|
-
tx.onabort = () => reject(tx.error ?? new CoreError("indexedDB transaction aborted"));
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
function wrapRequest(request) {
|
|
46
|
-
return new Promise((resolve, reject) => {
|
|
47
|
-
request.onsuccess = () => resolve(request.result);
|
|
48
|
-
request.onerror = () => reject(request.error ?? new CoreError("indexedDB request failed"));
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
// Coerce whatever IndexedDB gives us back into our Bytes type.
|
|
52
|
-
function asBytes(raw) {
|
|
53
|
-
if (raw instanceof Uint8Array)
|
|
54
|
-
return copy(raw);
|
|
55
|
-
if (raw instanceof ArrayBuffer)
|
|
56
|
-
return copy(new Uint8Array(raw));
|
|
57
|
-
if (ArrayBuffer.isView(raw)) {
|
|
58
|
-
const view = raw;
|
|
59
|
-
const buf = view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
|
60
|
-
return copy(new Uint8Array(buf));
|
|
61
|
-
}
|
|
62
|
-
throw new CoreError("indexedDB value is not binary data");
|
|
63
|
-
}
|
|
64
|
-
// IndexedDB-backed implementation implementing the storage API.
|
|
65
|
-
export function createIndexedDbStorage(opts = {}) {
|
|
66
|
-
const name = opts.name ?? DEFAULT_DB_NAME;
|
|
67
|
-
let db = null;
|
|
68
|
-
// Lazy-open the database so we only touch IndexedDB when needed.
|
|
69
|
-
async function ensureOpen() {
|
|
70
|
-
if (db)
|
|
71
|
-
return db;
|
|
72
|
-
// Guard for environments without IndexedDB (unit tests or SSR).
|
|
73
|
-
const idb = assertIndexedDb();
|
|
74
|
-
const request = idb.open(name, DB_VERSION);
|
|
75
|
-
// Versioned schema: create one store for kv entries and one for metadata.
|
|
76
|
-
request.onupgradeneeded = () => {
|
|
77
|
-
const upgradeDb = request.result;
|
|
78
|
-
if (!upgradeDb.objectStoreNames.contains(KV_STORE)) {
|
|
79
|
-
upgradeDb.createObjectStore(KV_STORE);
|
|
80
|
-
}
|
|
81
|
-
if (!upgradeDb.objectStoreNames.contains(META_STORE)) {
|
|
82
|
-
upgradeDb.createObjectStore(META_STORE);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
db = await new Promise((resolve, reject) => {
|
|
86
|
-
request.onsuccess = () => resolve(request.result);
|
|
87
|
-
request.onerror = () => reject(request.error ?? new CoreError("indexedDB open failed"));
|
|
88
|
-
request.onblocked = () => reject(new CoreError("indexedDB open request was blocked"));
|
|
89
|
-
});
|
|
90
|
-
return db;
|
|
91
|
-
}
|
|
92
|
-
// Start a transaction and give callers both the store and a completion promise.
|
|
93
|
-
const getStore = async (storeName, mode) => {
|
|
94
|
-
const database = await ensureOpen();
|
|
95
|
-
const tx = database.transaction(storeName, mode);
|
|
96
|
-
const done = txDone(tx);
|
|
97
|
-
return { store: tx.objectStore(storeName), done };
|
|
98
|
-
};
|
|
99
|
-
return {
|
|
100
|
-
async open() {
|
|
101
|
-
await ensureOpen();
|
|
102
|
-
},
|
|
103
|
-
async get(key) {
|
|
104
|
-
validateKey(key);
|
|
105
|
-
const { store, done } = await getStore(KV_STORE, "readonly");
|
|
106
|
-
const result = await wrapRequest(store.get(key));
|
|
107
|
-
await done;
|
|
108
|
-
if (result === undefined)
|
|
109
|
-
return null;
|
|
110
|
-
return asBytes(result);
|
|
111
|
-
},
|
|
112
|
-
async put(key, value) {
|
|
113
|
-
validateKey(key);
|
|
114
|
-
const { store, done } = await getStore(KV_STORE, "readwrite");
|
|
115
|
-
await wrapRequest(store.put(copy(value), key)); // copy avoids shared mutation; revisit if this becomes costly
|
|
116
|
-
await done;
|
|
117
|
-
},
|
|
118
|
-
async delete(key) {
|
|
119
|
-
validateKey(key);
|
|
120
|
-
const { store, done } = await getStore(KV_STORE, "readwrite");
|
|
121
|
-
await wrapRequest(store.delete(key));
|
|
122
|
-
await done;
|
|
123
|
-
},
|
|
124
|
-
async batch(ops) {
|
|
125
|
-
// Execute the batch within a single transaction to preserve atomicity.
|
|
126
|
-
const database = await ensureOpen();
|
|
127
|
-
const tx = database.transaction(KV_STORE, "readwrite");
|
|
128
|
-
const store = tx.objectStore(KV_STORE);
|
|
129
|
-
const done = txDone(tx);
|
|
130
|
-
try {
|
|
131
|
-
// Replay the batch within a single transaction; validation failure aborts everything.
|
|
132
|
-
for (const op of ops) {
|
|
133
|
-
if (op.put) {
|
|
134
|
-
const [key, value] = op.put;
|
|
135
|
-
validateKey(key);
|
|
136
|
-
store.put(copy(value), key); // copy avoids shared mutation; revisit if this becomes costly
|
|
137
|
-
}
|
|
138
|
-
if (op.del) {
|
|
139
|
-
validateKey(op.del);
|
|
140
|
-
store.delete(op.del);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
await done;
|
|
144
|
-
}
|
|
145
|
-
catch (err) {
|
|
146
|
-
try {
|
|
147
|
-
tx.abort();
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
/* noop */
|
|
151
|
-
}
|
|
152
|
-
await done.catch(() => { });
|
|
153
|
-
throw err;
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
async iter(options = {}) {
|
|
157
|
-
if (options.start && options.end && options.start > options.end) {
|
|
158
|
-
throw new CoreError("iter start bound must not exceed end bound");
|
|
159
|
-
}
|
|
160
|
-
// Pull everything into memory for now. TODO: stream w/ cursor when needed for perf
|
|
161
|
-
const database = await ensureOpen();
|
|
162
|
-
const tx = database.transaction(KV_STORE, "readonly");
|
|
163
|
-
const store = tx.objectStore(KV_STORE);
|
|
164
|
-
const done = txDone(tx);
|
|
165
|
-
const [keys, values] = await Promise.all([
|
|
166
|
-
wrapRequest(store.getAllKeys()),
|
|
167
|
-
wrapRequest(store.getAll()),
|
|
168
|
-
]);
|
|
169
|
-
await done;
|
|
170
|
-
const pairs = keys.map((key, idx) => {
|
|
171
|
-
if (typeof key !== "string") {
|
|
172
|
-
throw new CoreError("encountered non-string key in indexedDB");
|
|
173
|
-
}
|
|
174
|
-
const raw = values[idx];
|
|
175
|
-
return { key, value: asBytes(raw) };
|
|
176
|
-
});
|
|
177
|
-
let filtered = pairs.filter(({ key }) => {
|
|
178
|
-
if (options.prefix && !key.startsWith(options.prefix))
|
|
179
|
-
return false;
|
|
180
|
-
if (options.start && key < options.start)
|
|
181
|
-
return false;
|
|
182
|
-
if (options.end && key > options.end)
|
|
183
|
-
return false;
|
|
184
|
-
return true;
|
|
185
|
-
});
|
|
186
|
-
filtered.sort((a, b) => a.key.localeCompare(b.key));
|
|
187
|
-
if (options.reverse)
|
|
188
|
-
filtered = filtered.reverse();
|
|
189
|
-
if (options.limit)
|
|
190
|
-
filtered = filtered.slice(0, options.limit);
|
|
191
|
-
return filtered;
|
|
192
|
-
},
|
|
193
|
-
async getSchemaVersion() {
|
|
194
|
-
const { store, done } = await getStore(META_STORE, "readonly");
|
|
195
|
-
const value = await wrapRequest(store.get(SCHEMA_KEY));
|
|
196
|
-
await done;
|
|
197
|
-
return value ?? 0;
|
|
198
|
-
},
|
|
199
|
-
async setSchemaVersion(version) {
|
|
200
|
-
const { store, done } = await getStore(META_STORE, "readwrite");
|
|
201
|
-
await wrapRequest(store.put(version, SCHEMA_KEY));
|
|
202
|
-
await done;
|
|
203
|
-
},
|
|
204
|
-
};
|
|
205
|
-
}
|
package/dist/storage/memory.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-memory key/value storage for Unlink Core.
|
|
3
|
-
*
|
|
4
|
-
* What this is
|
|
5
|
-
* - A minimal, process-local KV store backed by a `Map<string, Uint8Array>`.
|
|
6
|
-
*
|
|
7
|
-
* Why we implement it
|
|
8
|
-
* - Fast, dependency-free storage for unit tests, demos, and ephemeral runs.
|
|
9
|
-
* - Useful as a reference driver for the Storage interface.
|
|
10
|
-
*
|
|
11
|
-
* When to use
|
|
12
|
-
* - Unit tests (deterministic, zero I/O).
|
|
13
|
-
* - CLI/Node scripts that do not need persistence beyond process lifetime.
|
|
14
|
-
* - Prototyping or sandboxing Core logic.
|
|
15
|
-
*
|
|
16
|
-
* Trade-offs
|
|
17
|
-
* - 👍 Extremely fast; zero external APIs; simple to reason about.
|
|
18
|
-
* - 👍 Great for isolation in tests; easy to snapshot/clone.
|
|
19
|
-
* - ⚠️ No durability (data is lost on process exit).
|
|
20
|
-
* - ⚠️ Per-process only; no cross-tab or reload survival.
|
|
21
|
-
*
|
|
22
|
-
*/
|
|
23
|
-
import { CoreError } from "../errors.js";
|
|
24
|
-
import { validateKey } from "../keys.js";
|
|
25
|
-
const copy = (value) => new Uint8Array(value);
|
|
26
|
-
export function createMemoryStorage() {
|
|
27
|
-
const data = new Map();
|
|
28
|
-
let schema = 0;
|
|
29
|
-
return {
|
|
30
|
-
async open() { },
|
|
31
|
-
async get(k) {
|
|
32
|
-
validateKey(k);
|
|
33
|
-
return data.has(k) ? copy(data.get(k)) : null;
|
|
34
|
-
},
|
|
35
|
-
async put(k, v) {
|
|
36
|
-
validateKey(k);
|
|
37
|
-
data.set(k, copy(v));
|
|
38
|
-
},
|
|
39
|
-
async delete(k) {
|
|
40
|
-
validateKey(k);
|
|
41
|
-
const value = data.get(k);
|
|
42
|
-
if (value) {
|
|
43
|
-
// We don't zeroize here by default since we don't know if this is sensitive data.
|
|
44
|
-
data.delete(k);
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
async batch(ops) {
|
|
48
|
-
const draft = new Map(data);
|
|
49
|
-
for (const op of ops) {
|
|
50
|
-
if (op.put) {
|
|
51
|
-
const [k, v] = op.put;
|
|
52
|
-
validateKey(k);
|
|
53
|
-
draft.set(k, copy(v));
|
|
54
|
-
}
|
|
55
|
-
if (op.del) {
|
|
56
|
-
validateKey(op.del);
|
|
57
|
-
draft.delete(op.del);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
data.clear();
|
|
61
|
-
for (const [k, v] of draft.entries()) {
|
|
62
|
-
data.set(k, copy(v));
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
async iter(o = {}) {
|
|
66
|
-
if (o.start && o.end && o.start > o.end) {
|
|
67
|
-
throw new CoreError("iter start bound must not exceed end bound");
|
|
68
|
-
}
|
|
69
|
-
const all = [...data.entries()].map(([key, value]) => ({
|
|
70
|
-
key,
|
|
71
|
-
value: copy(value),
|
|
72
|
-
}));
|
|
73
|
-
let ks = all
|
|
74
|
-
.filter(({ key }) => (!o.prefix || key.startsWith(o.prefix)) &&
|
|
75
|
-
(!o.start || key >= o.start) &&
|
|
76
|
-
(!o.end || key <= o.end))
|
|
77
|
-
.sort((a, b) => a.key.localeCompare(b.key));
|
|
78
|
-
if (o.reverse)
|
|
79
|
-
ks.reverse();
|
|
80
|
-
if (o.limit)
|
|
81
|
-
ks = ks.slice(0, o.limit);
|
|
82
|
-
return ks;
|
|
83
|
-
},
|
|
84
|
-
async getSchemaVersion() {
|
|
85
|
-
return schema;
|
|
86
|
-
},
|
|
87
|
-
async setSchemaVersion(n) {
|
|
88
|
-
schema = n;
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
}
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import { Interface } from "ethers";
|
|
2
|
-
import { resolveFetch } from "../clients/http.js";
|
|
3
|
-
import { createIndexerClient } from "../clients/indexer.js";
|
|
4
|
-
import { createServiceConfig } from "../config.js";
|
|
5
|
-
import { ETH_TOKEN } from "../constants.js";
|
|
6
|
-
import { CoreError, InitializationError, ValidationError } from "../errors.js";
|
|
7
|
-
import { FieldSize, Hex } from "../keys/hex.js";
|
|
8
|
-
import { DEFAULT_JOB_TIMEOUT_MS, rebuildTreeFromStore, resolveMerkleTrees, } from "../state/index.js";
|
|
9
|
-
import { sleep } from "../utils/async.js";
|
|
10
|
-
import { computeCommitment, deriveNpk, encryptNote } from "../utils/crypto.js";
|
|
11
|
-
import { DEFAULT_INITIAL_POLL_DELAY_MS, DEFAULT_POLL_INTERVAL_MS, DEFAULT_POLL_TIMEOUT_MS, MAX_POLL_INTERVAL_MS, } from "../utils/polling.js";
|
|
12
|
-
export const DEPOSIT_ABI = [
|
|
13
|
-
"function deposit(address _depositor, (uint256 npk, uint256 amount, address token)[] notes, (uint256[3] data)[] ciphertexts) payable",
|
|
14
|
-
];
|
|
15
|
-
const depositInterface = new Interface(DEPOSIT_ABI);
|
|
16
|
-
/**
|
|
17
|
-
* Process notes: derive npk, compute commitment, and encrypt.
|
|
18
|
-
*/
|
|
19
|
-
function processNotes(_account, _chainId, _poolAddress, _depositor, notes) {
|
|
20
|
-
return notes.map((note) => {
|
|
21
|
-
const npk = deriveNpk(note);
|
|
22
|
-
const commitment = computeCommitment(note, npk);
|
|
23
|
-
const commitmentHex = Hex.fromBigInt(commitment, FieldSize.SCALAR, true);
|
|
24
|
-
const encrypted = encryptNote(note);
|
|
25
|
-
return {
|
|
26
|
-
npk,
|
|
27
|
-
commitmentHex,
|
|
28
|
-
token: note.token,
|
|
29
|
-
amount: note.amount,
|
|
30
|
-
encrypted,
|
|
31
|
-
};
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Build deposit calldata from processed notes.
|
|
36
|
-
*/
|
|
37
|
-
function buildDepositCalldata(depositor, processedNotes) {
|
|
38
|
-
return depositInterface.encodeFunctionData("deposit", [
|
|
39
|
-
depositor,
|
|
40
|
-
processedNotes.map((d) => ({
|
|
41
|
-
npk: d.npk,
|
|
42
|
-
amount: d.amount,
|
|
43
|
-
token: d.token,
|
|
44
|
-
})),
|
|
45
|
-
processedNotes.map((d) => ({ data: d.encrypted.data })),
|
|
46
|
-
]);
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Generate a relay ID for tracking.
|
|
50
|
-
*/
|
|
51
|
-
function generateRelayId(prefix) {
|
|
52
|
-
return (globalThis.crypto?.randomUUID?.() ?? `${prefix}-${Date.now().toString(16)}`);
|
|
53
|
-
}
|
|
54
|
-
// ============================================================================
|
|
55
|
-
// Public API
|
|
56
|
-
// ============================================================================
|
|
57
|
-
/**
|
|
58
|
-
* Prepare a deposit: compute commitments, build calldata, and persist pending job.
|
|
59
|
-
* Accepts 1 or more notes in a single transaction.
|
|
60
|
-
*/
|
|
61
|
-
export async function deposit(store, req) {
|
|
62
|
-
if (!req.notes?.length) {
|
|
63
|
-
throw new ValidationError("At least one note is required for deposit");
|
|
64
|
-
}
|
|
65
|
-
const processedNotes = processNotes(req.account, req.chainId, req.poolAddress, req.depositor, req.notes);
|
|
66
|
-
const calldata = buildDepositCalldata(req.depositor, processedNotes);
|
|
67
|
-
const relayId = generateRelayId("dep");
|
|
68
|
-
const value = processedNotes
|
|
69
|
-
.filter((n) => n.token.toLowerCase() === ETH_TOKEN.toLowerCase())
|
|
70
|
-
.reduce((sum, n) => sum + n.amount, 0n);
|
|
71
|
-
const job = {
|
|
72
|
-
relayId,
|
|
73
|
-
kind: "deposit",
|
|
74
|
-
chainId: req.chainId,
|
|
75
|
-
status: "pending",
|
|
76
|
-
txHash: null,
|
|
77
|
-
createdAt: Date.now(),
|
|
78
|
-
timeoutMs: DEFAULT_JOB_TIMEOUT_MS,
|
|
79
|
-
predictedCommitments: processedNotes.map((d) => ({
|
|
80
|
-
hex: d.commitmentHex,
|
|
81
|
-
token: d.token,
|
|
82
|
-
amount: d.amount.toString(),
|
|
83
|
-
})),
|
|
84
|
-
};
|
|
85
|
-
await store.putJob(job);
|
|
86
|
-
return {
|
|
87
|
-
relayId,
|
|
88
|
-
to: req.poolAddress,
|
|
89
|
-
calldata,
|
|
90
|
-
value,
|
|
91
|
-
commitments: processedNotes.map((d) => ({
|
|
92
|
-
commitment: d.commitmentHex,
|
|
93
|
-
token: d.token,
|
|
94
|
-
amount: d.amount,
|
|
95
|
-
})),
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Poll for a single commitment and verify/persist it.
|
|
100
|
-
*/
|
|
101
|
-
async function pollAndVerifyCommitment(ctx, commitmentHex) {
|
|
102
|
-
let delay = ctx.pollInterval;
|
|
103
|
-
let record = null;
|
|
104
|
-
while (Date.now() <= ctx.deadline) {
|
|
105
|
-
record = await ctx.indexer.tryGetCommitment({
|
|
106
|
-
chainId: ctx.chainId,
|
|
107
|
-
commitment: commitmentHex,
|
|
108
|
-
});
|
|
109
|
-
if (record)
|
|
110
|
-
break;
|
|
111
|
-
await sleep(delay);
|
|
112
|
-
delay = Math.min(delay * 2, MAX_POLL_INTERVAL_MS);
|
|
113
|
-
}
|
|
114
|
-
if (!record) {
|
|
115
|
-
throw new CoreError(`commitment ${commitmentHex} not found before timeout`);
|
|
116
|
-
}
|
|
117
|
-
// Note: We intentionally do NOT verify or store the leaf here. The syncChain
|
|
118
|
-
// flow handles leaf storage, merkle verification, and note decryption together.
|
|
119
|
-
// If we verified the tree here, we'd need to keep it in sync across multiple
|
|
120
|
-
// reconcile calls, and if we stored the leaf, syncChain would skip this index
|
|
121
|
-
// and notes wouldn't be decrypted.
|
|
122
|
-
//
|
|
123
|
-
// The indexer's commitment record already contains the verified index and root,
|
|
124
|
-
// which we return to the caller. The subsequent sync will verify consistency
|
|
125
|
-
// when it processes this commitment.
|
|
126
|
-
return {
|
|
127
|
-
index: record.index,
|
|
128
|
-
commitment: record.commitment,
|
|
129
|
-
root: record.root,
|
|
130
|
-
txHash: record.txHash,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Create sync context with common setup.
|
|
135
|
-
*/
|
|
136
|
-
async function createSyncContext(store, chainId, createdAt, opts) {
|
|
137
|
-
const fetchFn = resolveFetch(opts.fetch);
|
|
138
|
-
if (!fetchFn)
|
|
139
|
-
throw new InitializationError("fetch is required for sync");
|
|
140
|
-
const serviceConfig = createServiceConfig(opts.rpcUrl);
|
|
141
|
-
const trees = resolveMerkleTrees(opts);
|
|
142
|
-
const indexer = createIndexerClient(serviceConfig.indexerBaseUrl, {
|
|
143
|
-
fetch: fetchFn,
|
|
144
|
-
});
|
|
145
|
-
const pollInterval = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
146
|
-
const pollTimeout = opts.pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
|
|
147
|
-
await rebuildTreeFromStore({
|
|
148
|
-
chainId,
|
|
149
|
-
trees,
|
|
150
|
-
loadLeaf: store.getLeaf.bind(store),
|
|
151
|
-
});
|
|
152
|
-
// Calculate deadline BEFORE initial delay so pollTimeout is the total wait time
|
|
153
|
-
const deadline = Date.now() + pollTimeout;
|
|
154
|
-
// Wait initial delay for fresh deposits to reduce wasted 404 polls
|
|
155
|
-
const initialDelay = opts.initialPollDelayMs ?? DEFAULT_INITIAL_POLL_DELAY_MS;
|
|
156
|
-
const jobAge = Date.now() - createdAt;
|
|
157
|
-
if (initialDelay > 0 && jobAge < initialDelay) {
|
|
158
|
-
await sleep(initialDelay - jobAge);
|
|
159
|
-
}
|
|
160
|
-
return {
|
|
161
|
-
store,
|
|
162
|
-
chainId,
|
|
163
|
-
indexer,
|
|
164
|
-
trees,
|
|
165
|
-
pollInterval,
|
|
166
|
-
deadline,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
// ============================================================================
|
|
170
|
-
// Sync Public API
|
|
171
|
-
// ============================================================================
|
|
172
|
-
/**
|
|
173
|
-
* Wait for all commitments in a deposit to be indexed and update local state.
|
|
174
|
-
* Works for both single and multi-note deposits.
|
|
175
|
-
*/
|
|
176
|
-
export async function syncDeposit(store, relayId, opts) {
|
|
177
|
-
const jobRecord = await store.getJob(relayId);
|
|
178
|
-
if (!jobRecord || jobRecord.kind !== "deposit")
|
|
179
|
-
throw new CoreError(`unknown deposit relay ${relayId}`);
|
|
180
|
-
const job = jobRecord;
|
|
181
|
-
const ctx = await createSyncContext(store, job.chainId, job.createdAt, opts);
|
|
182
|
-
try {
|
|
183
|
-
const syncedCommitments = [];
|
|
184
|
-
for (const predicted of job.predictedCommitments) {
|
|
185
|
-
const synced = await pollAndVerifyCommitment(ctx, predicted.hex);
|
|
186
|
-
syncedCommitments.push(synced);
|
|
187
|
-
}
|
|
188
|
-
const finalRoot = syncedCommitments[syncedCommitments.length - 1].root;
|
|
189
|
-
// Update job status (syncChain handles root storage)
|
|
190
|
-
await store.putJob({
|
|
191
|
-
...job,
|
|
192
|
-
status: "succeeded",
|
|
193
|
-
lastCheckedAt: Date.now(),
|
|
194
|
-
txHash: syncedCommitments[0]?.txHash ?? job.txHash ?? null,
|
|
195
|
-
predictedCommitments: job.predictedCommitments.map((p, i) => ({
|
|
196
|
-
...p,
|
|
197
|
-
index: syncedCommitments[i]?.index,
|
|
198
|
-
root: syncedCommitments[i]?.root,
|
|
199
|
-
})),
|
|
200
|
-
});
|
|
201
|
-
return {
|
|
202
|
-
chainId: job.chainId,
|
|
203
|
-
commitments: syncedCommitments.map((s) => ({
|
|
204
|
-
index: s.index,
|
|
205
|
-
commitment: s.commitment,
|
|
206
|
-
root: s.root,
|
|
207
|
-
})),
|
|
208
|
-
finalRoot,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
await store.putJob({
|
|
213
|
-
...job,
|
|
214
|
-
status: "failed",
|
|
215
|
-
lastCheckedAt: Date.now(),
|
|
216
|
-
error: error instanceof Error ? error.message : String(error),
|
|
217
|
-
});
|
|
218
|
-
throw error;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Primary API
|
|
2
|
-
export { deposit, syncDeposit } from "./deposit.js";
|
|
3
|
-
export { transact, syncTransact } from "./transact.js";
|
|
4
|
-
export { planTransaction } from "./transaction-planner.js";
|
|
5
|
-
export { planWithdrawals, } from "./withdrawal-planner.js";
|
|
6
|
-
export { planTransfers } from "./transfer-planner.js";
|
|
7
|
-
export { selectNotesForAmount } from "./note-selection.js";
|
|
8
|
-
export { createJobReconciler } from "./reconcile.js";
|
|
9
|
-
export { createNoteSyncService } from "./note-sync.js";
|