@qubic.ts/sdk 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.
@@ -0,0 +1,147 @@
1
+ import type { QueryTransaction, RpcClient } from "../rpc/client.js";
2
+ import { RpcError } from "../rpc/client.js";
3
+
4
+ export type WaitForConfirmationInput = Readonly<{
5
+ txId: string;
6
+ targetTick: bigint | number;
7
+ timeoutMs?: number;
8
+ pollIntervalMs?: number;
9
+ signal?: AbortSignal;
10
+ }>;
11
+
12
+ export class TxConfirmationTimeoutError extends Error {
13
+ override name = "TxConfirmationTimeoutError";
14
+ }
15
+
16
+ export class TxNotFoundError extends Error {
17
+ override name = "TxNotFoundError";
18
+ }
19
+
20
+ export class TxConfirmationAbortedError extends Error {
21
+ override name = "TxConfirmationAbortedError";
22
+ }
23
+
24
+ export type TxConfirmationHelpersConfig = Readonly<{
25
+ rpc: RpcClient;
26
+ defaultTimeoutMs?: number;
27
+ defaultPollIntervalMs?: number;
28
+ }>;
29
+
30
+ export type TxConfirmationHelpers = Readonly<{
31
+ waitForConfirmation(input: WaitForConfirmationInput): Promise<void>;
32
+ waitForConfirmedTransaction(input: WaitForConfirmationInput): Promise<QueryTransaction>;
33
+ }>;
34
+
35
+ export function createTxConfirmationHelpers(
36
+ config: TxConfirmationHelpersConfig,
37
+ ): TxConfirmationHelpers {
38
+ const defaultTimeoutMs = config.defaultTimeoutMs ?? 60_000;
39
+ const defaultPollIntervalMs = config.defaultPollIntervalMs ?? 1_000;
40
+
41
+ const waitForConfirmedTransaction = async (
42
+ input: WaitForConfirmationInput,
43
+ ): Promise<QueryTransaction> => {
44
+ const start = Date.now();
45
+ const timeoutMs = input.timeoutMs ?? defaultTimeoutMs;
46
+ const pollIntervalMs = input.pollIntervalMs ?? defaultPollIntervalMs;
47
+ const targetTick = toBigint(input.targetTick);
48
+ let reachedTargetTick = false;
49
+ let sawNotFoundAfterTarget = false;
50
+
51
+ const controller = new AbortController();
52
+ const signals: AbortSignal[] = [controller.signal];
53
+ if (input.signal) signals.push(input.signal);
54
+ const signal = anySignal(signals);
55
+
56
+ if (signal.aborted) throw new TxConfirmationAbortedError("Confirmation aborted");
57
+
58
+ while (true) {
59
+ if (signal.aborted) throw new TxConfirmationAbortedError("Confirmation aborted");
60
+ if (Date.now() - start > timeoutMs) {
61
+ if (reachedTargetTick && sawNotFoundAfterTarget) {
62
+ throw new TxNotFoundError(
63
+ `Transaction ${input.txId} not found after target tick ${targetTick}`,
64
+ );
65
+ }
66
+ throw new TxConfirmationTimeoutError(
67
+ `Timed out waiting for confirmation of ${input.txId} (target tick ${targetTick})`,
68
+ );
69
+ }
70
+
71
+ const lastProcessed = await config.rpc.query.getLastProcessedTick();
72
+ if (lastProcessed.tickNumber < targetTick) {
73
+ await sleep(pollIntervalMs, signal);
74
+ continue;
75
+ }
76
+ reachedTargetTick = true;
77
+
78
+ try {
79
+ return await config.rpc.query.getTransactionByHash(input.txId);
80
+ } catch (err) {
81
+ if (err instanceof RpcError && err.details.status === 404) {
82
+ sawNotFoundAfterTarget = true;
83
+ await sleep(pollIntervalMs, signal);
84
+ continue;
85
+ }
86
+ throw err;
87
+ }
88
+ }
89
+ };
90
+
91
+ return {
92
+ async waitForConfirmation(input: WaitForConfirmationInput): Promise<void> {
93
+ await waitForConfirmedTransaction(input);
94
+ },
95
+
96
+ async waitForConfirmedTransaction(input: WaitForConfirmationInput): Promise<QueryTransaction> {
97
+ return waitForConfirmedTransaction(input);
98
+ },
99
+ };
100
+ }
101
+
102
+ function sleep(ms: number, signal: AbortSignal): Promise<void> {
103
+ if (ms <= 0) return Promise.resolve();
104
+ return new Promise((resolve, reject) => {
105
+ const id = setTimeout(() => {
106
+ cleanup();
107
+ resolve();
108
+ }, ms);
109
+
110
+ const onAbort = () => {
111
+ cleanup();
112
+ reject(new TxConfirmationAbortedError("Confirmation aborted"));
113
+ };
114
+
115
+ const cleanup = () => {
116
+ clearTimeout(id);
117
+ signal.removeEventListener("abort", onAbort);
118
+ };
119
+
120
+ signal.addEventListener("abort", onAbort, { once: true });
121
+ });
122
+ }
123
+
124
+ function toBigint(value: bigint | number): bigint {
125
+ if (typeof value === "bigint") return value;
126
+ if (!Number.isFinite(value) || !Number.isInteger(value)) {
127
+ throw new TypeError("Expected an integer");
128
+ }
129
+ return BigInt(value);
130
+ }
131
+
132
+ function anySignal(signals: readonly AbortSignal[]): AbortSignal {
133
+ if (signals.length === 0) return new AbortController().signal;
134
+ if (signals.length === 1) {
135
+ const only = signals[0];
136
+ if (!only) return new AbortController().signal;
137
+ return only;
138
+ }
139
+
140
+ const controller = new AbortController();
141
+ const onAbort = () => controller.abort();
142
+ for (const s of signals) {
143
+ if (s.aborted) return s;
144
+ s.addEventListener("abort", onAbort, { once: true });
145
+ }
146
+ return controller.signal;
147
+ }
@@ -0,0 +1,146 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { TxQueue, TxQueueError } from "./tx-queue.js";
3
+
4
+ function createDeferred<T>() {
5
+ let resolve!: (value: T) => void;
6
+ let reject!: (error: unknown) => void;
7
+ const promise = new Promise<T>((res, rej) => {
8
+ resolve = res;
9
+ reject = rej;
10
+ });
11
+ return { promise, resolve, reject } as const;
12
+ }
13
+
14
+ describe("TxQueue", () => {
15
+ it("waits for confirmation by default (single tx per source)", async () => {
16
+ const confirmations = new Map<string, ReturnType<typeof createDeferred<void>>>();
17
+ const submitOrder: string[] = [];
18
+
19
+ const q = new TxQueue({
20
+ async confirm({ txId, signal }) {
21
+ if (signal.aborted) throw new Error("aborted");
22
+ const d = confirmations.get(txId);
23
+ if (!d) throw new Error(`missing deferred for ${txId}`);
24
+ return d.promise;
25
+ },
26
+ });
27
+
28
+ confirmations.set("tx1", createDeferred<void>());
29
+ confirmations.set("tx2", createDeferred<void>());
30
+
31
+ const p1 = q.enqueue({
32
+ sourceIdentity: "A",
33
+ targetTick: 10,
34
+ async submit() {
35
+ submitOrder.push("tx1");
36
+ return { txId: "tx1", result: "r1" };
37
+ },
38
+ });
39
+
40
+ const p2 = q.enqueue({
41
+ sourceIdentity: "A",
42
+ targetTick: 11,
43
+ async submit() {
44
+ submitOrder.push("tx2");
45
+ return { txId: "tx2", result: "r2" };
46
+ },
47
+ });
48
+
49
+ await Promise.resolve();
50
+ expect(submitOrder).toEqual(["tx1"]);
51
+
52
+ mustGet(confirmations, "tx1").resolve();
53
+ const r1 = await p1;
54
+ expect(r1.status).toBe("confirmed");
55
+
56
+ mustGet(confirmations, "tx2").resolve();
57
+ const r2 = await p2;
58
+ expect(r2.status).toBe("confirmed");
59
+ expect(submitOrder).toEqual(["tx1", "tx2"]);
60
+ });
61
+
62
+ it("rejects enqueue when policy is reject", async () => {
63
+ const confirmations = new Map<string, ReturnType<typeof createDeferred<void>>>();
64
+ confirmations.set("tx1", createDeferred<void>());
65
+
66
+ const q = new TxQueue({
67
+ policy: "reject",
68
+ async confirm({ txId }) {
69
+ return mustGet(confirmations, txId).promise;
70
+ },
71
+ });
72
+
73
+ void q.enqueue({
74
+ sourceIdentity: "A",
75
+ targetTick: 10,
76
+ async submit() {
77
+ return { txId: "tx1", result: null };
78
+ },
79
+ });
80
+
81
+ await expect(
82
+ q.enqueue({
83
+ sourceIdentity: "A",
84
+ targetTick: 11,
85
+ async submit() {
86
+ return { txId: "tx2", result: null };
87
+ },
88
+ }),
89
+ ).rejects.toBeInstanceOf(TxQueueError);
90
+
91
+ mustGet(confirmations, "tx1").resolve();
92
+ });
93
+
94
+ it("replaces with higher tick when policy is replaceHigherTick", async () => {
95
+ const confirmations = new Map<string, ReturnType<typeof createDeferred<void>>>();
96
+ confirmations.set("tx1", createDeferred<void>());
97
+ confirmations.set("tx2", createDeferred<void>());
98
+
99
+ const q = new TxQueue({
100
+ policy: "replaceHigherTick",
101
+ async confirm({ txId, signal }) {
102
+ if (signal.aborted) throw new Error("aborted");
103
+ return mustGet(confirmations, txId).promise;
104
+ },
105
+ });
106
+
107
+ const p1 = q.enqueue({
108
+ sourceIdentity: "A",
109
+ targetTick: 10,
110
+ async submit() {
111
+ return { txId: "tx1", result: null };
112
+ },
113
+ });
114
+
115
+ const p2 = q.enqueue({
116
+ sourceIdentity: "A",
117
+ targetTick: 15,
118
+ async submit() {
119
+ return { txId: "tx2", result: null };
120
+ },
121
+ });
122
+
123
+ const r1 = await p1;
124
+ expect(r1.status).toBe("superseded");
125
+
126
+ await expect(
127
+ q.enqueue({
128
+ sourceIdentity: "A",
129
+ targetTick: 14,
130
+ async submit() {
131
+ return { txId: "tx3", result: null };
132
+ },
133
+ }),
134
+ ).rejects.toBeInstanceOf(TxQueueError);
135
+
136
+ mustGet(confirmations, "tx2").resolve();
137
+ const r2 = await p2;
138
+ expect(r2.status).toBe("confirmed");
139
+ });
140
+ });
141
+
142
+ function mustGet<K, V>(map: ReadonlyMap<K, V>, key: K): V {
143
+ const value = map.get(key);
144
+ if (!value) throw new Error(`Missing key: ${String(key)}`);
145
+ return value;
146
+ }
@@ -0,0 +1,214 @@
1
+ export type TxQueuePolicy = "waitForConfirm" | "reject" | "replaceHigherTick";
2
+
3
+ export type TxQueueItemStatus =
4
+ | "pending"
5
+ | "submitted"
6
+ | "confirming"
7
+ | "confirmed"
8
+ | "failed"
9
+ | "superseded";
10
+
11
+ export type TxQueueConfirmFn = (input: {
12
+ txId: string;
13
+ targetTick: bigint;
14
+ signal: AbortSignal;
15
+ }) => Promise<void>;
16
+
17
+ export type TxQueueConfig = Readonly<{
18
+ policy?: TxQueuePolicy;
19
+ confirm: TxQueueConfirmFn;
20
+ }>;
21
+
22
+ export type EnqueueTxInput<Result> = Readonly<{
23
+ sourceIdentity: string;
24
+ targetTick: bigint | number;
25
+ submit: (input: { signal: AbortSignal }) => Promise<Readonly<{ txId: string; result: Result }>>;
26
+ confirm?: TxQueueConfirmFn;
27
+ }>;
28
+
29
+ export type TxQueueItem<Result> = Readonly<{
30
+ id: string;
31
+ sourceIdentity: string;
32
+ targetTick: bigint;
33
+ createdAtMs: number;
34
+ status: TxQueueItemStatus;
35
+ txId?: string;
36
+ result?: Result;
37
+ error?: unknown;
38
+ }>;
39
+
40
+ export class TxQueueError extends Error {
41
+ override name = "TxQueueError";
42
+ }
43
+
44
+ export class TxQueue {
45
+ readonly #policy: TxQueuePolicy;
46
+ readonly #confirm: TxQueueConfirmFn;
47
+
48
+ readonly #activeBySource = new Map<string, ActiveItem<unknown>>();
49
+ readonly #itemsBySource = new Map<string, TxQueueItem<unknown>[]>();
50
+
51
+ constructor(config: TxQueueConfig) {
52
+ this.#policy = config.policy ?? "waitForConfirm";
53
+ this.#confirm = config.confirm;
54
+ }
55
+
56
+ getItems(sourceIdentity?: string): readonly TxQueueItem<unknown>[] {
57
+ if (sourceIdentity) return (this.#itemsBySource.get(sourceIdentity) ?? []).slice();
58
+ const all: TxQueueItem<unknown>[] = [];
59
+ for (const items of this.#itemsBySource.values()) all.push(...items);
60
+ return all;
61
+ }
62
+
63
+ getActive(sourceIdentity: string): TxQueueItem<unknown> | undefined {
64
+ return this.#activeBySource.get(sourceIdentity)?.item;
65
+ }
66
+
67
+ async enqueue<Result>(input: EnqueueTxInput<Result>): Promise<TxQueueItem<Result>> {
68
+ const sourceIdentity = input.sourceIdentity;
69
+ const targetTick = toBigint(input.targetTick);
70
+ const policy = this.#policy;
71
+
72
+ const existing = this.#activeBySource.get(sourceIdentity);
73
+ if (existing) {
74
+ if (policy === "waitForConfirm") {
75
+ await existing.done;
76
+ } else if (policy === "reject") {
77
+ throw new TxQueueError(
78
+ `TxQueue rejected enqueue: source ${sourceIdentity} already has an active transaction`,
79
+ );
80
+ } else if (policy === "replaceHigherTick") {
81
+ if (targetTick <= existing.item.targetTick) {
82
+ throw new TxQueueError(
83
+ `TxQueue rejected enqueue: targetTick ${targetTick} must be higher than active targetTick ${existing.item.targetTick}`,
84
+ );
85
+ }
86
+ existing.supersede();
87
+ } else {
88
+ throw new TxQueueError(`Unknown policy: ${policy}`);
89
+ }
90
+ }
91
+
92
+ const id = crypto.randomUUID();
93
+ const controller = new AbortController();
94
+ const createdAtMs = Date.now();
95
+ const deferred = createDeferred<TxQueueItem<Result>>();
96
+
97
+ const item: MutableTxQueueItem<Result> = {
98
+ id,
99
+ sourceIdentity,
100
+ targetTick,
101
+ createdAtMs,
102
+ status: "pending",
103
+ };
104
+
105
+ const active: ActiveItem<Result> = {
106
+ item,
107
+ controller,
108
+ confirm: input.confirm ?? this.#confirm,
109
+ done: deferred.promise,
110
+ supersede: () => {
111
+ if (
112
+ item.status === "confirmed" ||
113
+ item.status === "failed" ||
114
+ item.status === "superseded"
115
+ ) {
116
+ return;
117
+ }
118
+ item.status = "superseded";
119
+ controller.abort();
120
+ deferred.resolve({ ...item });
121
+ this.#activeBySource.delete(sourceIdentity);
122
+ },
123
+ };
124
+
125
+ this.#activeBySource.set(sourceIdentity, active as ActiveItem<unknown>);
126
+ this.#pushItem(sourceIdentity, item as TxQueueItem<unknown>);
127
+
128
+ void this.#run(active, input, deferred);
129
+ return deferred.promise;
130
+ }
131
+
132
+ #pushItem(sourceIdentity: string, item: TxQueueItem<unknown>) {
133
+ const list = this.#itemsBySource.get(sourceIdentity);
134
+ if (list) list.push(item);
135
+ else this.#itemsBySource.set(sourceIdentity, [item]);
136
+ }
137
+
138
+ async #run<Result>(
139
+ active: ActiveItem<Result>,
140
+ input: EnqueueTxInput<Result>,
141
+ deferred: Deferred<TxQueueItem<Result>>,
142
+ ) {
143
+ const item = active.item;
144
+ try {
145
+ const submitted = await input.submit({ signal: active.controller.signal });
146
+ if (getStatus(item) === "superseded") return;
147
+
148
+ item.status = "submitted";
149
+ item.txId = submitted.txId;
150
+ item.result = submitted.result;
151
+
152
+ item.status = "confirming";
153
+ await active.confirm({
154
+ txId: submitted.txId,
155
+ targetTick: item.targetTick,
156
+ signal: active.controller.signal,
157
+ });
158
+ if (getStatus(item) === "superseded") return;
159
+
160
+ item.status = "confirmed";
161
+ deferred.resolve({ ...item });
162
+ } catch (err) {
163
+ if (getStatus(item) === "superseded") return;
164
+ item.status = "failed";
165
+ item.error = err;
166
+ deferred.resolve({ ...item });
167
+ } finally {
168
+ const current = this.#activeBySource.get(item.sourceIdentity);
169
+ if (current?.item.id === item.id) {
170
+ this.#activeBySource.delete(item.sourceIdentity);
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ type MutableTxQueueItem<Result> = {
177
+ -readonly [K in keyof TxQueueItem<Result>]: TxQueueItem<Result>[K];
178
+ };
179
+
180
+ type ActiveItem<Result> = Readonly<{
181
+ item: MutableTxQueueItem<Result>;
182
+ controller: AbortController;
183
+ confirm: TxQueueConfirmFn;
184
+ done: Promise<TxQueueItem<Result>>;
185
+ supersede: () => void;
186
+ }>;
187
+
188
+ type Deferred<T> = Readonly<{
189
+ promise: Promise<T>;
190
+ resolve: (value: T) => void;
191
+ reject: (error: unknown) => void;
192
+ }>;
193
+
194
+ function createDeferred<T>(): Deferred<T> {
195
+ let resolve!: (value: T) => void;
196
+ let reject!: (error: unknown) => void;
197
+ const promise = new Promise<T>((res, rej) => {
198
+ resolve = res;
199
+ reject = rej;
200
+ });
201
+ return { promise, resolve, reject };
202
+ }
203
+
204
+ function toBigint(value: bigint | number): bigint {
205
+ if (typeof value === "bigint") return value;
206
+ if (!Number.isFinite(value) || !Number.isInteger(value)) {
207
+ throw new TypeError("Expected an integer");
208
+ }
209
+ return BigInt(value);
210
+ }
211
+
212
+ function getStatus(item: { status: TxQueueItemStatus }): TxQueueItemStatus {
213
+ return item.status;
214
+ }
package/src/tx/tx.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { BroadcastTransactionResult, QueryTransaction, RpcClient } from "../rpc/client.js";
2
+ import type { TxConfirmationHelpers, WaitForConfirmationInput } from "./confirm.js";
3
+ import { createTxConfirmationHelpers } from "./confirm.js";
4
+
5
+ export type TxHelpersConfig = Readonly<{
6
+ rpc: RpcClient;
7
+ confirm?: TxConfirmationHelpers;
8
+ }>;
9
+
10
+ export type TxHelpers = Readonly<{
11
+ broadcastSigned(txBytes: Uint8Array | string): Promise<BroadcastTransactionResult>;
12
+ waitForConfirmation(input: WaitForConfirmationInput): Promise<void>;
13
+ waitForConfirmedTransaction(input: WaitForConfirmationInput): Promise<QueryTransaction>;
14
+ }>;
15
+
16
+ export function createTxHelpers(config: TxHelpersConfig): TxHelpers {
17
+ const confirm =
18
+ config.confirm ??
19
+ createTxConfirmationHelpers({
20
+ rpc: config.rpc,
21
+ });
22
+
23
+ return {
24
+ async broadcastSigned(txBytes: Uint8Array | string): Promise<BroadcastTransactionResult> {
25
+ return config.rpc.live.broadcastTransaction(txBytes);
26
+ },
27
+
28
+ async waitForConfirmation(input: WaitForConfirmationInput): Promise<void> {
29
+ return confirm.waitForConfirmation(input);
30
+ },
31
+
32
+ async waitForConfirmedTransaction(input: WaitForConfirmationInput): Promise<QueryTransaction> {
33
+ return confirm.waitForConfirmedTransaction(input);
34
+ },
35
+ };
36
+ }
@@ -0,0 +1,131 @@
1
+ export type ScryptKdfParams = Readonly<{
2
+ N: number;
3
+ r: number;
4
+ p: number;
5
+ dkLen: number;
6
+ saltBase64: string;
7
+ }>;
8
+
9
+ export type Pbkdf2Hash = "SHA-256" | "SHA-384" | "SHA-512";
10
+
11
+ export type Pbkdf2KdfParams = Readonly<{
12
+ iterations: number;
13
+ hash: Pbkdf2Hash;
14
+ dkLen: number;
15
+ saltBase64: string;
16
+ }>;
17
+
18
+ export type VaultKdfParams = ScryptKdfParams | Pbkdf2KdfParams;
19
+
20
+ export type VaultKdf =
21
+ | Readonly<{
22
+ name: "scrypt";
23
+ params: ScryptKdfParams;
24
+ }>
25
+ | Readonly<{
26
+ name: "pbkdf2";
27
+ params: Pbkdf2KdfParams;
28
+ }>;
29
+
30
+ export type VaultHeader = Readonly<{
31
+ vaultVersion: number;
32
+ kdf: VaultKdf;
33
+ }>;
34
+
35
+ export type VaultEntryEncrypted = Readonly<{
36
+ nonceBase64: string;
37
+ ciphertextBase64: string;
38
+ tagBase64: string;
39
+ }>;
40
+
41
+ export type VaultEntry = Readonly<{
42
+ name: string;
43
+ identity: string;
44
+ seedIndex: number;
45
+ createdAt: string;
46
+ updatedAt: string;
47
+ encrypted: VaultEntryEncrypted;
48
+ }>;
49
+
50
+ export type VaultSummary = Readonly<{
51
+ name: string;
52
+ identity: string;
53
+ seedIndex: number;
54
+ createdAt: string;
55
+ updatedAt: string;
56
+ }>;
57
+
58
+ export type VaultExport = VaultHeader & Readonly<{ entries: readonly VaultEntry[] }>;
59
+
60
+ export class VaultError extends Error {
61
+ constructor(message: string) {
62
+ super(message);
63
+ this.name = "VaultError";
64
+ }
65
+ }
66
+
67
+ export class VaultNotFoundError extends VaultError {
68
+ constructor(path: string) {
69
+ super(`Vault file not found: ${path}`);
70
+ this.name = "VaultNotFoundError";
71
+ }
72
+ }
73
+
74
+ export class VaultInvalidPassphraseError extends VaultError {
75
+ constructor() {
76
+ super("Invalid passphrase or corrupted vault data");
77
+ this.name = "VaultInvalidPassphraseError";
78
+ }
79
+ }
80
+
81
+ export class VaultEntryNotFoundError extends VaultError {
82
+ constructor(ref: string) {
83
+ super(`Vault entry not found: ${ref}`);
84
+ this.name = "VaultEntryNotFoundError";
85
+ }
86
+ }
87
+
88
+ export class VaultEntryExistsError extends VaultError {
89
+ constructor(name: string) {
90
+ super(`Vault entry already exists: ${name}`);
91
+ this.name = "VaultEntryExistsError";
92
+ }
93
+ }
94
+
95
+ export type SeedVault = Readonly<{
96
+ path: string;
97
+ list(): readonly VaultSummary[];
98
+ getEntry(ref: string): VaultEntry;
99
+ getIdentity(ref: string): string;
100
+ signer(ref: string): Readonly<{ fromVault: string }>;
101
+ getSeed(ref: string): Promise<string>;
102
+ addSeed(
103
+ input: Readonly<{ name: string; seed: string; seedIndex?: number; overwrite?: boolean }>,
104
+ ): Promise<VaultSummary>;
105
+ remove(ref: string): Promise<void>;
106
+ rotatePassphrase(newPassphrase: string): Promise<void>;
107
+ exportEncrypted(): VaultExport;
108
+ exportJson(): string;
109
+ importEncrypted(
110
+ input: VaultExport | string,
111
+ options?: Readonly<{ mode?: "merge" | "replace"; sourcePassphrase?: string }>,
112
+ ): Promise<void>;
113
+ getSeedSource(ref: string): Promise<Readonly<{ fromSeed: string }>>;
114
+ save(): Promise<void>;
115
+ close(): Promise<void>;
116
+ }>;
117
+
118
+ export type OpenSeedVaultInput = Readonly<{
119
+ path: string;
120
+ passphrase: string;
121
+ create?: boolean;
122
+ autoSave?: boolean;
123
+ lock?: boolean;
124
+ lockTimeoutMs?: number;
125
+ kdfParams?: Readonly<{
126
+ N?: number;
127
+ r?: number;
128
+ p?: number;
129
+ dkLen?: number;
130
+ }>;
131
+ }>;