@unlink-xyz/node 0.1.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/dist/index.cjs +320 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +201 -0
- package/dist/index.d.ts +201 -0
- package/dist/index.js +271 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
DEFAULT_POLL_INTERVAL_MS: () => DEFAULT_POLL_INTERVAL_MS,
|
|
34
|
+
DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
|
|
35
|
+
FAILED_TX_STATES: () => FAILED_TX_STATES,
|
|
36
|
+
TERMINAL_TX_STATES: () => TERMINAL_TX_STATES,
|
|
37
|
+
TimeoutError: () => TimeoutError,
|
|
38
|
+
TransactionFailedError: () => TransactionFailedError,
|
|
39
|
+
UnlinkWallet: () => import_core3.UnlinkWallet,
|
|
40
|
+
createMemoryStorage: () => import_core3.createMemoryStorage,
|
|
41
|
+
createSqliteStorage: () => createSqliteStorage,
|
|
42
|
+
createWalletEmitter: () => createWalletEmitter,
|
|
43
|
+
initWallet: () => initWallet,
|
|
44
|
+
pollRelayStatus: () => pollRelayStatus,
|
|
45
|
+
waitForConfirmation: () => waitForConfirmation
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(index_exports);
|
|
48
|
+
|
|
49
|
+
// src/init.ts
|
|
50
|
+
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
51
|
+
var import_core = require("@unlink-xyz/core");
|
|
52
|
+
var nodeRng = (n) => new Uint8Array(import_node_crypto.default.randomBytes(n));
|
|
53
|
+
async function initWallet(config) {
|
|
54
|
+
const { setup, sync, ...coreConfig } = config;
|
|
55
|
+
void setup;
|
|
56
|
+
void sync;
|
|
57
|
+
const wallet = await import_core.UnlinkWallet.create({
|
|
58
|
+
...coreConfig,
|
|
59
|
+
storage: config.storage ?? (0, import_core.createMemoryStorage)(),
|
|
60
|
+
rng: config.rng ?? nodeRng,
|
|
61
|
+
autoSync: config.autoSync ?? false,
|
|
62
|
+
fetch: config.fetch ?? globalThis.fetch
|
|
63
|
+
});
|
|
64
|
+
if (config.setup !== false) {
|
|
65
|
+
const hasSeed = await wallet.seed.exists();
|
|
66
|
+
if (!hasSeed) {
|
|
67
|
+
await wallet.seed.create();
|
|
68
|
+
}
|
|
69
|
+
const accounts = await wallet.accounts.list();
|
|
70
|
+
if (accounts.length === 0) {
|
|
71
|
+
await wallet.accounts.create();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (config.sync !== false) {
|
|
75
|
+
await wallet.sync();
|
|
76
|
+
}
|
|
77
|
+
return wallet;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/errors.ts
|
|
81
|
+
var TERMINAL_TX_STATES = [
|
|
82
|
+
"succeeded",
|
|
83
|
+
"reverted",
|
|
84
|
+
"failed",
|
|
85
|
+
"dead"
|
|
86
|
+
];
|
|
87
|
+
var FAILED_TX_STATES = [
|
|
88
|
+
"reverted",
|
|
89
|
+
"failed",
|
|
90
|
+
"dead"
|
|
91
|
+
];
|
|
92
|
+
var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
93
|
+
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
94
|
+
var MAX_POLL_INTERVAL_MS = 3e4;
|
|
95
|
+
var BACKOFF_FACTOR = 1.5;
|
|
96
|
+
var TimeoutError = class extends Error {
|
|
97
|
+
txId;
|
|
98
|
+
timeout;
|
|
99
|
+
constructor(txId, timeout) {
|
|
100
|
+
super(`Transaction ${txId} did not confirm within ${timeout}ms`);
|
|
101
|
+
this.name = "TimeoutError";
|
|
102
|
+
this.txId = txId;
|
|
103
|
+
this.timeout = timeout;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
var TransactionFailedError = class extends Error {
|
|
107
|
+
txId;
|
|
108
|
+
state;
|
|
109
|
+
reason;
|
|
110
|
+
constructor(txId, state, reason) {
|
|
111
|
+
super(
|
|
112
|
+
`Transaction ${txId} failed with state: ${state}${reason ? ` \u2014 ${reason}` : ""}`
|
|
113
|
+
);
|
|
114
|
+
this.name = "TransactionFailedError";
|
|
115
|
+
this.txId = txId;
|
|
116
|
+
this.state = state;
|
|
117
|
+
this.reason = reason;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/confirmation.ts
|
|
122
|
+
function toTxStatus(txId, raw) {
|
|
123
|
+
return {
|
|
124
|
+
txId,
|
|
125
|
+
state: raw.state,
|
|
126
|
+
txHash: raw.txHash ?? void 0,
|
|
127
|
+
blockNumber: raw.receipt?.blockNumber ?? void 0,
|
|
128
|
+
error: raw.error ?? void 0
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function sleep(ms) {
|
|
132
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
133
|
+
}
|
|
134
|
+
async function waitForConfirmation(wallet, txId, opts) {
|
|
135
|
+
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
136
|
+
let interval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;
|
|
137
|
+
const deadline = Date.now() + timeout;
|
|
138
|
+
while (Date.now() < deadline) {
|
|
139
|
+
const raw = await wallet.getTxStatus(txId);
|
|
140
|
+
const status = toTxStatus(txId, raw);
|
|
141
|
+
if (status.state === "succeeded") {
|
|
142
|
+
return status;
|
|
143
|
+
}
|
|
144
|
+
if (FAILED_TX_STATES.includes(status.state)) {
|
|
145
|
+
throw new TransactionFailedError(txId, status.state, status.error);
|
|
146
|
+
}
|
|
147
|
+
const remaining = deadline - Date.now();
|
|
148
|
+
if (remaining <= 0) break;
|
|
149
|
+
await sleep(Math.min(interval, remaining));
|
|
150
|
+
interval = Math.min(interval * BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS);
|
|
151
|
+
}
|
|
152
|
+
throw new TimeoutError(txId, timeout);
|
|
153
|
+
}
|
|
154
|
+
async function* pollRelayStatus(wallet, txId, opts) {
|
|
155
|
+
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
156
|
+
let interval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;
|
|
157
|
+
const deadline = Date.now() + timeout;
|
|
158
|
+
while (Date.now() < deadline) {
|
|
159
|
+
const raw = await wallet.getTxStatus(txId);
|
|
160
|
+
const status = toTxStatus(txId, raw);
|
|
161
|
+
yield status;
|
|
162
|
+
if (TERMINAL_TX_STATES.includes(status.state)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const remaining = deadline - Date.now();
|
|
166
|
+
if (remaining <= 0) return;
|
|
167
|
+
await sleep(Math.min(interval, remaining));
|
|
168
|
+
interval = Math.min(interval * BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/events.ts
|
|
173
|
+
var import_node_events = require("events");
|
|
174
|
+
function createWalletEmitter(wallet) {
|
|
175
|
+
const emitter = new import_node_events.EventEmitter();
|
|
176
|
+
const unsubscribe = wallet.on((event) => {
|
|
177
|
+
emitter.emit(event.type, event);
|
|
178
|
+
emitter.emit("*", event);
|
|
179
|
+
});
|
|
180
|
+
return { emitter, unsubscribe };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/storage/sqlite.ts
|
|
184
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
185
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
186
|
+
var import_core2 = require("@unlink-xyz/core");
|
|
187
|
+
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
188
|
+
function createSqliteStorage(opts) {
|
|
189
|
+
let db = null;
|
|
190
|
+
let stmtGet;
|
|
191
|
+
let stmtPut;
|
|
192
|
+
let stmtDel;
|
|
193
|
+
let stmtGetSchema;
|
|
194
|
+
let stmtUpsertSchema;
|
|
195
|
+
return {
|
|
196
|
+
async open() {
|
|
197
|
+
const dir = import_node_path.default.dirname(opts.path);
|
|
198
|
+
import_node_fs.default.mkdirSync(dir, { recursive: true });
|
|
199
|
+
db = new import_better_sqlite3.default(opts.path);
|
|
200
|
+
db.pragma("journal_mode = WAL");
|
|
201
|
+
db.pragma("busy_timeout = 5000");
|
|
202
|
+
db.exec(`
|
|
203
|
+
CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value BLOB NOT NULL);
|
|
204
|
+
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value INTEGER NOT NULL);
|
|
205
|
+
`);
|
|
206
|
+
stmtGet = db.prepare("SELECT value FROM kv WHERE key = ?");
|
|
207
|
+
stmtPut = db.prepare(
|
|
208
|
+
"INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)"
|
|
209
|
+
);
|
|
210
|
+
stmtDel = db.prepare("DELETE FROM kv WHERE key = ?");
|
|
211
|
+
stmtGetSchema = db.prepare(
|
|
212
|
+
"SELECT value FROM meta WHERE key = 'schema_version'"
|
|
213
|
+
);
|
|
214
|
+
stmtUpsertSchema = db.prepare(
|
|
215
|
+
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)"
|
|
216
|
+
);
|
|
217
|
+
},
|
|
218
|
+
async get(key) {
|
|
219
|
+
(0, import_core2.validateKey)(key);
|
|
220
|
+
const row = stmtGet.get(key);
|
|
221
|
+
return row ? new Uint8Array(row.value) : null;
|
|
222
|
+
},
|
|
223
|
+
async put(key, value) {
|
|
224
|
+
(0, import_core2.validateKey)(key);
|
|
225
|
+
stmtPut.run(key, Buffer.from(value));
|
|
226
|
+
},
|
|
227
|
+
async delete(key) {
|
|
228
|
+
(0, import_core2.validateKey)(key);
|
|
229
|
+
stmtDel.run(key);
|
|
230
|
+
},
|
|
231
|
+
async batch(ops) {
|
|
232
|
+
const runBatch = db.transaction(() => {
|
|
233
|
+
for (const op of ops) {
|
|
234
|
+
if (op.put) {
|
|
235
|
+
const [k, v] = op.put;
|
|
236
|
+
(0, import_core2.validateKey)(k);
|
|
237
|
+
stmtPut.run(k, Buffer.from(v));
|
|
238
|
+
}
|
|
239
|
+
if (op.del) {
|
|
240
|
+
(0, import_core2.validateKey)(op.del);
|
|
241
|
+
stmtDel.run(op.del);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
runBatch();
|
|
246
|
+
},
|
|
247
|
+
async iter(o = {}) {
|
|
248
|
+
if (o.start && o.end && o.start > o.end) {
|
|
249
|
+
throw new Error("iter start bound must not exceed end bound");
|
|
250
|
+
}
|
|
251
|
+
const conditions = [];
|
|
252
|
+
const params = [];
|
|
253
|
+
if (o.prefix) {
|
|
254
|
+
const escaped = o.prefix.replace(/[%_\\]/g, "\\$&");
|
|
255
|
+
conditions.push("key LIKE ? ESCAPE '\\'");
|
|
256
|
+
params.push(escaped + "%");
|
|
257
|
+
}
|
|
258
|
+
if (o.start) {
|
|
259
|
+
conditions.push("key >= ?");
|
|
260
|
+
params.push(o.start);
|
|
261
|
+
}
|
|
262
|
+
if (o.end) {
|
|
263
|
+
conditions.push("key <= ?");
|
|
264
|
+
params.push(o.end);
|
|
265
|
+
}
|
|
266
|
+
let sql = "SELECT key, value FROM kv";
|
|
267
|
+
if (conditions.length > 0) {
|
|
268
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
269
|
+
}
|
|
270
|
+
sql += o.reverse ? " ORDER BY key DESC" : " ORDER BY key ASC";
|
|
271
|
+
if (o.limit != null) {
|
|
272
|
+
sql += " LIMIT ?";
|
|
273
|
+
params.push(o.limit);
|
|
274
|
+
}
|
|
275
|
+
const rows = db.prepare(sql).all(...params);
|
|
276
|
+
return rows.map((row) => ({
|
|
277
|
+
key: row.key,
|
|
278
|
+
value: new Uint8Array(row.value)
|
|
279
|
+
}));
|
|
280
|
+
},
|
|
281
|
+
async count(prefix) {
|
|
282
|
+
let sql = "SELECT COUNT(*) as cnt FROM kv";
|
|
283
|
+
const params = [];
|
|
284
|
+
if (prefix) {
|
|
285
|
+
const escaped = prefix.replace(/[%_\\]/g, "\\$&");
|
|
286
|
+
sql += " WHERE key LIKE ? ESCAPE '\\'";
|
|
287
|
+
params.push(escaped + "%");
|
|
288
|
+
}
|
|
289
|
+
const row = db.prepare(sql).get(...params);
|
|
290
|
+
return row.cnt;
|
|
291
|
+
},
|
|
292
|
+
async getSchemaVersion() {
|
|
293
|
+
const row = stmtGetSchema.get();
|
|
294
|
+
return row?.value ?? 0;
|
|
295
|
+
},
|
|
296
|
+
async setSchemaVersion(v) {
|
|
297
|
+
stmtUpsertSchema.run(v);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/index.ts
|
|
303
|
+
var import_core3 = require("@unlink-xyz/core");
|
|
304
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
305
|
+
0 && (module.exports = {
|
|
306
|
+
DEFAULT_POLL_INTERVAL_MS,
|
|
307
|
+
DEFAULT_TIMEOUT_MS,
|
|
308
|
+
FAILED_TX_STATES,
|
|
309
|
+
TERMINAL_TX_STATES,
|
|
310
|
+
TimeoutError,
|
|
311
|
+
TransactionFailedError,
|
|
312
|
+
UnlinkWallet,
|
|
313
|
+
createMemoryStorage,
|
|
314
|
+
createSqliteStorage,
|
|
315
|
+
createWalletEmitter,
|
|
316
|
+
initWallet,
|
|
317
|
+
pollRelayStatus,
|
|
318
|
+
waitForConfirmation
|
|
319
|
+
});
|
|
320
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/init.ts","../src/errors.ts","../src/confirmation.ts","../src/events.ts","../src/storage/sqlite.ts"],"sourcesContent":["// Init\nexport { initWallet } from \"./init.js\";\n\n// Confirmation\nexport { waitForConfirmation, pollRelayStatus } from \"./confirmation.js\";\n\n// Events\nexport { createWalletEmitter } from \"./events.js\";\n\n// Storage\nexport {\n createSqliteStorage,\n type SqliteStorageOptions,\n} from \"./storage/sqlite.js\";\n\n// Errors\nexport {\n TimeoutError,\n TransactionFailedError,\n TERMINAL_TX_STATES,\n FAILED_TX_STATES,\n DEFAULT_TIMEOUT_MS,\n DEFAULT_POLL_INTERVAL_MS,\n} from \"./errors.js\";\n\n// Types\nexport type {\n NodeWalletConfig,\n TxStatus,\n WaitOptions,\n PollOptions,\n} from \"./types.js\";\n\n// Re-export commonly needed core types so consumers don't need @unlink-xyz/core\nexport { UnlinkWallet, createMemoryStorage } from \"@unlink-xyz/core\";\n\nexport type {\n Account,\n AccountInfo,\n AccountView,\n DepositRelayResult,\n Environment,\n HistoryEntry,\n NoteRecord,\n RelayState,\n RelayStatusResponse,\n Storage,\n TransactRelayResult,\n TransferPlanResult,\n TransferResult,\n WalletSDKEvent,\n WithdrawPlanResult,\n WithdrawResult,\n} from \"@unlink-xyz/core\";\n","import crypto from \"node:crypto\";\nimport { createMemoryStorage, UnlinkWallet } from \"@unlink-xyz/core\";\n\nimport type { NodeWalletConfig } from \"./types.js\";\n\nconst nodeRng = (n: number): Uint8Array =>\n new Uint8Array(crypto.randomBytes(n));\n\n/**\n * Initialize an Unlink wallet with Node.js-friendly defaults.\n *\n * Compared to `UnlinkWallet.create()`, this:\n * - Defaults storage to in-memory (no IndexedDB required)\n * - Defaults RNG to `node:crypto`\n * - Defaults autoSync to `false`\n * - Optionally creates seed + first account automatically\n * - Optionally syncs notes from blockchain\n *\n * @example\n * ```ts\n * // Minimal — ready to use in one call:\n * const wallet = await initWallet({\n * chainId: 11155111,\n * environment: \"testnet\",\n * });\n *\n * // Skip auto-setup (e.g., to import an existing mnemonic):\n * const wallet = await initWallet({\n * chainId: 11155111,\n * environment: \"testnet\",\n * setup: false,\n * sync: false,\n * });\n * await wallet.seed.importMnemonic(\"your mnemonic ...\");\n * await wallet.accounts.create();\n * await wallet.sync();\n * ```\n */\nexport async function initWallet(\n config: NodeWalletConfig,\n): Promise<UnlinkWallet> {\n // Strip node-specific fields before passing to core\n const { setup, sync, ...coreConfig } = config;\n void setup;\n void sync;\n\n const wallet = await UnlinkWallet.create({\n ...coreConfig,\n storage: config.storage ?? createMemoryStorage(),\n rng: config.rng ?? nodeRng,\n autoSync: config.autoSync ?? false,\n fetch: config.fetch ?? globalThis.fetch,\n });\n\n if (config.setup !== false) {\n const hasSeed = await wallet.seed.exists();\n if (!hasSeed) {\n await wallet.seed.create();\n }\n\n const accounts = await wallet.accounts.list();\n if (accounts.length === 0) {\n await wallet.accounts.create();\n }\n }\n\n if (config.sync !== false) {\n await wallet.sync();\n }\n\n return wallet;\n}\n","import type { RelayState } from \"@unlink-xyz/core\";\n\n/** Terminal relay states — the transaction has finished processing. */\nexport const TERMINAL_TX_STATES: readonly RelayState[] = [\n \"succeeded\",\n \"reverted\",\n \"failed\",\n \"dead\",\n] as const;\n\n/** Failed relay states — the transaction did not succeed. */\nexport const FAILED_TX_STATES: readonly RelayState[] = [\n \"reverted\",\n \"failed\",\n \"dead\",\n] as const;\n\n/** Default timeout for `waitForConfirmation` (5 minutes). */\nexport const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;\n\n/** Default initial poll interval (2 seconds). */\nexport const DEFAULT_POLL_INTERVAL_MS = 2_000;\n\n/** Maximum poll interval after backoff (30 seconds). */\nexport const MAX_POLL_INTERVAL_MS = 30_000;\n\n/** Backoff multiplier for exponential polling. */\nexport const BACKOFF_FACTOR = 1.5;\n\n/** Thrown when relay polling exceeds the configured timeout. */\nexport class TimeoutError extends Error {\n readonly txId: string;\n readonly timeout: number;\n\n constructor(txId: string, timeout: number) {\n super(`Transaction ${txId} did not confirm within ${timeout}ms`);\n this.name = \"TimeoutError\";\n this.txId = txId;\n this.timeout = timeout;\n }\n}\n\n/** Thrown when a transaction reaches a failed terminal state. */\nexport class TransactionFailedError extends Error {\n readonly txId: string;\n readonly state: RelayState;\n readonly reason?: string;\n\n constructor(txId: string, state: RelayState, reason?: string) {\n super(\n `Transaction ${txId} failed with state: ${state}${reason ? ` — ${reason}` : \"\"}`,\n );\n this.name = \"TransactionFailedError\";\n this.txId = txId;\n this.state = state;\n this.reason = reason;\n }\n}\n","import type { UnlinkWallet } from \"@unlink-xyz/core\";\n\nimport {\n BACKOFF_FACTOR,\n DEFAULT_POLL_INTERVAL_MS,\n DEFAULT_TIMEOUT_MS,\n FAILED_TX_STATES,\n MAX_POLL_INTERVAL_MS,\n TERMINAL_TX_STATES,\n TimeoutError,\n TransactionFailedError,\n} from \"./errors.js\";\nimport type { PollOptions, TxStatus, WaitOptions } from \"./types.js\";\n\nfunction toTxStatus(\n txId: string,\n raw: Awaited<ReturnType<UnlinkWallet[\"getTxStatus\"]>>,\n): TxStatus {\n return {\n txId,\n state: raw.state,\n txHash: raw.txHash ?? undefined,\n blockNumber: raw.receipt?.blockNumber ?? undefined,\n error: raw.error ?? undefined,\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Poll relay status until the transaction reaches a terminal state.\n *\n * Resolves with `TxStatus` on success (`\"succeeded\"`).\n * Throws `TransactionFailedError` on failure (`\"reverted\"`, `\"failed\"`, `\"dead\"`).\n * Throws `TimeoutError` if the timeout is exceeded.\n *\n * Uses exponential backoff: 2s → 3s → 4.5s → ... capped at 30s.\n *\n * @example\n * ```ts\n * const result = await wallet.transfer({ transfers: [{ token, recipient, amount }] });\n * const status = await waitForConfirmation(wallet, result.relayId);\n * console.log(status.state); // \"succeeded\"\n * ```\n */\nexport async function waitForConfirmation(\n wallet: UnlinkWallet,\n txId: string,\n opts?: WaitOptions,\n): Promise<TxStatus> {\n const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;\n let interval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;\n const deadline = Date.now() + timeout;\n\n while (Date.now() < deadline) {\n const raw = await wallet.getTxStatus(txId);\n const status = toTxStatus(txId, raw);\n\n if (status.state === \"succeeded\") {\n return status;\n }\n\n if (FAILED_TX_STATES.includes(status.state)) {\n throw new TransactionFailedError(txId, status.state, status.error);\n }\n\n const remaining = deadline - Date.now();\n if (remaining <= 0) break;\n\n await sleep(Math.min(interval, remaining));\n interval = Math.min(interval * BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS);\n }\n\n throw new TimeoutError(txId, timeout);\n}\n\n/**\n * Async generator that yields `TxStatus` on each poll until a terminal state.\n *\n * Useful for streaming status updates (e.g., logging, progress bars).\n *\n * @example\n * ```ts\n * for await (const status of pollRelayStatus(wallet, relayId)) {\n * console.log(status.state); // \"pending\" → \"broadcasting\" → \"submitted\" → \"succeeded\"\n * }\n * ```\n */\nexport async function* pollRelayStatus(\n wallet: UnlinkWallet,\n txId: string,\n opts?: PollOptions,\n): AsyncGenerator<TxStatus, void, unknown> {\n const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;\n let interval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;\n const deadline = Date.now() + timeout;\n\n while (Date.now() < deadline) {\n const raw = await wallet.getTxStatus(txId);\n const status = toTxStatus(txId, raw);\n\n yield status;\n\n if (TERMINAL_TX_STATES.includes(status.state)) {\n return;\n }\n\n const remaining = deadline - Date.now();\n if (remaining <= 0) return;\n\n await sleep(Math.min(interval, remaining));\n interval = Math.min(interval * BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS);\n }\n}\n","import { EventEmitter } from \"node:events\";\nimport type { UnlinkWallet, WalletSDKEvent } from \"@unlink-xyz/core\";\n\n/**\n * Wrap an UnlinkWallet's event system with a Node.js `EventEmitter`.\n *\n * Events are emitted by their `type` field (e.g., `\"notes-updated\"`,\n * `\"tx-status-changed\"`). A wildcard `\"*\"` event is also emitted for\n * every event.\n *\n * @returns An `EventEmitter` and an `unsubscribe` function to detach from the wallet.\n *\n * @example\n * ```ts\n * const { emitter, unsubscribe } = createWalletEmitter(wallet);\n *\n * emitter.on(\"notes-updated\", (event) => {\n * console.log(\"Notes updated on chain\", event.chainId);\n * });\n *\n * emitter.on(\"tx-status-changed\", (event) => {\n * console.log(`TX ${event.txId}: ${event.state}`);\n * });\n *\n * // Listen to all events\n * emitter.on(\"*\", (event) => console.log(event));\n *\n * // Cleanup when done\n * unsubscribe();\n * ```\n */\nexport function createWalletEmitter(wallet: UnlinkWallet): {\n emitter: EventEmitter;\n unsubscribe: () => void;\n} {\n const emitter = new EventEmitter();\n\n const unsubscribe = wallet.on((event: WalletSDKEvent) => {\n emitter.emit(event.type, event);\n emitter.emit(\"*\", event);\n });\n\n return { emitter, unsubscribe };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type {\n BatchOp,\n Bytes,\n IterOptions,\n KvPair,\n Storage,\n} from \"@unlink-xyz/core\";\nimport { validateKey } from \"@unlink-xyz/core\";\nimport Database from \"better-sqlite3\";\n\nexport type SqliteStorageOptions = {\n /** Full path to the .db file */\n path: string;\n};\n\nexport function createSqliteStorage(opts: SqliteStorageOptions): Storage {\n let db: Database.Database | null = null;\n let stmtGet: Database.Statement;\n let stmtPut: Database.Statement;\n let stmtDel: Database.Statement;\n let stmtGetSchema: Database.Statement;\n let stmtUpsertSchema: Database.Statement;\n\n return {\n async open() {\n const dir = path.dirname(opts.path);\n fs.mkdirSync(dir, { recursive: true });\n\n db = new Database(opts.path);\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"busy_timeout = 5000\");\n db.exec(`\n CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value BLOB NOT NULL);\n CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value INTEGER NOT NULL);\n `);\n\n stmtGet = db.prepare(\"SELECT value FROM kv WHERE key = ?\");\n stmtPut = db.prepare(\n \"INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)\",\n );\n stmtDel = db.prepare(\"DELETE FROM kv WHERE key = ?\");\n stmtGetSchema = db.prepare(\n \"SELECT value FROM meta WHERE key = 'schema_version'\",\n );\n stmtUpsertSchema = db.prepare(\n \"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)\",\n );\n },\n\n async get(key: string): Promise<Bytes | null> {\n validateKey(key);\n const row = stmtGet.get(key) as { value: Buffer } | undefined;\n return row ? new Uint8Array(row.value) : null;\n },\n\n async put(key: string, value: Bytes): Promise<void> {\n validateKey(key);\n stmtPut.run(key, Buffer.from(value));\n },\n\n async delete(key: string): Promise<void> {\n validateKey(key);\n stmtDel.run(key);\n },\n\n async batch(ops: BatchOp[]): Promise<void> {\n const runBatch = db!.transaction(() => {\n for (const op of ops) {\n if (op.put) {\n const [k, v] = op.put;\n validateKey(k);\n stmtPut.run(k, Buffer.from(v));\n }\n if (op.del) {\n validateKey(op.del);\n stmtDel.run(op.del);\n }\n }\n });\n runBatch();\n },\n\n async iter(o: IterOptions = {}): Promise<KvPair[]> {\n if (o.start && o.end && o.start > o.end) {\n throw new Error(\"iter start bound must not exceed end bound\");\n }\n\n const conditions: string[] = [];\n const params: (string | number)[] = [];\n\n if (o.prefix) {\n // Escape SQL LIKE wildcards in the prefix\n const escaped = o.prefix.replace(/[%_\\\\]/g, \"\\\\$&\");\n conditions.push(\"key LIKE ? ESCAPE '\\\\'\");\n params.push(escaped + \"%\");\n }\n if (o.start) {\n conditions.push(\"key >= ?\");\n params.push(o.start);\n }\n if (o.end) {\n conditions.push(\"key <= ?\");\n params.push(o.end);\n }\n\n let sql = \"SELECT key, value FROM kv\";\n if (conditions.length > 0) {\n sql += \" WHERE \" + conditions.join(\" AND \");\n }\n sql += o.reverse ? \" ORDER BY key DESC\" : \" ORDER BY key ASC\";\n if (o.limit != null) {\n sql += \" LIMIT ?\";\n params.push(o.limit);\n }\n\n const rows = db!.prepare(sql).all(...params) as Array<{\n key: string;\n value: Buffer;\n }>;\n\n return rows.map((row) => ({\n key: row.key,\n value: new Uint8Array(row.value),\n }));\n },\n\n async count(prefix?: string): Promise<number> {\n let sql = \"SELECT COUNT(*) as cnt FROM kv\";\n const params: string[] = [];\n if (prefix) {\n const escaped = prefix.replace(/[%_\\\\]/g, \"\\\\$&\");\n sql += \" WHERE key LIKE ? ESCAPE '\\\\'\";\n params.push(escaped + \"%\");\n }\n const row = db!.prepare(sql).get(...params) as { cnt: number };\n return row.cnt;\n },\n\n async getSchemaVersion(): Promise<number> {\n const row = stmtGetSchema.get() as { value: number } | undefined;\n return row?.value ?? 0;\n },\n\n async setSchemaVersion(v: number): Promise<void> {\n stmtUpsertSchema.run(v);\n },\n };\n}\n"],"mappings":"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","names":["crypto","import_core","path","fs","Database","import_core"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { Storage, Environment, RelayState, UnlinkWallet } from '@unlink-xyz/core';
|
|
2
|
+
export { Account, AccountInfo, AccountView, DepositRelayResult, Environment, HistoryEntry, NoteRecord, RelayState, RelayStatusResponse, Storage, TransactRelayResult, TransferPlanResult, TransferResult, UnlinkWallet, WalletSDKEvent, WithdrawPlanResult, WithdrawResult, createMemoryStorage } from '@unlink-xyz/core';
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for `initWallet()`.
|
|
7
|
+
*
|
|
8
|
+
* Extends core wallet config with Node.js-friendly defaults:
|
|
9
|
+
* - `storage` defaults to in-memory
|
|
10
|
+
* - `rng` defaults to `node:crypto`
|
|
11
|
+
* - `autoSync` defaults to `false`
|
|
12
|
+
*/
|
|
13
|
+
type NodeWalletConfig = {
|
|
14
|
+
chainId: number;
|
|
15
|
+
/** Storage backend. Defaults to `createMemoryStorage()`. */
|
|
16
|
+
storage?: Storage;
|
|
17
|
+
/** Random number generator. Defaults to `node:crypto.randomBytes`. */
|
|
18
|
+
rng?: (n: number) => Uint8Array;
|
|
19
|
+
/** Whether to auto-sync on an interval. Defaults to `false`. */
|
|
20
|
+
autoSync?: boolean;
|
|
21
|
+
/** Custom fetch implementation. Defaults to `globalThis.fetch`. */
|
|
22
|
+
fetch?: typeof globalThis.fetch;
|
|
23
|
+
/** Chain RPC URL for burner EOA transactions. */
|
|
24
|
+
chainRpcUrl?: string;
|
|
25
|
+
/** Prover artifact configuration. */
|
|
26
|
+
prover?: {
|
|
27
|
+
artifactSource?: {
|
|
28
|
+
version?: string;
|
|
29
|
+
baseUrl?: string;
|
|
30
|
+
preferLocalFiles?: boolean;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Auto-create seed + first account on init. Defaults to `true`.
|
|
35
|
+
* Set to `false` if you want to import an existing mnemonic.
|
|
36
|
+
*/
|
|
37
|
+
setup?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Sync notes from blockchain after init. Defaults to `true`.
|
|
40
|
+
* Set to `false` for offline/local operations.
|
|
41
|
+
*/
|
|
42
|
+
sync?: boolean;
|
|
43
|
+
} & ({
|
|
44
|
+
/** Environment to auto-resolve gateway URL and pool address. */
|
|
45
|
+
environment: Environment;
|
|
46
|
+
/** Override pool address (defaults to environment config). */
|
|
47
|
+
poolAddress?: string;
|
|
48
|
+
} | {
|
|
49
|
+
/** Explicit gateway URL. */
|
|
50
|
+
gatewayUrl: string;
|
|
51
|
+
/** Pool contract address (required with explicit gatewayUrl). */
|
|
52
|
+
poolAddress: string;
|
|
53
|
+
});
|
|
54
|
+
/** Status of a relayed transaction. */
|
|
55
|
+
type TxStatus = {
|
|
56
|
+
txId: string;
|
|
57
|
+
state: RelayState;
|
|
58
|
+
txHash?: string;
|
|
59
|
+
blockNumber?: number;
|
|
60
|
+
error?: string;
|
|
61
|
+
};
|
|
62
|
+
/** Options for `waitForConfirmation()`. */
|
|
63
|
+
type WaitOptions = {
|
|
64
|
+
/** Timeout in milliseconds. Defaults to 5 minutes. */
|
|
65
|
+
timeout?: number;
|
|
66
|
+
/** Initial poll interval in milliseconds. Defaults to 2000. */
|
|
67
|
+
pollInterval?: number;
|
|
68
|
+
};
|
|
69
|
+
/** Options for `pollRelayStatus()`. */
|
|
70
|
+
type PollOptions = {
|
|
71
|
+
/** Initial poll interval in milliseconds. Defaults to 2000. */
|
|
72
|
+
pollInterval?: number;
|
|
73
|
+
/** Timeout in milliseconds. Defaults to 5 minutes. */
|
|
74
|
+
timeout?: number;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Initialize an Unlink wallet with Node.js-friendly defaults.
|
|
79
|
+
*
|
|
80
|
+
* Compared to `UnlinkWallet.create()`, this:
|
|
81
|
+
* - Defaults storage to in-memory (no IndexedDB required)
|
|
82
|
+
* - Defaults RNG to `node:crypto`
|
|
83
|
+
* - Defaults autoSync to `false`
|
|
84
|
+
* - Optionally creates seed + first account automatically
|
|
85
|
+
* - Optionally syncs notes from blockchain
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* // Minimal — ready to use in one call:
|
|
90
|
+
* const wallet = await initWallet({
|
|
91
|
+
* chainId: 11155111,
|
|
92
|
+
* environment: "testnet",
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* // Skip auto-setup (e.g., to import an existing mnemonic):
|
|
96
|
+
* const wallet = await initWallet({
|
|
97
|
+
* chainId: 11155111,
|
|
98
|
+
* environment: "testnet",
|
|
99
|
+
* setup: false,
|
|
100
|
+
* sync: false,
|
|
101
|
+
* });
|
|
102
|
+
* await wallet.seed.importMnemonic("your mnemonic ...");
|
|
103
|
+
* await wallet.accounts.create();
|
|
104
|
+
* await wallet.sync();
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function initWallet(config: NodeWalletConfig): Promise<UnlinkWallet>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Poll relay status until the transaction reaches a terminal state.
|
|
111
|
+
*
|
|
112
|
+
* Resolves with `TxStatus` on success (`"succeeded"`).
|
|
113
|
+
* Throws `TransactionFailedError` on failure (`"reverted"`, `"failed"`, `"dead"`).
|
|
114
|
+
* Throws `TimeoutError` if the timeout is exceeded.
|
|
115
|
+
*
|
|
116
|
+
* Uses exponential backoff: 2s → 3s → 4.5s → ... capped at 30s.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* const result = await wallet.transfer({ transfers: [{ token, recipient, amount }] });
|
|
121
|
+
* const status = await waitForConfirmation(wallet, result.relayId);
|
|
122
|
+
* console.log(status.state); // "succeeded"
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
declare function waitForConfirmation(wallet: UnlinkWallet, txId: string, opts?: WaitOptions): Promise<TxStatus>;
|
|
126
|
+
/**
|
|
127
|
+
* Async generator that yields `TxStatus` on each poll until a terminal state.
|
|
128
|
+
*
|
|
129
|
+
* Useful for streaming status updates (e.g., logging, progress bars).
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* for await (const status of pollRelayStatus(wallet, relayId)) {
|
|
134
|
+
* console.log(status.state); // "pending" → "broadcasting" → "submitted" → "succeeded"
|
|
135
|
+
* }
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
declare function pollRelayStatus(wallet: UnlinkWallet, txId: string, opts?: PollOptions): AsyncGenerator<TxStatus, void, unknown>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Wrap an UnlinkWallet's event system with a Node.js `EventEmitter`.
|
|
142
|
+
*
|
|
143
|
+
* Events are emitted by their `type` field (e.g., `"notes-updated"`,
|
|
144
|
+
* `"tx-status-changed"`). A wildcard `"*"` event is also emitted for
|
|
145
|
+
* every event.
|
|
146
|
+
*
|
|
147
|
+
* @returns An `EventEmitter` and an `unsubscribe` function to detach from the wallet.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* const { emitter, unsubscribe } = createWalletEmitter(wallet);
|
|
152
|
+
*
|
|
153
|
+
* emitter.on("notes-updated", (event) => {
|
|
154
|
+
* console.log("Notes updated on chain", event.chainId);
|
|
155
|
+
* });
|
|
156
|
+
*
|
|
157
|
+
* emitter.on("tx-status-changed", (event) => {
|
|
158
|
+
* console.log(`TX ${event.txId}: ${event.state}`);
|
|
159
|
+
* });
|
|
160
|
+
*
|
|
161
|
+
* // Listen to all events
|
|
162
|
+
* emitter.on("*", (event) => console.log(event));
|
|
163
|
+
*
|
|
164
|
+
* // Cleanup when done
|
|
165
|
+
* unsubscribe();
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
declare function createWalletEmitter(wallet: UnlinkWallet): {
|
|
169
|
+
emitter: EventEmitter;
|
|
170
|
+
unsubscribe: () => void;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
type SqliteStorageOptions = {
|
|
174
|
+
/** Full path to the .db file */
|
|
175
|
+
path: string;
|
|
176
|
+
};
|
|
177
|
+
declare function createSqliteStorage(opts: SqliteStorageOptions): Storage;
|
|
178
|
+
|
|
179
|
+
/** Terminal relay states — the transaction has finished processing. */
|
|
180
|
+
declare const TERMINAL_TX_STATES: readonly RelayState[];
|
|
181
|
+
/** Failed relay states — the transaction did not succeed. */
|
|
182
|
+
declare const FAILED_TX_STATES: readonly RelayState[];
|
|
183
|
+
/** Default timeout for `waitForConfirmation` (5 minutes). */
|
|
184
|
+
declare const DEFAULT_TIMEOUT_MS: number;
|
|
185
|
+
/** Default initial poll interval (2 seconds). */
|
|
186
|
+
declare const DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
187
|
+
/** Thrown when relay polling exceeds the configured timeout. */
|
|
188
|
+
declare class TimeoutError extends Error {
|
|
189
|
+
readonly txId: string;
|
|
190
|
+
readonly timeout: number;
|
|
191
|
+
constructor(txId: string, timeout: number);
|
|
192
|
+
}
|
|
193
|
+
/** Thrown when a transaction reaches a failed terminal state. */
|
|
194
|
+
declare class TransactionFailedError extends Error {
|
|
195
|
+
readonly txId: string;
|
|
196
|
+
readonly state: RelayState;
|
|
197
|
+
readonly reason?: string;
|
|
198
|
+
constructor(txId: string, state: RelayState, reason?: string);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export { DEFAULT_POLL_INTERVAL_MS, DEFAULT_TIMEOUT_MS, FAILED_TX_STATES, type NodeWalletConfig, type PollOptions, type SqliteStorageOptions, TERMINAL_TX_STATES, TimeoutError, TransactionFailedError, type TxStatus, type WaitOptions, createSqliteStorage, createWalletEmitter, initWallet, pollRelayStatus, waitForConfirmation };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { Storage, Environment, RelayState, UnlinkWallet } from '@unlink-xyz/core';
|
|
2
|
+
export { Account, AccountInfo, AccountView, DepositRelayResult, Environment, HistoryEntry, NoteRecord, RelayState, RelayStatusResponse, Storage, TransactRelayResult, TransferPlanResult, TransferResult, UnlinkWallet, WalletSDKEvent, WithdrawPlanResult, WithdrawResult, createMemoryStorage } from '@unlink-xyz/core';
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for `initWallet()`.
|
|
7
|
+
*
|
|
8
|
+
* Extends core wallet config with Node.js-friendly defaults:
|
|
9
|
+
* - `storage` defaults to in-memory
|
|
10
|
+
* - `rng` defaults to `node:crypto`
|
|
11
|
+
* - `autoSync` defaults to `false`
|
|
12
|
+
*/
|
|
13
|
+
type NodeWalletConfig = {
|
|
14
|
+
chainId: number;
|
|
15
|
+
/** Storage backend. Defaults to `createMemoryStorage()`. */
|
|
16
|
+
storage?: Storage;
|
|
17
|
+
/** Random number generator. Defaults to `node:crypto.randomBytes`. */
|
|
18
|
+
rng?: (n: number) => Uint8Array;
|
|
19
|
+
/** Whether to auto-sync on an interval. Defaults to `false`. */
|
|
20
|
+
autoSync?: boolean;
|
|
21
|
+
/** Custom fetch implementation. Defaults to `globalThis.fetch`. */
|
|
22
|
+
fetch?: typeof globalThis.fetch;
|
|
23
|
+
/** Chain RPC URL for burner EOA transactions. */
|
|
24
|
+
chainRpcUrl?: string;
|
|
25
|
+
/** Prover artifact configuration. */
|
|
26
|
+
prover?: {
|
|
27
|
+
artifactSource?: {
|
|
28
|
+
version?: string;
|
|
29
|
+
baseUrl?: string;
|
|
30
|
+
preferLocalFiles?: boolean;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Auto-create seed + first account on init. Defaults to `true`.
|
|
35
|
+
* Set to `false` if you want to import an existing mnemonic.
|
|
36
|
+
*/
|
|
37
|
+
setup?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Sync notes from blockchain after init. Defaults to `true`.
|
|
40
|
+
* Set to `false` for offline/local operations.
|
|
41
|
+
*/
|
|
42
|
+
sync?: boolean;
|
|
43
|
+
} & ({
|
|
44
|
+
/** Environment to auto-resolve gateway URL and pool address. */
|
|
45
|
+
environment: Environment;
|
|
46
|
+
/** Override pool address (defaults to environment config). */
|
|
47
|
+
poolAddress?: string;
|
|
48
|
+
} | {
|
|
49
|
+
/** Explicit gateway URL. */
|
|
50
|
+
gatewayUrl: string;
|
|
51
|
+
/** Pool contract address (required with explicit gatewayUrl). */
|
|
52
|
+
poolAddress: string;
|
|
53
|
+
});
|
|
54
|
+
/** Status of a relayed transaction. */
|
|
55
|
+
type TxStatus = {
|
|
56
|
+
txId: string;
|
|
57
|
+
state: RelayState;
|
|
58
|
+
txHash?: string;
|
|
59
|
+
blockNumber?: number;
|
|
60
|
+
error?: string;
|
|
61
|
+
};
|
|
62
|
+
/** Options for `waitForConfirmation()`. */
|
|
63
|
+
type WaitOptions = {
|
|
64
|
+
/** Timeout in milliseconds. Defaults to 5 minutes. */
|
|
65
|
+
timeout?: number;
|
|
66
|
+
/** Initial poll interval in milliseconds. Defaults to 2000. */
|
|
67
|
+
pollInterval?: number;
|
|
68
|
+
};
|
|
69
|
+
/** Options for `pollRelayStatus()`. */
|
|
70
|
+
type PollOptions = {
|
|
71
|
+
/** Initial poll interval in milliseconds. Defaults to 2000. */
|
|
72
|
+
pollInterval?: number;
|
|
73
|
+
/** Timeout in milliseconds. Defaults to 5 minutes. */
|
|
74
|
+
timeout?: number;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Initialize an Unlink wallet with Node.js-friendly defaults.
|
|
79
|
+
*
|
|
80
|
+
* Compared to `UnlinkWallet.create()`, this:
|
|
81
|
+
* - Defaults storage to in-memory (no IndexedDB required)
|
|
82
|
+
* - Defaults RNG to `node:crypto`
|
|
83
|
+
* - Defaults autoSync to `false`
|
|
84
|
+
* - Optionally creates seed + first account automatically
|
|
85
|
+
* - Optionally syncs notes from blockchain
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* // Minimal — ready to use in one call:
|
|
90
|
+
* const wallet = await initWallet({
|
|
91
|
+
* chainId: 11155111,
|
|
92
|
+
* environment: "testnet",
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* // Skip auto-setup (e.g., to import an existing mnemonic):
|
|
96
|
+
* const wallet = await initWallet({
|
|
97
|
+
* chainId: 11155111,
|
|
98
|
+
* environment: "testnet",
|
|
99
|
+
* setup: false,
|
|
100
|
+
* sync: false,
|
|
101
|
+
* });
|
|
102
|
+
* await wallet.seed.importMnemonic("your mnemonic ...");
|
|
103
|
+
* await wallet.accounts.create();
|
|
104
|
+
* await wallet.sync();
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function initWallet(config: NodeWalletConfig): Promise<UnlinkWallet>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Poll relay status until the transaction reaches a terminal state.
|
|
111
|
+
*
|
|
112
|
+
* Resolves with `TxStatus` on success (`"succeeded"`).
|
|
113
|
+
* Throws `TransactionFailedError` on failure (`"reverted"`, `"failed"`, `"dead"`).
|
|
114
|
+
* Throws `TimeoutError` if the timeout is exceeded.
|
|
115
|
+
*
|
|
116
|
+
* Uses exponential backoff: 2s → 3s → 4.5s → ... capped at 30s.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* const result = await wallet.transfer({ transfers: [{ token, recipient, amount }] });
|
|
121
|
+
* const status = await waitForConfirmation(wallet, result.relayId);
|
|
122
|
+
* console.log(status.state); // "succeeded"
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
declare function waitForConfirmation(wallet: UnlinkWallet, txId: string, opts?: WaitOptions): Promise<TxStatus>;
|
|
126
|
+
/**
|
|
127
|
+
* Async generator that yields `TxStatus` on each poll until a terminal state.
|
|
128
|
+
*
|
|
129
|
+
* Useful for streaming status updates (e.g., logging, progress bars).
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* for await (const status of pollRelayStatus(wallet, relayId)) {
|
|
134
|
+
* console.log(status.state); // "pending" → "broadcasting" → "submitted" → "succeeded"
|
|
135
|
+
* }
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
declare function pollRelayStatus(wallet: UnlinkWallet, txId: string, opts?: PollOptions): AsyncGenerator<TxStatus, void, unknown>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Wrap an UnlinkWallet's event system with a Node.js `EventEmitter`.
|
|
142
|
+
*
|
|
143
|
+
* Events are emitted by their `type` field (e.g., `"notes-updated"`,
|
|
144
|
+
* `"tx-status-changed"`). A wildcard `"*"` event is also emitted for
|
|
145
|
+
* every event.
|
|
146
|
+
*
|
|
147
|
+
* @returns An `EventEmitter` and an `unsubscribe` function to detach from the wallet.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* const { emitter, unsubscribe } = createWalletEmitter(wallet);
|
|
152
|
+
*
|
|
153
|
+
* emitter.on("notes-updated", (event) => {
|
|
154
|
+
* console.log("Notes updated on chain", event.chainId);
|
|
155
|
+
* });
|
|
156
|
+
*
|
|
157
|
+
* emitter.on("tx-status-changed", (event) => {
|
|
158
|
+
* console.log(`TX ${event.txId}: ${event.state}`);
|
|
159
|
+
* });
|
|
160
|
+
*
|
|
161
|
+
* // Listen to all events
|
|
162
|
+
* emitter.on("*", (event) => console.log(event));
|
|
163
|
+
*
|
|
164
|
+
* // Cleanup when done
|
|
165
|
+
* unsubscribe();
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
declare function createWalletEmitter(wallet: UnlinkWallet): {
|
|
169
|
+
emitter: EventEmitter;
|
|
170
|
+
unsubscribe: () => void;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
type SqliteStorageOptions = {
|
|
174
|
+
/** Full path to the .db file */
|
|
175
|
+
path: string;
|
|
176
|
+
};
|
|
177
|
+
declare function createSqliteStorage(opts: SqliteStorageOptions): Storage;
|
|
178
|
+
|
|
179
|
+
/** Terminal relay states — the transaction has finished processing. */
|
|
180
|
+
declare const TERMINAL_TX_STATES: readonly RelayState[];
|
|
181
|
+
/** Failed relay states — the transaction did not succeed. */
|
|
182
|
+
declare const FAILED_TX_STATES: readonly RelayState[];
|
|
183
|
+
/** Default timeout for `waitForConfirmation` (5 minutes). */
|
|
184
|
+
declare const DEFAULT_TIMEOUT_MS: number;
|
|
185
|
+
/** Default initial poll interval (2 seconds). */
|
|
186
|
+
declare const DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
187
|
+
/** Thrown when relay polling exceeds the configured timeout. */
|
|
188
|
+
declare class TimeoutError extends Error {
|
|
189
|
+
readonly txId: string;
|
|
190
|
+
readonly timeout: number;
|
|
191
|
+
constructor(txId: string, timeout: number);
|
|
192
|
+
}
|
|
193
|
+
/** Thrown when a transaction reaches a failed terminal state. */
|
|
194
|
+
declare class TransactionFailedError extends Error {
|
|
195
|
+
readonly txId: string;
|
|
196
|
+
readonly state: RelayState;
|
|
197
|
+
readonly reason?: string;
|
|
198
|
+
constructor(txId: string, state: RelayState, reason?: string);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export { DEFAULT_POLL_INTERVAL_MS, DEFAULT_TIMEOUT_MS, FAILED_TX_STATES, type NodeWalletConfig, type PollOptions, type SqliteStorageOptions, TERMINAL_TX_STATES, TimeoutError, TransactionFailedError, type TxStatus, type WaitOptions, createSqliteStorage, createWalletEmitter, initWallet, pollRelayStatus, waitForConfirmation };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// src/init.ts
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
import { createMemoryStorage, UnlinkWallet } from "@unlink-xyz/core";
|
|
4
|
+
var nodeRng = (n) => new Uint8Array(crypto.randomBytes(n));
|
|
5
|
+
async function initWallet(config) {
|
|
6
|
+
const { setup, sync, ...coreConfig } = config;
|
|
7
|
+
void setup;
|
|
8
|
+
void sync;
|
|
9
|
+
const wallet = await UnlinkWallet.create({
|
|
10
|
+
...coreConfig,
|
|
11
|
+
storage: config.storage ?? createMemoryStorage(),
|
|
12
|
+
rng: config.rng ?? nodeRng,
|
|
13
|
+
autoSync: config.autoSync ?? false,
|
|
14
|
+
fetch: config.fetch ?? globalThis.fetch
|
|
15
|
+
});
|
|
16
|
+
if (config.setup !== false) {
|
|
17
|
+
const hasSeed = await wallet.seed.exists();
|
|
18
|
+
if (!hasSeed) {
|
|
19
|
+
await wallet.seed.create();
|
|
20
|
+
}
|
|
21
|
+
const accounts = await wallet.accounts.list();
|
|
22
|
+
if (accounts.length === 0) {
|
|
23
|
+
await wallet.accounts.create();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (config.sync !== false) {
|
|
27
|
+
await wallet.sync();
|
|
28
|
+
}
|
|
29
|
+
return wallet;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/errors.ts
|
|
33
|
+
var TERMINAL_TX_STATES = [
|
|
34
|
+
"succeeded",
|
|
35
|
+
"reverted",
|
|
36
|
+
"failed",
|
|
37
|
+
"dead"
|
|
38
|
+
];
|
|
39
|
+
var FAILED_TX_STATES = [
|
|
40
|
+
"reverted",
|
|
41
|
+
"failed",
|
|
42
|
+
"dead"
|
|
43
|
+
];
|
|
44
|
+
var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
45
|
+
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
46
|
+
var MAX_POLL_INTERVAL_MS = 3e4;
|
|
47
|
+
var BACKOFF_FACTOR = 1.5;
|
|
48
|
+
var TimeoutError = class extends Error {
|
|
49
|
+
txId;
|
|
50
|
+
timeout;
|
|
51
|
+
constructor(txId, timeout) {
|
|
52
|
+
super(`Transaction ${txId} did not confirm within ${timeout}ms`);
|
|
53
|
+
this.name = "TimeoutError";
|
|
54
|
+
this.txId = txId;
|
|
55
|
+
this.timeout = timeout;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var TransactionFailedError = class extends Error {
|
|
59
|
+
txId;
|
|
60
|
+
state;
|
|
61
|
+
reason;
|
|
62
|
+
constructor(txId, state, reason) {
|
|
63
|
+
super(
|
|
64
|
+
`Transaction ${txId} failed with state: ${state}${reason ? ` \u2014 ${reason}` : ""}`
|
|
65
|
+
);
|
|
66
|
+
this.name = "TransactionFailedError";
|
|
67
|
+
this.txId = txId;
|
|
68
|
+
this.state = state;
|
|
69
|
+
this.reason = reason;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/confirmation.ts
|
|
74
|
+
function toTxStatus(txId, raw) {
|
|
75
|
+
return {
|
|
76
|
+
txId,
|
|
77
|
+
state: raw.state,
|
|
78
|
+
txHash: raw.txHash ?? void 0,
|
|
79
|
+
blockNumber: raw.receipt?.blockNumber ?? void 0,
|
|
80
|
+
error: raw.error ?? void 0
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function sleep(ms) {
|
|
84
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
85
|
+
}
|
|
86
|
+
async function waitForConfirmation(wallet, txId, opts) {
|
|
87
|
+
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
88
|
+
let interval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;
|
|
89
|
+
const deadline = Date.now() + timeout;
|
|
90
|
+
while (Date.now() < deadline) {
|
|
91
|
+
const raw = await wallet.getTxStatus(txId);
|
|
92
|
+
const status = toTxStatus(txId, raw);
|
|
93
|
+
if (status.state === "succeeded") {
|
|
94
|
+
return status;
|
|
95
|
+
}
|
|
96
|
+
if (FAILED_TX_STATES.includes(status.state)) {
|
|
97
|
+
throw new TransactionFailedError(txId, status.state, status.error);
|
|
98
|
+
}
|
|
99
|
+
const remaining = deadline - Date.now();
|
|
100
|
+
if (remaining <= 0) break;
|
|
101
|
+
await sleep(Math.min(interval, remaining));
|
|
102
|
+
interval = Math.min(interval * BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS);
|
|
103
|
+
}
|
|
104
|
+
throw new TimeoutError(txId, timeout);
|
|
105
|
+
}
|
|
106
|
+
async function* pollRelayStatus(wallet, txId, opts) {
|
|
107
|
+
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
108
|
+
let interval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;
|
|
109
|
+
const deadline = Date.now() + timeout;
|
|
110
|
+
while (Date.now() < deadline) {
|
|
111
|
+
const raw = await wallet.getTxStatus(txId);
|
|
112
|
+
const status = toTxStatus(txId, raw);
|
|
113
|
+
yield status;
|
|
114
|
+
if (TERMINAL_TX_STATES.includes(status.state)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const remaining = deadline - Date.now();
|
|
118
|
+
if (remaining <= 0) return;
|
|
119
|
+
await sleep(Math.min(interval, remaining));
|
|
120
|
+
interval = Math.min(interval * BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/events.ts
|
|
125
|
+
import { EventEmitter } from "events";
|
|
126
|
+
function createWalletEmitter(wallet) {
|
|
127
|
+
const emitter = new EventEmitter();
|
|
128
|
+
const unsubscribe = wallet.on((event) => {
|
|
129
|
+
emitter.emit(event.type, event);
|
|
130
|
+
emitter.emit("*", event);
|
|
131
|
+
});
|
|
132
|
+
return { emitter, unsubscribe };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/storage/sqlite.ts
|
|
136
|
+
import fs from "fs";
|
|
137
|
+
import path from "path";
|
|
138
|
+
import { validateKey } from "@unlink-xyz/core";
|
|
139
|
+
import Database from "better-sqlite3";
|
|
140
|
+
function createSqliteStorage(opts) {
|
|
141
|
+
let db = null;
|
|
142
|
+
let stmtGet;
|
|
143
|
+
let stmtPut;
|
|
144
|
+
let stmtDel;
|
|
145
|
+
let stmtGetSchema;
|
|
146
|
+
let stmtUpsertSchema;
|
|
147
|
+
return {
|
|
148
|
+
async open() {
|
|
149
|
+
const dir = path.dirname(opts.path);
|
|
150
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
151
|
+
db = new Database(opts.path);
|
|
152
|
+
db.pragma("journal_mode = WAL");
|
|
153
|
+
db.pragma("busy_timeout = 5000");
|
|
154
|
+
db.exec(`
|
|
155
|
+
CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value BLOB NOT NULL);
|
|
156
|
+
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value INTEGER NOT NULL);
|
|
157
|
+
`);
|
|
158
|
+
stmtGet = db.prepare("SELECT value FROM kv WHERE key = ?");
|
|
159
|
+
stmtPut = db.prepare(
|
|
160
|
+
"INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)"
|
|
161
|
+
);
|
|
162
|
+
stmtDel = db.prepare("DELETE FROM kv WHERE key = ?");
|
|
163
|
+
stmtGetSchema = db.prepare(
|
|
164
|
+
"SELECT value FROM meta WHERE key = 'schema_version'"
|
|
165
|
+
);
|
|
166
|
+
stmtUpsertSchema = db.prepare(
|
|
167
|
+
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)"
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
async get(key) {
|
|
171
|
+
validateKey(key);
|
|
172
|
+
const row = stmtGet.get(key);
|
|
173
|
+
return row ? new Uint8Array(row.value) : null;
|
|
174
|
+
},
|
|
175
|
+
async put(key, value) {
|
|
176
|
+
validateKey(key);
|
|
177
|
+
stmtPut.run(key, Buffer.from(value));
|
|
178
|
+
},
|
|
179
|
+
async delete(key) {
|
|
180
|
+
validateKey(key);
|
|
181
|
+
stmtDel.run(key);
|
|
182
|
+
},
|
|
183
|
+
async batch(ops) {
|
|
184
|
+
const runBatch = db.transaction(() => {
|
|
185
|
+
for (const op of ops) {
|
|
186
|
+
if (op.put) {
|
|
187
|
+
const [k, v] = op.put;
|
|
188
|
+
validateKey(k);
|
|
189
|
+
stmtPut.run(k, Buffer.from(v));
|
|
190
|
+
}
|
|
191
|
+
if (op.del) {
|
|
192
|
+
validateKey(op.del);
|
|
193
|
+
stmtDel.run(op.del);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
runBatch();
|
|
198
|
+
},
|
|
199
|
+
async iter(o = {}) {
|
|
200
|
+
if (o.start && o.end && o.start > o.end) {
|
|
201
|
+
throw new Error("iter start bound must not exceed end bound");
|
|
202
|
+
}
|
|
203
|
+
const conditions = [];
|
|
204
|
+
const params = [];
|
|
205
|
+
if (o.prefix) {
|
|
206
|
+
const escaped = o.prefix.replace(/[%_\\]/g, "\\$&");
|
|
207
|
+
conditions.push("key LIKE ? ESCAPE '\\'");
|
|
208
|
+
params.push(escaped + "%");
|
|
209
|
+
}
|
|
210
|
+
if (o.start) {
|
|
211
|
+
conditions.push("key >= ?");
|
|
212
|
+
params.push(o.start);
|
|
213
|
+
}
|
|
214
|
+
if (o.end) {
|
|
215
|
+
conditions.push("key <= ?");
|
|
216
|
+
params.push(o.end);
|
|
217
|
+
}
|
|
218
|
+
let sql = "SELECT key, value FROM kv";
|
|
219
|
+
if (conditions.length > 0) {
|
|
220
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
221
|
+
}
|
|
222
|
+
sql += o.reverse ? " ORDER BY key DESC" : " ORDER BY key ASC";
|
|
223
|
+
if (o.limit != null) {
|
|
224
|
+
sql += " LIMIT ?";
|
|
225
|
+
params.push(o.limit);
|
|
226
|
+
}
|
|
227
|
+
const rows = db.prepare(sql).all(...params);
|
|
228
|
+
return rows.map((row) => ({
|
|
229
|
+
key: row.key,
|
|
230
|
+
value: new Uint8Array(row.value)
|
|
231
|
+
}));
|
|
232
|
+
},
|
|
233
|
+
async count(prefix) {
|
|
234
|
+
let sql = "SELECT COUNT(*) as cnt FROM kv";
|
|
235
|
+
const params = [];
|
|
236
|
+
if (prefix) {
|
|
237
|
+
const escaped = prefix.replace(/[%_\\]/g, "\\$&");
|
|
238
|
+
sql += " WHERE key LIKE ? ESCAPE '\\'";
|
|
239
|
+
params.push(escaped + "%");
|
|
240
|
+
}
|
|
241
|
+
const row = db.prepare(sql).get(...params);
|
|
242
|
+
return row.cnt;
|
|
243
|
+
},
|
|
244
|
+
async getSchemaVersion() {
|
|
245
|
+
const row = stmtGetSchema.get();
|
|
246
|
+
return row?.value ?? 0;
|
|
247
|
+
},
|
|
248
|
+
async setSchemaVersion(v) {
|
|
249
|
+
stmtUpsertSchema.run(v);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/index.ts
|
|
255
|
+
import { UnlinkWallet as UnlinkWallet2, createMemoryStorage as createMemoryStorage2 } from "@unlink-xyz/core";
|
|
256
|
+
export {
|
|
257
|
+
DEFAULT_POLL_INTERVAL_MS,
|
|
258
|
+
DEFAULT_TIMEOUT_MS,
|
|
259
|
+
FAILED_TX_STATES,
|
|
260
|
+
TERMINAL_TX_STATES,
|
|
261
|
+
TimeoutError,
|
|
262
|
+
TransactionFailedError,
|
|
263
|
+
UnlinkWallet2 as UnlinkWallet,
|
|
264
|
+
createMemoryStorage2 as createMemoryStorage,
|
|
265
|
+
createSqliteStorage,
|
|
266
|
+
createWalletEmitter,
|
|
267
|
+
initWallet,
|
|
268
|
+
pollRelayStatus,
|
|
269
|
+
waitForConfirmation
|
|
270
|
+
};
|
|
271
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/init.ts","../src/errors.ts","../src/confirmation.ts","../src/events.ts","../src/storage/sqlite.ts","../src/index.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport { createMemoryStorage, UnlinkWallet } from \"@unlink-xyz/core\";\n\nimport type { NodeWalletConfig } from \"./types.js\";\n\nconst nodeRng = (n: number): Uint8Array =>\n new Uint8Array(crypto.randomBytes(n));\n\n/**\n * Initialize an Unlink wallet with Node.js-friendly defaults.\n *\n * Compared to `UnlinkWallet.create()`, this:\n * - Defaults storage to in-memory (no IndexedDB required)\n * - Defaults RNG to `node:crypto`\n * - Defaults autoSync to `false`\n * - Optionally creates seed + first account automatically\n * - Optionally syncs notes from blockchain\n *\n * @example\n * ```ts\n * // Minimal — ready to use in one call:\n * const wallet = await initWallet({\n * chainId: 11155111,\n * environment: \"testnet\",\n * });\n *\n * // Skip auto-setup (e.g., to import an existing mnemonic):\n * const wallet = await initWallet({\n * chainId: 11155111,\n * environment: \"testnet\",\n * setup: false,\n * sync: false,\n * });\n * await wallet.seed.importMnemonic(\"your mnemonic ...\");\n * await wallet.accounts.create();\n * await wallet.sync();\n * ```\n */\nexport async function initWallet(\n config: NodeWalletConfig,\n): Promise<UnlinkWallet> {\n // Strip node-specific fields before passing to core\n const { setup, sync, ...coreConfig } = config;\n void setup;\n void sync;\n\n const wallet = await UnlinkWallet.create({\n ...coreConfig,\n storage: config.storage ?? createMemoryStorage(),\n rng: config.rng ?? nodeRng,\n autoSync: config.autoSync ?? false,\n fetch: config.fetch ?? globalThis.fetch,\n });\n\n if (config.setup !== false) {\n const hasSeed = await wallet.seed.exists();\n if (!hasSeed) {\n await wallet.seed.create();\n }\n\n const accounts = await wallet.accounts.list();\n if (accounts.length === 0) {\n await wallet.accounts.create();\n }\n }\n\n if (config.sync !== false) {\n await wallet.sync();\n }\n\n return wallet;\n}\n","import type { RelayState } from \"@unlink-xyz/core\";\n\n/** Terminal relay states — the transaction has finished processing. */\nexport const TERMINAL_TX_STATES: readonly RelayState[] = [\n \"succeeded\",\n \"reverted\",\n \"failed\",\n \"dead\",\n] as const;\n\n/** Failed relay states — the transaction did not succeed. */\nexport const FAILED_TX_STATES: readonly RelayState[] = [\n \"reverted\",\n \"failed\",\n \"dead\",\n] as const;\n\n/** Default timeout for `waitForConfirmation` (5 minutes). */\nexport const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;\n\n/** Default initial poll interval (2 seconds). */\nexport const DEFAULT_POLL_INTERVAL_MS = 2_000;\n\n/** Maximum poll interval after backoff (30 seconds). */\nexport const MAX_POLL_INTERVAL_MS = 30_000;\n\n/** Backoff multiplier for exponential polling. */\nexport const BACKOFF_FACTOR = 1.5;\n\n/** Thrown when relay polling exceeds the configured timeout. */\nexport class TimeoutError extends Error {\n readonly txId: string;\n readonly timeout: number;\n\n constructor(txId: string, timeout: number) {\n super(`Transaction ${txId} did not confirm within ${timeout}ms`);\n this.name = \"TimeoutError\";\n this.txId = txId;\n this.timeout = timeout;\n }\n}\n\n/** Thrown when a transaction reaches a failed terminal state. */\nexport class TransactionFailedError extends Error {\n readonly txId: string;\n readonly state: RelayState;\n readonly reason?: string;\n\n constructor(txId: string, state: RelayState, reason?: string) {\n super(\n `Transaction ${txId} failed with state: ${state}${reason ? ` — ${reason}` : \"\"}`,\n );\n this.name = \"TransactionFailedError\";\n this.txId = txId;\n this.state = state;\n this.reason = reason;\n }\n}\n","import type { UnlinkWallet } from \"@unlink-xyz/core\";\n\nimport {\n BACKOFF_FACTOR,\n DEFAULT_POLL_INTERVAL_MS,\n DEFAULT_TIMEOUT_MS,\n FAILED_TX_STATES,\n MAX_POLL_INTERVAL_MS,\n TERMINAL_TX_STATES,\n TimeoutError,\n TransactionFailedError,\n} from \"./errors.js\";\nimport type { PollOptions, TxStatus, WaitOptions } from \"./types.js\";\n\nfunction toTxStatus(\n txId: string,\n raw: Awaited<ReturnType<UnlinkWallet[\"getTxStatus\"]>>,\n): TxStatus {\n return {\n txId,\n state: raw.state,\n txHash: raw.txHash ?? undefined,\n blockNumber: raw.receipt?.blockNumber ?? undefined,\n error: raw.error ?? undefined,\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Poll relay status until the transaction reaches a terminal state.\n *\n * Resolves with `TxStatus` on success (`\"succeeded\"`).\n * Throws `TransactionFailedError` on failure (`\"reverted\"`, `\"failed\"`, `\"dead\"`).\n * Throws `TimeoutError` if the timeout is exceeded.\n *\n * Uses exponential backoff: 2s → 3s → 4.5s → ... capped at 30s.\n *\n * @example\n * ```ts\n * const result = await wallet.transfer({ transfers: [{ token, recipient, amount }] });\n * const status = await waitForConfirmation(wallet, result.relayId);\n * console.log(status.state); // \"succeeded\"\n * ```\n */\nexport async function waitForConfirmation(\n wallet: UnlinkWallet,\n txId: string,\n opts?: WaitOptions,\n): Promise<TxStatus> {\n const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;\n let interval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;\n const deadline = Date.now() + timeout;\n\n while (Date.now() < deadline) {\n const raw = await wallet.getTxStatus(txId);\n const status = toTxStatus(txId, raw);\n\n if (status.state === \"succeeded\") {\n return status;\n }\n\n if (FAILED_TX_STATES.includes(status.state)) {\n throw new TransactionFailedError(txId, status.state, status.error);\n }\n\n const remaining = deadline - Date.now();\n if (remaining <= 0) break;\n\n await sleep(Math.min(interval, remaining));\n interval = Math.min(interval * BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS);\n }\n\n throw new TimeoutError(txId, timeout);\n}\n\n/**\n * Async generator that yields `TxStatus` on each poll until a terminal state.\n *\n * Useful for streaming status updates (e.g., logging, progress bars).\n *\n * @example\n * ```ts\n * for await (const status of pollRelayStatus(wallet, relayId)) {\n * console.log(status.state); // \"pending\" → \"broadcasting\" → \"submitted\" → \"succeeded\"\n * }\n * ```\n */\nexport async function* pollRelayStatus(\n wallet: UnlinkWallet,\n txId: string,\n opts?: PollOptions,\n): AsyncGenerator<TxStatus, void, unknown> {\n const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;\n let interval = opts?.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;\n const deadline = Date.now() + timeout;\n\n while (Date.now() < deadline) {\n const raw = await wallet.getTxStatus(txId);\n const status = toTxStatus(txId, raw);\n\n yield status;\n\n if (TERMINAL_TX_STATES.includes(status.state)) {\n return;\n }\n\n const remaining = deadline - Date.now();\n if (remaining <= 0) return;\n\n await sleep(Math.min(interval, remaining));\n interval = Math.min(interval * BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS);\n }\n}\n","import { EventEmitter } from \"node:events\";\nimport type { UnlinkWallet, WalletSDKEvent } from \"@unlink-xyz/core\";\n\n/**\n * Wrap an UnlinkWallet's event system with a Node.js `EventEmitter`.\n *\n * Events are emitted by their `type` field (e.g., `\"notes-updated\"`,\n * `\"tx-status-changed\"`). A wildcard `\"*\"` event is also emitted for\n * every event.\n *\n * @returns An `EventEmitter` and an `unsubscribe` function to detach from the wallet.\n *\n * @example\n * ```ts\n * const { emitter, unsubscribe } = createWalletEmitter(wallet);\n *\n * emitter.on(\"notes-updated\", (event) => {\n * console.log(\"Notes updated on chain\", event.chainId);\n * });\n *\n * emitter.on(\"tx-status-changed\", (event) => {\n * console.log(`TX ${event.txId}: ${event.state}`);\n * });\n *\n * // Listen to all events\n * emitter.on(\"*\", (event) => console.log(event));\n *\n * // Cleanup when done\n * unsubscribe();\n * ```\n */\nexport function createWalletEmitter(wallet: UnlinkWallet): {\n emitter: EventEmitter;\n unsubscribe: () => void;\n} {\n const emitter = new EventEmitter();\n\n const unsubscribe = wallet.on((event: WalletSDKEvent) => {\n emitter.emit(event.type, event);\n emitter.emit(\"*\", event);\n });\n\n return { emitter, unsubscribe };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type {\n BatchOp,\n Bytes,\n IterOptions,\n KvPair,\n Storage,\n} from \"@unlink-xyz/core\";\nimport { validateKey } from \"@unlink-xyz/core\";\nimport Database from \"better-sqlite3\";\n\nexport type SqliteStorageOptions = {\n /** Full path to the .db file */\n path: string;\n};\n\nexport function createSqliteStorage(opts: SqliteStorageOptions): Storage {\n let db: Database.Database | null = null;\n let stmtGet: Database.Statement;\n let stmtPut: Database.Statement;\n let stmtDel: Database.Statement;\n let stmtGetSchema: Database.Statement;\n let stmtUpsertSchema: Database.Statement;\n\n return {\n async open() {\n const dir = path.dirname(opts.path);\n fs.mkdirSync(dir, { recursive: true });\n\n db = new Database(opts.path);\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"busy_timeout = 5000\");\n db.exec(`\n CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value BLOB NOT NULL);\n CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value INTEGER NOT NULL);\n `);\n\n stmtGet = db.prepare(\"SELECT value FROM kv WHERE key = ?\");\n stmtPut = db.prepare(\n \"INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)\",\n );\n stmtDel = db.prepare(\"DELETE FROM kv WHERE key = ?\");\n stmtGetSchema = db.prepare(\n \"SELECT value FROM meta WHERE key = 'schema_version'\",\n );\n stmtUpsertSchema = db.prepare(\n \"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)\",\n );\n },\n\n async get(key: string): Promise<Bytes | null> {\n validateKey(key);\n const row = stmtGet.get(key) as { value: Buffer } | undefined;\n return row ? new Uint8Array(row.value) : null;\n },\n\n async put(key: string, value: Bytes): Promise<void> {\n validateKey(key);\n stmtPut.run(key, Buffer.from(value));\n },\n\n async delete(key: string): Promise<void> {\n validateKey(key);\n stmtDel.run(key);\n },\n\n async batch(ops: BatchOp[]): Promise<void> {\n const runBatch = db!.transaction(() => {\n for (const op of ops) {\n if (op.put) {\n const [k, v] = op.put;\n validateKey(k);\n stmtPut.run(k, Buffer.from(v));\n }\n if (op.del) {\n validateKey(op.del);\n stmtDel.run(op.del);\n }\n }\n });\n runBatch();\n },\n\n async iter(o: IterOptions = {}): Promise<KvPair[]> {\n if (o.start && o.end && o.start > o.end) {\n throw new Error(\"iter start bound must not exceed end bound\");\n }\n\n const conditions: string[] = [];\n const params: (string | number)[] = [];\n\n if (o.prefix) {\n // Escape SQL LIKE wildcards in the prefix\n const escaped = o.prefix.replace(/[%_\\\\]/g, \"\\\\$&\");\n conditions.push(\"key LIKE ? ESCAPE '\\\\'\");\n params.push(escaped + \"%\");\n }\n if (o.start) {\n conditions.push(\"key >= ?\");\n params.push(o.start);\n }\n if (o.end) {\n conditions.push(\"key <= ?\");\n params.push(o.end);\n }\n\n let sql = \"SELECT key, value FROM kv\";\n if (conditions.length > 0) {\n sql += \" WHERE \" + conditions.join(\" AND \");\n }\n sql += o.reverse ? \" ORDER BY key DESC\" : \" ORDER BY key ASC\";\n if (o.limit != null) {\n sql += \" LIMIT ?\";\n params.push(o.limit);\n }\n\n const rows = db!.prepare(sql).all(...params) as Array<{\n key: string;\n value: Buffer;\n }>;\n\n return rows.map((row) => ({\n key: row.key,\n value: new Uint8Array(row.value),\n }));\n },\n\n async count(prefix?: string): Promise<number> {\n let sql = \"SELECT COUNT(*) as cnt FROM kv\";\n const params: string[] = [];\n if (prefix) {\n const escaped = prefix.replace(/[%_\\\\]/g, \"\\\\$&\");\n sql += \" WHERE key LIKE ? ESCAPE '\\\\'\";\n params.push(escaped + \"%\");\n }\n const row = db!.prepare(sql).get(...params) as { cnt: number };\n return row.cnt;\n },\n\n async getSchemaVersion(): Promise<number> {\n const row = stmtGetSchema.get() as { value: number } | undefined;\n return row?.value ?? 0;\n },\n\n async setSchemaVersion(v: number): Promise<void> {\n stmtUpsertSchema.run(v);\n },\n };\n}\n","// Init\nexport { initWallet } from \"./init.js\";\n\n// Confirmation\nexport { waitForConfirmation, pollRelayStatus } from \"./confirmation.js\";\n\n// Events\nexport { createWalletEmitter } from \"./events.js\";\n\n// Storage\nexport {\n createSqliteStorage,\n type SqliteStorageOptions,\n} from \"./storage/sqlite.js\";\n\n// Errors\nexport {\n TimeoutError,\n TransactionFailedError,\n TERMINAL_TX_STATES,\n FAILED_TX_STATES,\n DEFAULT_TIMEOUT_MS,\n DEFAULT_POLL_INTERVAL_MS,\n} from \"./errors.js\";\n\n// Types\nexport type {\n NodeWalletConfig,\n TxStatus,\n WaitOptions,\n PollOptions,\n} from \"./types.js\";\n\n// Re-export commonly needed core types so consumers don't need @unlink-xyz/core\nexport { UnlinkWallet, createMemoryStorage } from \"@unlink-xyz/core\";\n\nexport type {\n Account,\n AccountInfo,\n AccountView,\n DepositRelayResult,\n Environment,\n HistoryEntry,\n NoteRecord,\n RelayState,\n RelayStatusResponse,\n Storage,\n TransactRelayResult,\n TransferPlanResult,\n TransferResult,\n WalletSDKEvent,\n WithdrawPlanResult,\n WithdrawResult,\n} from \"@unlink-xyz/core\";\n"],"mappings":"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","names":["UnlinkWallet","createMemoryStorage"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unlink-xyz/node",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"typecheck": "pnpm --filter @unlink-xyz/core build && tsc --noEmit",
|
|
29
|
+
"lint": "eslint .",
|
|
30
|
+
"lint:fix": "eslint . --fix",
|
|
31
|
+
"format": "prettier . --write",
|
|
32
|
+
"format:check": "prettier . --check",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest watch",
|
|
35
|
+
"test:coverage": "vitest run --coverage"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@unlink-xyz/core": "0.1.3",
|
|
39
|
+
"better-sqlite3": "^11.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"eslint": "^9.19.0",
|
|
45
|
+
"globals": "^15.14.0",
|
|
46
|
+
"prettier": "^3.5.3",
|
|
47
|
+
"tsup": "^8.3.5",
|
|
48
|
+
"typescript": "^5.9.2",
|
|
49
|
+
"vitest": "4.0.1"
|
|
50
|
+
}
|
|
51
|
+
}
|