@kuiralabs/midnight-account-chain 0.1.0 → 0.2.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.
@@ -1,3 +1,3 @@
1
- import { type Session } from '../src/session.ts';
1
+ import type { Session } from '../src/session.ts';
2
2
  export type Out = (text: string) => void;
3
- export declare const runOwner: (argv: readonly string[], open?: () => Promise<Session>, out?: Out) => Promise<number>;
3
+ export declare const runOwner: (argv: readonly string[], open: () => Promise<Session>, out?: Out) => Promise<number>;
package/dist/cli/owner.js CHANGED
@@ -18,7 +18,6 @@
18
18
  import { randomBytes } from 'node:crypto';
19
19
  import { grantRequestFromLink, hexOf, sealedResponseLink } from '@kuiralabs/midnight-account';
20
20
  import { DEFAULT_LIMITS, exhaustedGrants, issueReadGrant, listRoster, revokeEveryGrant, revokeReadGrant, revokeReadGrants, termsFor } from "../src/issuance.js";
21
- import { openSession } from "../src/session.js";
22
21
  const EXIT_OK = 0;
23
22
  const EXIT_FAILED = 1;
24
23
  const EXIT_USAGE = 2;
@@ -216,7 +215,7 @@ const COMMANDS = {
216
215
  };
217
216
  // Runs one command and returns its exit code. The session is opened only
218
217
  // once the command line is known to be well formed, and always closed.
219
- export const runOwner = async (argv, open = openSession, out = console.log) => {
218
+ export const runOwner = async (argv, open, out = console.log) => {
220
219
  let flags;
221
220
  try {
222
221
  flags = parse(argv);
@@ -255,5 +254,7 @@ export const runOwner = async (argv, open = openSession, out = console.log) => {
255
254
  await session?.close();
256
255
  }
257
256
  };
257
+ // Run as a command, the genesis wallet pays: its module is loaded only then, so
258
+ // the driver, which brings its own wallet, never loads testkit-js.
258
259
  if (import.meta.main)
259
- process.exitCode = await runOwner(process.argv.slice(2));
260
+ process.exitCode = await runOwner(process.argv.slice(2), (await import("../src/seedWallets.js")).openSession);
package/dist/driver.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Arm } from '@kuiralabs/midnight-account';
2
2
  import { type Out } from './cli/owner.ts';
3
3
  import { type FeeWallet } from './src/providers.ts';
4
- export declare const DRIVER_VERSION = 3;
4
+ export declare const DRIVER_VERSION = 4;
5
5
  export declare const accountOnRecord: () => {
6
6
  readonly address: string;
7
7
  readonly arm: Arm;
package/dist/driver.js CHANGED
@@ -8,9 +8,12 @@
8
8
  // MIDNIGHT_ACCOUNT_NODE_URL the node, http(s)
9
9
  // MIDNIGHT_ACCOUNT_INDEXER_URL the indexer's GraphQL endpoint, http(s)
10
10
  // MIDNIGHT_ACCOUNT_PROOF_SERVER_URL the proof server, http(s)
11
- // MIDNIGHT_ACCOUNT_ARTEFACTS_DIR the proving artefacts, as compactc lays them out; this
12
- // package's build carries none, so it is required when
13
- // running from the published package (src/profile.ts)
11
+ // MIDNIGHT_ACCOUNT_ARTEFACTS_DIR a directory to cache the proving artefacts in, each
12
+ // fetched as it is first needed and checked against this
13
+ // package's manifest; required when running from the
14
+ // published package, which carries none (src/artefacts.ts)
15
+ // MIDNIGHT_ACCOUNT_ARTEFACTS_SOURCE optional: where they are fetched from, this build's
16
+ // release by default
14
17
  // and runs with the working directory where the private-state stores should
15
18
  // live (they are LevelDB directories under it).
16
19
  import { existsSync, renameSync, statSync } from 'node:fs';
@@ -23,7 +26,7 @@ import { OWNER_PATHS, stateFile } from "./src/profile.js";
23
26
  import { accountProviders } from "./src/providers.js";
24
27
  import { OWNER_RECORD, openSessionOver, readOwner } from "./src/session.js";
25
28
  // Bumped when what this module offers or expects changes in a way a caller must know.
26
- export const DRIVER_VERSION = 3;
29
+ export const DRIVER_VERSION = 4;
27
30
  // A margin over the deploy transaction's validity before a deploy that has
28
31
  // not landed is taken as one that never will.
29
32
  const UNLANDED_MARGIN_MS = 5 * 60 * 1000;
@@ -6,7 +6,6 @@
6
6
  // resumes where it stopped.
7
7
  import { readFileSync } from 'node:fs';
8
8
  import { findDeployedContract } from '@midnight-ntwrk/midnight-js-contracts';
9
- import { MidnightWalletProvider, createDefaultTestLogger } from '@midnight-ntwrk/testkit-js';
10
9
  import { ACCOUNT_CIRCUITS, compiledAccount } from "../src/account.js";
11
10
  import { GENESIS_SEED, LOCALNET } from "../src/localnet.js";
12
11
  import { insertMissingVerifierKeys } from "../src/maintenance.js";
@@ -26,7 +25,9 @@ export const completeDeploy = async (providers, address) => {
26
25
  throw new Error('the maintenance authority was not retired');
27
26
  console.log(`all ${ACCOUNT_CIRCUITS.length} verifier keys on chain match the build; the maintenance authority is retired (counter ${authority.counter})`);
28
27
  };
28
+ // Run as a script, the genesis wallet pays; testkit-js is loaded only then.
29
29
  if (import.meta.main) {
30
+ const { MidnightWalletProvider, createDefaultTestLogger } = await import('@midnight-ntwrk/testkit-js');
30
31
  const owner = JSON.parse(readFileSync(stateFile('owner.json'), 'utf8'));
31
32
  const wallet = await MidnightWalletProvider.build(createDefaultTestLogger(), LOCALNET, GENESIS_SEED);
32
33
  await wallet.start(true);
@@ -28,7 +28,6 @@ import { getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
28
28
  import { sampleSigningKey } from '@midnight-ntwrk/midnight-js-protocol/compact-runtime';
29
29
  import { ContractDeploy, ContractState, Intent, Transaction } from '@midnight-ntwrk/midnight-js-protocol/ledger';
30
30
  import { SucceedEntirely } from '@midnight-ntwrk/midnight-js-types';
31
- import { MidnightWalletProvider, createDefaultTestLogger } from '@midnight-ntwrk/testkit-js';
32
31
  import { ARMS, accountEncryptionKeyPair, bootCommitmentPreimage, fromHex, hexOf, k1PublicKey, k1SecretKey, sha256, v1PublicKey, v1SecretKey } from '@kuiralabs/midnight-account';
33
32
  import { EMPTY_PRIVATE_STATE, compiledAccount } from "../src/account.js";
34
33
  import { GENESIS_SEED, LOCALNET } from "../src/localnet.js";
@@ -118,7 +117,10 @@ export const deployAccount = async (providers, arm, device = freshDevice(arm)) =
118
117
  await completeDeploy(providers, address);
119
118
  return address;
120
119
  };
120
+ // Run as a script, the genesis wallet pays. testkit-js is loaded only then, so
121
+ // the driver, which imports deployAccount with its own wallet, never loads it.
121
122
  if (import.meta.main) {
123
+ const { MidnightWalletProvider, createDefaultTestLogger } = await import('@midnight-ntwrk/testkit-js');
122
124
  const armArgument = process.argv[2] ?? 'v1';
123
125
  const arm = ARMS.find((candidate) => candidate === armArgument);
124
126
  if (arm === undefined)
@@ -2,7 +2,7 @@ import { type Arm, type Bytes32, type GrantRequest, type SealedResponse, type Pa
2
2
  import { type VersionedFinalizedTransaction } from '@midnight-ntwrk/midnight-js-types';
3
3
  import { type DiscoveredCoin } from './coins.ts';
4
4
  import { type UnprovenCall } from './compose.ts';
5
- import { type Connection, type ShieldedAddress } from './connect.ts';
5
+ import type { Connection, ShieldedAddress } from './connect.ts';
6
6
  import { type Grantee } from './grantee.ts';
7
7
  import { type FeeWallet } from './providers.ts';
8
8
  export declare const AGENT_VAULT: URL;
@@ -25,35 +25,6 @@ export type AgentOptions = {
25
25
  readonly mailboxOnly?: boolean;
26
26
  };
27
27
  export declare const agentAccount: () => string;
28
- export declare const openAgent: (slot: number, options?: AgentOptions) => Promise<{
29
- grantee: Grantee;
30
- grantId: Bytes32;
31
- payContract: (coin: DiscoveredCoin, payee: string, amount: bigint) => Promise<{
32
- finalized: import("@midnight-ntwrk/midnight-js-types").FinalizedTxData;
33
- sent: SealedCoin;
34
- change: SealedCoin | undefined;
35
- }>;
36
- unprovenContractSpend: (coin: DiscoveredCoin, payee: Bytes32, amount: bigint) => Promise<UnprovenCall & {
37
- readonly change: SealedCoin | undefined;
38
- }>;
39
- scope: import("@kuiralabs/midnight-account").GrantScope;
40
- source: () => "mailbox" | "response file";
41
- spent: () => bigint;
42
- readCoins: () => Promise<readonly DiscoveredCoin[]>;
43
- spendShielded: (coin: DiscoveredCoin, merchant: ShieldedAddress, amount: bigint) => Promise<{
44
- result: import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "withdraw_shielded_with_grant_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "withdraw_shielded_with_grant_k256">;
45
- change: SealedCoin | undefined;
46
- }>;
47
- proveShieldedSpend: (coin: DiscoveredCoin, merchant: ShieldedAddress, amount: bigint) => Promise<ProvedSpend>;
48
- submitProved: ({ tx, amount }: ProvedSpend) => Promise<import("@midnight-ntwrk/midnight-js-types").VersionedFinalizedTxData>;
49
- address: string;
50
- providers: import("./providers.ts").AccountProviders;
51
- account: import("@midnight-ntwrk/midnight-js-contracts").FoundContract<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>>;
52
- self: Bytes32;
53
- readLedger: () => Promise<import("../managed/Account/contract/index.js").Ledger>;
54
- close: () => Promise<void>;
55
- wallet: FeeWallet;
56
- }>;
57
28
  export declare const openAgentOver: (connection: Connection & {
58
29
  readonly wallet: FeeWallet;
59
30
  }, slot: number, { mailboxOnly }?: AgentOptions) => Promise<{
package/dist/src/agent.js CHANGED
@@ -14,10 +14,8 @@ import { compiledAccount } from "./account.js";
14
14
  import { grantFromMessages, readMailbox } from "./mailbox.js";
15
15
  import { changeCoinOf, readAccountZswap, sentCoinOf, walkInbox } from "./coins.js";
16
16
  import { payeeProviders, submitComposed, withClaim } from "./compose.js";
17
- import { connect } from "./connect.js";
18
17
  import { freshGrantee, granteeFromVault } from "./grantee.js";
19
- import { AGENT_FEE_SEED } from "./localnet.js";
20
- import { AGENT_PATHS, PAYEE_PATHS, ensureStateDir, stateFile } from "./profile.js";
18
+ import { PAYEE_PATHS, ensureStateDir, stateFile } from "./profile.js";
21
19
  import { PRIVATE_STATE_ID } from "./providers.js";
22
20
  export const AGENT_VAULT = stateFile('agent.json');
23
21
  export const GRANT_REQUEST = stateFile('grant-request.json');
@@ -67,8 +65,6 @@ export const requestGrant = (account, clientId, slot, payTo, payToKind, arm) =>
67
65
  };
68
66
  // The account the agent's vault names, which its connection must be to.
69
67
  export const agentAccount = () => readVault().account;
70
- // The agent's session over its own fee wallet.
71
- export const openAgent = async (slot, options = {}) => openAgentOver(await connect(agentAccount(), AGENT_FEE_SEED, AGENT_PATHS), slot, options);
72
68
  // The agent's session over a connection the caller made to the vault's
73
69
  // account, with the wallet that pays its fees: the grantee key, the read
74
70
  // secret and the running spend are the vault's, whoever pays.
@@ -0,0 +1,13 @@
1
+ import { ZKConfigProvider } from '@midnight-ntwrk/midnight-js-types';
2
+ export declare const ACCOUNT_BUILD_ID: string;
3
+ export declare const BUILD_RELEASE: string;
4
+ export type ArtefactCache = {
5
+ readonly directory: string;
6
+ readonly ensure: (circuits: readonly string[]) => Promise<void>;
7
+ readonly provider: <K extends string>() => ZKConfigProvider<K>;
8
+ };
9
+ export type ArtefactCacheOptions = {
10
+ readonly source?: string;
11
+ readonly stallMs?: number;
12
+ };
13
+ export declare const artefactCache: (root: string, { source, stallMs }?: ArtefactCacheOptions) => ArtefactCache;
@@ -0,0 +1,257 @@
1
+ // The account's proving artefacts for a client of this package, which ships
2
+ // none (a proving key is up to a quarter of a gigabyte): fetched one circuit's
3
+ // file at a time, when midnight-js first asks for it, into a cache kept per
4
+ // build, and kept only when it matches this package's manifest.
5
+ //
6
+ // The trust chain: the manifest this package carries (compactc's, beside the
7
+ // compiled contract) lists a SHA-256 and a size for every key and ZKIR file. A
8
+ // fetched file is kept only if it matches, so a key is as trusted as this
9
+ // package, wherever it came from. The cache is laid out as compactc's output,
10
+ // manifest included, so midnight-js's own provider checks every file again as
11
+ // it reads it. A wrong proving key could not spend anything anyway: the node
12
+ // verifies every proof against the verifier key the account holds on chain.
13
+ //
14
+ // Where from: the build's release in kuiralabs/midnight-account-artifacts by
15
+ // default, or a source the caller names:
16
+ // - an http(s) URL laid out as a release, every file under its own name
17
+ // (<source>/<circuit>.prover, .verifier, .bzkir). Its query string is kept
18
+ // for every file, so a URL signed for the whole prefix works; one signed for
19
+ // a single object cannot.
20
+ // - an absolute directory (or file: URL) laid out as compactc's output
21
+ // (<source>/keys/<circuit>.prover, ...), as a local build of the contract is.
22
+ import { createHash, randomBytes } from 'node:crypto';
23
+ import { createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
24
+ import { copyFile } from 'node:fs/promises';
25
+ import { dirname, isAbsolute, join } from 'node:path';
26
+ import { pipeline } from 'node:stream/promises';
27
+ import { fileURLToPath } from 'node:url';
28
+ import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
29
+ import { ZKConfigProvider } from '@midnight-ntwrk/midnight-js-types';
30
+ import { ACCOUNT_BUILD } from "./account.js";
31
+ // Where each build's release is: its tag is `build-` and the build's id.
32
+ const ARTEFACT_RELEASES = 'https://github.com/kuiralabs/midnight-account-artifacts/releases/download/';
33
+ // A fetch is abandoned when no bytes arrive for this long.
34
+ const FETCH_STALL_MS = 60_000;
35
+ // A build's id: this many hex digits of its manifest's SHA-256.
36
+ const BUILD_ID_HEX_DIGITS = 16;
37
+ const KEYS = 'keys';
38
+ const ZKIR_DIRECTORY = 'zkir';
39
+ const COMPILER = 'compiler';
40
+ const MANIFEST = 'contract-manifest.json';
41
+ const CONTRACT_INFO = 'contract-info.json';
42
+ const ARTEFACT_DIRECTORIES = [KEYS, ZKIR_DIRECTORY];
43
+ const manifestBytes = () => readFileSync(join(ACCOUNT_BUILD, COMPILER, MANIFEST));
44
+ // This package's build of the account, by the hash of its manifest.
45
+ export const ACCOUNT_BUILD_ID = createHash('sha256').update(manifestBytes()).digest('hex').slice(0, BUILD_ID_HEX_DIGITS);
46
+ // This build's release, the source when none is named.
47
+ export const BUILD_RELEASE = `${ARTEFACT_RELEASES}build-${ACCOUNT_BUILD_ID}/`;
48
+ const MANIFEST_ENTRIES = JSON.parse(manifestBytes().toString('utf8'));
49
+ const entryOf = (directory, name) => {
50
+ const entry = MANIFEST_ENTRIES[directory][name];
51
+ if (entry?.type !== 'file')
52
+ throw new Error(`this build of the account has no ${directory}/${name}`);
53
+ return entry;
54
+ };
55
+ const proverKeyFile = (circuit) => [KEYS, `${circuit}.prover`];
56
+ const verifierKeyFile = (circuit) => [KEYS, `${circuit}.verifier`];
57
+ const zkirFile = (circuit) => [ZKIR_DIRECTORY, `${circuit}.bzkir`];
58
+ const hasCode = (error, code) => error instanceof Error && 'code' in error && error.code === code;
59
+ // A temporary file beside `path`, named for the process writing it, so a run
60
+ // can tell a dead process's leftovers from a live one's.
61
+ const TEMPORARY_RANDOM_BYTES = 6;
62
+ const temporaryPath = (path) => `${path}.${process.pid}.${randomBytes(TEMPORARY_RANDOM_BYTES).toString('hex')}.tmp`;
63
+ const TEMPORARY_NAME = new RegExp(`\\.(\\d+)\\.[0-9a-f]{${TEMPORARY_RANDOM_BYTES * 2}}\\.tmp$`);
64
+ const isRunning = (pid) => {
65
+ try {
66
+ process.kill(pid, 0);
67
+ return true;
68
+ }
69
+ catch (cause) {
70
+ return hasCode(cause, 'EPERM');
71
+ }
72
+ };
73
+ // Removes a file another process may be removing at the same moment.
74
+ const removeIfThere = (path) => {
75
+ try {
76
+ unlinkSync(path);
77
+ }
78
+ catch (cause) {
79
+ if (!hasCode(cause, 'ENOENT'))
80
+ throw cause;
81
+ }
82
+ };
83
+ const sizeOf = (path) => {
84
+ try {
85
+ return statSync(path).size;
86
+ }
87
+ catch (cause) {
88
+ if (hasCode(cause, 'ENOENT'))
89
+ return undefined;
90
+ throw cause;
91
+ }
92
+ };
93
+ const sha256Of = async (path) => {
94
+ const hash = createHash('sha256');
95
+ for await (const chunk of createReadStream(path))
96
+ hash.update(chunk);
97
+ return hash.digest('hex');
98
+ };
99
+ const matches = async (path, entry) => sizeOf(path) === entry.size && (await sha256Of(path)) === entry.hash;
100
+ const sourceOf = (source) => {
101
+ if (/^https?:\/\//.test(source))
102
+ return { kind: 'url', base: new URL(source) };
103
+ const path = source.startsWith('file://') ? fileURLToPath(source) : source;
104
+ if (!isAbsolute(path))
105
+ throw new Error(`the source of the proving artefacts must be an http(s) URL or an absolute directory, not ${JSON.stringify(source)}`);
106
+ return { kind: 'directory', path };
107
+ };
108
+ // `name` under the base URL's path, keeping its query string.
109
+ const urlOf = (base, name) => {
110
+ const url = new URL(base);
111
+ url.pathname = `${url.pathname.replace(/\/?$/, '/')}${name}`;
112
+ return url;
113
+ };
114
+ // Streams `url` into `temporary`, refusing more bytes than the manifest's size
115
+ // and giving up when the server stalls for `stallMs`. The request is aborted
116
+ // once done either way, so a refused response's body never holds a socket open.
117
+ const download = async (url, temporary, entry, stallMs) => {
118
+ const controller = new AbortController();
119
+ const stall = setTimeout(() => controller.abort(new Error(`no data for ${stallMs / 1000} s`)), stallMs);
120
+ try {
121
+ const response = await fetch(url, { signal: controller.signal });
122
+ const body = response.body;
123
+ if (!response.ok || body === null)
124
+ throw new Error(`HTTP ${response.status}`);
125
+ let received = 0;
126
+ await pipeline(async function* () {
127
+ for await (const chunk of body) {
128
+ received += chunk.length;
129
+ if (received > entry.size)
130
+ throw new Error(`more than the manifest's ${entry.size} bytes`);
131
+ stall.refresh();
132
+ yield chunk;
133
+ }
134
+ }, createWriteStream(temporary));
135
+ }
136
+ finally {
137
+ clearTimeout(stall);
138
+ controller.abort();
139
+ }
140
+ };
141
+ // The cache for this build under `root` (its own directory there, named by the
142
+ // build's id, so two builds' files never mix): the manifest is placed in it,
143
+ // and what a killed fetch left is removed.
144
+ export const artefactCache = (root, { source = BUILD_RELEASE, stallMs = FETCH_STALL_MS } = {}) => {
145
+ if (!isAbsolute(root))
146
+ throw new Error(`the proving artefacts' cache must be an absolute path, not ${JSON.stringify(root)}`);
147
+ const from = sourceOf(source);
148
+ const directory = join(root, ACCOUNT_BUILD_ID);
149
+ placeCompilerFiles(directory);
150
+ removeAbandoned(directory);
151
+ // One fetch per file at a time: a second ask waits for the first.
152
+ const inFlight = new Map();
153
+ const ensureFile = ([folder, name]) => {
154
+ const relative = `${folder}/${name}`;
155
+ const running = inFlight.get(relative);
156
+ if (running !== undefined)
157
+ return running;
158
+ const fetching = (async () => {
159
+ const target = join(directory, folder, name);
160
+ const entry = entryOf(folder, name);
161
+ if (!(await matches(target, entry)))
162
+ await fetchInto(from, folder, name, target, entry, stallMs);
163
+ })().finally(() => inFlight.delete(relative));
164
+ inFlight.set(relative, fetching);
165
+ return fetching;
166
+ };
167
+ const ensure = async (circuits) => {
168
+ for (const circuit of circuits)
169
+ for (const file of [proverKeyFile(circuit), verifierKeyFile(circuit), zkirFile(circuit)])
170
+ await ensureFile(file);
171
+ };
172
+ const provider = () => new FetchingZkConfigProvider(new NodeZkConfigProvider(directory), ensureFile);
173
+ return { directory, ensure, provider };
174
+ };
175
+ // midnight-js's provider over the cache, each file fetched before it is read.
176
+ class FetchingZkConfigProvider extends ZKConfigProvider {
177
+ cached;
178
+ ensureFile;
179
+ constructor(cached, ensureFile) {
180
+ super();
181
+ this.cached = cached;
182
+ this.ensureFile = ensureFile;
183
+ }
184
+ getArtifactRuntimeVersion() {
185
+ return this.cached.getArtifactRuntimeVersion();
186
+ }
187
+ async getProverKey(circuitId) {
188
+ await this.ensureFile(proverKeyFile(circuitId));
189
+ return this.cached.getProverKey(circuitId);
190
+ }
191
+ async getVerifierKey(circuitId) {
192
+ await this.ensureFile(verifierKeyFile(circuitId));
193
+ return this.cached.getVerifierKey(circuitId);
194
+ }
195
+ async getZKIR(circuitId) {
196
+ await this.ensureFile(zkirFile(circuitId));
197
+ return this.cached.getZKIR(circuitId);
198
+ }
199
+ }
200
+ // Writes this build's compiler files into the cache, atomically, unless the
201
+ // same ones are there; a cache holding another build's is refused.
202
+ const placeCompilerFiles = (directory) => {
203
+ mkdirSync(join(directory, COMPILER), { recursive: true });
204
+ for (const name of [MANIFEST, CONTRACT_INFO]) {
205
+ const ours = readFileSync(join(ACCOUNT_BUILD, COMPILER, name));
206
+ const target = join(directory, COMPILER, name);
207
+ if (existsSync(target)) {
208
+ if (readFileSync(target).equals(ours))
209
+ continue;
210
+ throw new Error(`${target} is another build's: the cache ${directory} is not this build's`);
211
+ }
212
+ const temporary = temporaryPath(target);
213
+ writeFileSync(temporary, ours);
214
+ renameSync(temporary, target);
215
+ }
216
+ };
217
+ // Removes the temporary files of fetches whose process is gone (killed
218
+ // mid-download); a running process's own are left alone.
219
+ const removeAbandoned = (directory) => {
220
+ for (const folder of ARTEFACT_DIRECTORIES) {
221
+ const path = join(directory, folder);
222
+ if (!existsSync(path))
223
+ continue;
224
+ for (const name of readdirSync(path)) {
225
+ const pid = TEMPORARY_NAME.exec(name)?.[1];
226
+ if (pid !== undefined && !isRunning(Number(pid)))
227
+ removeIfThere(join(path, name));
228
+ }
229
+ }
230
+ };
231
+ // Fetches one file to a temporary path beside `target`, checks it against its
232
+ // manifest entry, and only then moves it into place. A URL serves it under its
233
+ // own name, a directory under its compactc directory.
234
+ const fetchInto = async (source, folder, name, target, entry, stallMs) => {
235
+ mkdirSync(dirname(target), { recursive: true });
236
+ const temporary = temporaryPath(target);
237
+ const from = source.kind === 'url' ? urlOf(source.base, name).href : join(source.path, folder, name);
238
+ try {
239
+ if (source.kind === 'url') {
240
+ await download(new URL(from), temporary, entry, stallMs);
241
+ }
242
+ else {
243
+ if (!existsSync(from))
244
+ throw new Error('not found');
245
+ await copyFile(from, temporary);
246
+ }
247
+ if (!(await matches(temporary, entry)))
248
+ throw new Error("not this build's file: refused");
249
+ renameSync(temporary, target);
250
+ }
251
+ catch (cause) {
252
+ throw new Error(`${folder}/${name} from ${from}: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
253
+ }
254
+ finally {
255
+ removeIfThere(temporary);
256
+ }
257
+ };
@@ -1,5 +1,4 @@
1
- import { type EncPublicKey } from '@midnight-ntwrk/midnight-js-protocol/ledger';
2
- import { MidnightWalletProvider } from '@midnight-ntwrk/testkit-js';
1
+ import type { EncPublicKey } from '@midnight-ntwrk/midnight-js-protocol/ledger';
3
2
  import { type Bytes32 } from '@kuiralabs/midnight-account';
4
3
  import { type Ledger } from '../managed/Account/contract/index.js';
5
4
  import { type FeeWallet, type ProviderPaths } from './providers.ts';
@@ -12,18 +11,8 @@ export declare const connectOver: (address: string, wallet: FeeWallet, paths: Pr
12
11
  readLedger: () => Promise<Ledger>;
13
12
  close: () => Promise<void>;
14
13
  }>;
15
- export declare const connect: (address: string, walletSeed: string, paths: ProviderPaths) => Promise<{
16
- wallet: MidnightWalletProvider;
17
- address: string;
18
- providers: import("./providers.ts").AccountProviders;
19
- account: import("@midnight-ntwrk/midnight-js-contracts").FoundContract<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>>;
20
- self: Bytes32;
21
- readLedger: () => Promise<Ledger>;
22
- close: () => Promise<void>;
23
- }>;
24
14
  export declare const timed: <T>(label: string, run: () => Promise<T>) => Promise<T>;
25
15
  export type ShieldedAddress = {
26
16
  readonly coinPublicKey: Bytes32;
27
17
  readonly encryptionPublicKey: EncPublicKey;
28
18
  };
29
- export declare const shieldedAddressOf: (walletSeed: string) => ShieldedAddress;
@@ -2,12 +2,9 @@
2
2
  // providers and private-state store, the found contract, and reads of the
3
3
  // account's public state.
4
4
  import { findDeployedContract } from '@midnight-ntwrk/midnight-js-contracts';
5
- import { ZswapSecretKeys } from '@midnight-ntwrk/midnight-js-protocol/ledger';
6
- import { MidnightWalletProvider, createDefaultTestLogger, getShieldedSeed } from '@midnight-ntwrk/testkit-js';
7
5
  import { fromHex } from '@kuiralabs/midnight-account';
8
6
  import { ledger } from '../managed/Account/contract/index.js';
9
7
  import { EMPTY_PRIVATE_STATE, compiledAccount } from "./account.js";
10
- import { LOCALNET } from "./localnet.js";
11
8
  import { PRIVATE_STATE_ID, accountProviders } from "./providers.js";
12
9
  // A connection over a wallet the caller brings and will close: the account's
13
10
  // calls need only that it balances and submits (FeeWallet).
@@ -31,13 +28,6 @@ export const connectOver = async (address, wallet, paths, close) => {
31
28
  };
32
29
  return { address, providers, account, self, readLedger, close };
33
30
  };
34
- // A connection over the testkit wallet of a seed, which it starts and stops.
35
- // The scripts that move the wallet's own funds reach it as `wallet`.
36
- export const connect = async (address, walletSeed, paths) => {
37
- const wallet = await MidnightWalletProvider.build(createDefaultTestLogger(), LOCALNET, walletSeed);
38
- await wallet.start(true);
39
- return { ...(await connectOver(address, wallet, paths, () => wallet.stop())), wallet };
40
- };
41
31
  // Times one on-chain step and reports it.
42
32
  export const timed = async (label, run) => {
43
33
  const startedAt = Date.now();
@@ -45,9 +35,3 @@ export const timed = async (label, run) => {
45
35
  console.log(`${label}: ${((Date.now() - startedAt) / 1000).toFixed(1)} s`);
46
36
  return result;
47
37
  };
48
- // A wallet seed's shielded address, derived as the wallet derives it (the
49
- // Zswap role of account 0, key 0), without syncing that wallet.
50
- export const shieldedAddressOf = (walletSeed) => {
51
- const keys = ZswapSecretKeys.fromSeed(getShieldedSeed(walletSeed));
52
- return { coinPublicKey: fromHex(keys.coinPublicKey, 32), encryptionPublicKey: keys.encryptionPublicKey };
53
- };
@@ -1,6 +1,16 @@
1
- import type { EnvironmentConfiguration } from '@midnight-ntwrk/testkit-js';
1
+ import type { NetworkId } from '@midnightntwrk/wallet-sdk';
2
2
  export declare const NETWORK_ID = "undeployed";
3
3
  export declare const GENESIS_SEED = "0000000000000000000000000000000000000000000000000000000000000001";
4
4
  export declare const AGENT_FEE_SEED = "0000000000000000000000000000000000000000000000000000000000000002";
5
5
  export declare const MERCHANT_SEED = "0000000000000000000000000000000000000000000000000000000000000003";
6
- export declare const LOCALNET: EnvironmentConfiguration;
6
+ export type Network = {
7
+ readonly networkId: string;
8
+ readonly walletNetworkId: NetworkId.NetworkId;
9
+ readonly node: string;
10
+ readonly nodeWS: string;
11
+ readonly indexer: string;
12
+ readonly indexerWS: string;
13
+ readonly proofServer: string;
14
+ readonly faucet: string | undefined;
15
+ };
16
+ export declare const LOCALNET: Network;
@@ -1,9 +1,10 @@
1
+ import { type ArtefactCache } from './artefacts.ts';
1
2
  import type { ProviderPaths } from './providers.ts';
2
3
  export declare const PROFILE: string | undefined;
3
4
  export declare const STATE_DIR: URL;
4
5
  export declare const stateFile: (name: string) => URL;
5
6
  export declare const ensureStateDir: () => void;
6
- export declare const ARTEFACTS: string;
7
+ export declare const ARTEFACTS: string | ArtefactCache;
7
8
  export declare const OWNER_PATHS: ProviderPaths;
8
9
  export declare const AGENT_PATHS: ProviderPaths;
9
10
  export declare const PAYEE_PATHS: ProviderPaths;
@@ -10,13 +10,16 @@
10
10
  // profiles then live under it the same way.
11
11
  //
12
12
  // The proving artefacts are the repo's own build. The package as published
13
- // carries none (they are hundreds of megabytes), so a caller running from it
14
- // names a directory holding them with MIDNIGHT_ACCOUNT_ARTEFACTS_DIR, laid out
15
- // as compactc's output; its manifest must be this package's own.
16
- import { existsSync, mkdirSync, readFileSync } from 'node:fs';
13
+ // carries none (they are gigabytes), so a caller running from it names a
14
+ // directory to cache them in with MIDNIGHT_ACCOUNT_ARTEFACTS_DIR: each file is
15
+ // fetched as it is first needed, from this build's release or from
16
+ // MIDNIGHT_ACCOUNT_ARTEFACTS_SOURCE, and kept only if it matches this
17
+ // package's manifest (artefacts.ts).
18
+ import { existsSync, mkdirSync } from 'node:fs';
17
19
  import { isAbsolute, join } from 'node:path';
18
20
  import { pathToFileURL } from 'node:url';
19
21
  import { ACCOUNT_BUILD } from "./account.js";
22
+ import { artefactCache } from "./artefacts.js";
20
23
  // A profile name is a plain directory name, so it can neither escape
21
24
  // .state/ nor alias the default profile's directory.
22
25
  const PROFILE_NAME = /^[a-z0-9][a-z0-9-]{0,31}$/;
@@ -45,26 +48,22 @@ export const ensureStateDir = () => {
45
48
  };
46
49
  // A private-state store name for this profile.
47
50
  const profileStore = (base) => (PROFILE === undefined ? base : `${base}-${PROFILE}`);
48
- const MANIFEST = join('compiler', 'contract-manifest.json');
49
51
  const KEYS = 'keys';
50
- // The proving artefacts every party proves with: the directory the caller
51
- // names, if its build is this package's, else this package's own build, if it
52
- // holds them.
52
+ // The proving artefacts every party proves with: a cache in the directory the
53
+ // caller names, else this package's own build, if it holds them.
53
54
  const artefactsFromEnvironment = () => {
54
55
  const directory = process.env.MIDNIGHT_ACCOUNT_ARTEFACTS_DIR;
56
+ const source = process.env.MIDNIGHT_ACCOUNT_ARTEFACTS_SOURCE;
55
57
  if (directory === undefined) {
58
+ if (source !== undefined)
59
+ throw new Error('MIDNIGHT_ACCOUNT_ARTEFACTS_SOURCE needs MIDNIGHT_ACCOUNT_ARTEFACTS_DIR: name the directory to cache the proving artefacts in');
56
60
  if (!existsSync(join(ACCOUNT_BUILD, KEYS)))
57
- throw new Error('this build of the account carries no proving keys: name a directory holding them with MIDNIGHT_ACCOUNT_ARTEFACTS_DIR');
61
+ throw new Error('this build of the account carries no proving keys: name a directory to cache them in with MIDNIGHT_ACCOUNT_ARTEFACTS_DIR');
58
62
  return ACCOUNT_BUILD;
59
63
  }
60
64
  if (!isAbsolute(directory))
61
65
  throw new Error(`MIDNIGHT_ACCOUNT_ARTEFACTS_DIR must be an absolute path: ${JSON.stringify(directory)}`);
62
- const manifest = join(directory, MANIFEST);
63
- if (!existsSync(manifest))
64
- throw new Error(`MIDNIGHT_ACCOUNT_ARTEFACTS_DIR holds no ${MANIFEST}: ${directory}`);
65
- if (!readFileSync(manifest).equals(readFileSync(join(ACCOUNT_BUILD, MANIFEST))))
66
- throw new Error(`MIDNIGHT_ACCOUNT_ARTEFACTS_DIR holds another build of the account than this package's: ${directory}`);
67
- return directory;
66
+ return artefactCache(directory, source === undefined ? {} : { source });
68
67
  };
69
68
  export const ARTEFACTS = artefactsFromEnvironment();
70
69
  // Each party's providers for this profile.
@@ -1,10 +1,11 @@
1
1
  import type { MidnightProvider, MidnightProviders, WalletProvider } from '@midnight-ntwrk/midnight-js-types';
2
2
  import type { AccountCircuitId, AccountPrivateState } from './account.ts';
3
+ import type { ArtefactCache } from './artefacts.ts';
3
4
  export declare const PRIVATE_STATE_ID = "account";
4
5
  export type AccountProviders = MidnightProviders<AccountCircuitId, string, AccountPrivateState>;
5
6
  export type ProviderPaths = {
6
7
  readonly store: string;
7
- readonly artefacts: string;
8
+ readonly artefacts: string | ArtefactCache;
8
9
  };
9
10
  export type FeeWallet = WalletProvider & MidnightProvider;
10
11
  export declare const accountProviders: (wallet: FeeWallet, { store, artefacts }: ProviderPaths) => AccountProviders;
@@ -16,7 +16,7 @@ const STORE_PASSWORD = 'Answer to the Ultimate Question of Life, the Universe, a
16
16
  // before this function existed are still found.
17
17
  const storeAccountOf = (wallet) => Buffer.from(wallet.getCoinPublicKey()).toString('hex');
18
18
  export const accountProviders = (wallet, { store, artefacts }) => {
19
- const zkConfigProvider = new NodeZkConfigProvider(artefacts);
19
+ const zkConfigProvider = typeof artefacts === 'string' ? new NodeZkConfigProvider(artefacts) : artefacts.provider();
20
20
  return {
21
21
  privateStateProvider: levelPrivateStateProvider({
22
22
  privateStateStoreName: store,
@@ -0,0 +1,75 @@
1
+ import { MidnightWalletProvider } from '@midnight-ntwrk/testkit-js';
2
+ import { type AgentOptions } from './agent.ts';
3
+ import { type ShieldedAddress } from './connect.ts';
4
+ import type { ProviderPaths } from './providers.ts';
5
+ export declare const connect: (address: string, walletSeed: string, paths: ProviderPaths) => Promise<{
6
+ wallet: MidnightWalletProvider;
7
+ address: string;
8
+ providers: import("./providers.ts").AccountProviders;
9
+ account: import("@midnight-ntwrk/midnight-js-contracts").FoundContract<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>>;
10
+ self: import("@kuiralabs/midnight-account").Bytes32;
11
+ readLedger: () => Promise<import("../managed/Account/contract/index.js").Ledger>;
12
+ close: () => Promise<void>;
13
+ }>;
14
+ export declare const shieldedAddressOf: (walletSeed: string) => ShieldedAddress;
15
+ export declare const openSession: () => Promise<{
16
+ wallet: MidnightWalletProvider;
17
+ address: string;
18
+ providers: import("./providers.ts").AccountProviders;
19
+ account: import("@midnight-ntwrk/midnight-js-contracts").FoundContract<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>>;
20
+ self: import("@kuiralabs/midnight-account").Bytes32;
21
+ readLedger: () => Promise<import("../managed/Account/contract/index.js").Ledger>;
22
+ close: () => Promise<void>;
23
+ } & {
24
+ device: import("./device.ts").OwnerDevice;
25
+ bootSalt: Uint8Array<ArrayBufferLike>;
26
+ encSecret: () => import("@kuiralabs/midnight-account").Bytes32;
27
+ calls: {
28
+ activate: (bootSalt: import("@kuiralabs/midnight-account").Bytes32) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "activate_initial_device_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "activate_initial_device_with_k256">>;
29
+ withdrawUnshielded: (color: import("@kuiralabs/midnight-account").Bytes32, amount: bigint, recipient: import("@kuiralabs/midnight-account").Bytes32) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "withdraw_unshielded_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "withdraw_unshielded_with_k256">>;
30
+ withdrawShielded: (recipient: import("@kuiralabs/midnight-account").Bytes32, amount: bigint, coin: import("@kuiralabs/midnight-account").HeldCoin) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "withdraw_shielded_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "withdraw_shielded_with_k256">>;
31
+ unprovenWithdrawShieldedToContract: (recipient: import("@kuiralabs/midnight-account").Bytes32, amount: bigint, coin: import("@kuiralabs/midnight-account").HeldCoin) => Promise<import("./compose.ts").UnprovenCall & {
32
+ readonly landed: () => Promise<void>;
33
+ }>;
34
+ appendInbox: (entry: Uint8Array) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "append_inbox_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "append_inbox_with_k256">>;
35
+ rotateEncKey: (newKey: import("@kuiralabs/midnight-account").Bytes32) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "rotate_enc_key_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "rotate_enc_key_with_k256">>;
36
+ addDevice: (newPk: import("@kuiralabs/midnight-account").Point) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "add_device_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "add_device_with_k256">>;
37
+ issueGrant: (grantId: import("@kuiralabs/midnight-account").Bytes32, scope: import("@kuiralabs/midnight-account").GrantScope) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "issue_grant_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "issue_grant_with_k256">>;
38
+ revokeGrant: (grantId: import("@kuiralabs/midnight-account").Bytes32) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "revoke_grant_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "revoke_grant_with_k256">>;
39
+ revokeAllGrants: () => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "revoke_all_grants_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "revoke_all_grants_with_k256">>;
40
+ };
41
+ expectChange: (change: import("@kuiralabs/midnight-account").SealedCoin) => void;
42
+ settleChanges: () => Promise<number>;
43
+ refreshCoins: () => Promise<readonly import("./coins.ts").DiscoveredCoin[]>;
44
+ reencryptHoldings: () => Promise<number>;
45
+ rotateEncKey: () => Promise<number>;
46
+ }>;
47
+ export declare const openAgent: (slot: number, options?: AgentOptions) => Promise<{
48
+ grantee: import("./grantee.ts").Grantee;
49
+ grantId: import("@kuiralabs/midnight-account").Bytes32;
50
+ payContract: (coin: import("./coins.ts").DiscoveredCoin, payee: string, amount: bigint) => Promise<{
51
+ finalized: import("@midnight-ntwrk/midnight-js-types").FinalizedTxData;
52
+ sent: import("@kuiralabs/midnight-account").SealedCoin;
53
+ change: import("@kuiralabs/midnight-account").SealedCoin | undefined;
54
+ }>;
55
+ unprovenContractSpend: (coin: import("./coins.ts").DiscoveredCoin, payee: import("@kuiralabs/midnight-account").Bytes32, amount: bigint) => Promise<import("./compose.ts").UnprovenCall & {
56
+ readonly change: import("@kuiralabs/midnight-account").SealedCoin | undefined;
57
+ }>;
58
+ scope: import("@kuiralabs/midnight-account").GrantScope;
59
+ source: () => "mailbox" | "response file";
60
+ spent: () => bigint;
61
+ readCoins: () => Promise<readonly import("./coins.ts").DiscoveredCoin[]>;
62
+ spendShielded: (coin: import("./coins.ts").DiscoveredCoin, merchant: ShieldedAddress, amount: bigint) => Promise<{
63
+ result: import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "withdraw_shielded_with_grant_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "withdraw_shielded_with_grant_k256">;
64
+ change: import("@kuiralabs/midnight-account").SealedCoin | undefined;
65
+ }>;
66
+ proveShieldedSpend: (coin: import("./coins.ts").DiscoveredCoin, merchant: ShieldedAddress, amount: bigint) => Promise<import("./agent.ts").ProvedSpend>;
67
+ submitProved: ({ tx, amount }: import("./agent.ts").ProvedSpend) => Promise<import("@midnight-ntwrk/midnight-js-types").VersionedFinalizedTxData>;
68
+ address: string;
69
+ providers: import("./providers.ts").AccountProviders;
70
+ account: import("@midnight-ntwrk/midnight-js-contracts").FoundContract<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>>;
71
+ self: import("@kuiralabs/midnight-account").Bytes32;
72
+ readLedger: () => Promise<import("../managed/Account/contract/index.js").Ledger>;
73
+ close: () => Promise<void>;
74
+ wallet: import("./providers.ts").FeeWallet;
75
+ }>;
@@ -0,0 +1,31 @@
1
+ // The repository's own parties, each paid by the testkit wallet of a localnet
2
+ // seed: the owner by the genesis wallet, the agent by its fee wallet. Only the
3
+ // repository's scripts and command line use them, so they are not built, and
4
+ // nothing a client loads reaches testkit-js. A client brings its own wallet
5
+ // instead (connectOver, openSessionOver, openAgentOver).
6
+ import { ZswapSecretKeys } from '@midnight-ntwrk/midnight-js-protocol/ledger';
7
+ import { MidnightWalletProvider, createDefaultTestLogger, getShieldedSeed } from '@midnight-ntwrk/testkit-js';
8
+ import { fromHex } from '@kuiralabs/midnight-account';
9
+ import { agentAccount, openAgentOver } from "./agent.js";
10
+ import { connectOver } from "./connect.js";
11
+ import { AGENT_FEE_SEED, GENESIS_SEED, LOCALNET } from "./localnet.js";
12
+ import { AGENT_PATHS, OWNER_PATHS } from "./profile.js";
13
+ import { openSessionOver, readOwner } from "./session.js";
14
+ // A connection over the testkit wallet of a seed, which it starts and stops.
15
+ // The scripts that move the wallet's own funds reach it as `wallet`.
16
+ export const connect = async (address, walletSeed, paths) => {
17
+ const wallet = await MidnightWalletProvider.build(createDefaultTestLogger(), LOCALNET, walletSeed);
18
+ await wallet.start(true);
19
+ return { ...(await connectOver(address, wallet, paths, () => wallet.stop())), wallet };
20
+ };
21
+ // A wallet seed's shielded address, derived as the wallet derives it (the
22
+ // Zswap role of account 0, key 0), without syncing that wallet.
23
+ export const shieldedAddressOf = (walletSeed) => {
24
+ const keys = ZswapSecretKeys.fromSeed(getShieldedSeed(walletSeed));
25
+ return { coinPublicKey: fromHex(keys.coinPublicKey, 32), encryptionPublicKey: keys.encryptionPublicKey };
26
+ };
27
+ // The owner's session paid by the genesis wallet, which the scripts that move
28
+ // that wallet's own funds reach as `wallet`.
29
+ export const openSession = async () => openSessionOver(await connect(readOwner().contractAddress, GENESIS_SEED, OWNER_PATHS));
30
+ // The agent's session over its own fee wallet.
31
+ export const openAgent = async (slot, options = {}) => openAgentOver(await connect(agentAccount(), AGENT_FEE_SEED, AGENT_PATHS), slot, options);
@@ -1,6 +1,6 @@
1
1
  import { type Arm, type Bytes32, type SealedCoin } from '@kuiralabs/midnight-account';
2
2
  import { type DiscoveredCoin } from './coins.ts';
3
- import { type Connection } from './connect.ts';
3
+ import type { Connection } from './connect.ts';
4
4
  import { type OwnerDevice } from './device.ts';
5
5
  export type OwnerRecord = {
6
6
  readonly contractAddress: string;
@@ -44,36 +44,4 @@ export declare const openSessionOver: <C extends Connection>(connection: C) => P
44
44
  reencryptHoldings: () => Promise<number>;
45
45
  rotateEncKey: () => Promise<number>;
46
46
  }>;
47
- export declare const openSession: () => Promise<{
48
- wallet: import("@midnight-ntwrk/testkit-js").MidnightWalletProvider;
49
- address: string;
50
- providers: import("./providers.ts").AccountProviders;
51
- account: import("@midnight-ntwrk/midnight-js-contracts").FoundContract<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>>;
52
- self: Bytes32;
53
- readLedger: () => Promise<import("../managed/Account/contract/index.js").Ledger>;
54
- close: () => Promise<void>;
55
- } & {
56
- device: OwnerDevice;
57
- bootSalt: Uint8Array<ArrayBufferLike>;
58
- encSecret: () => Bytes32;
59
- calls: {
60
- activate: (bootSalt: Bytes32) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "activate_initial_device_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "activate_initial_device_with_k256">>;
61
- withdrawUnshielded: (color: Bytes32, amount: bigint, recipient: Bytes32) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "withdraw_unshielded_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "withdraw_unshielded_with_k256">>;
62
- withdrawShielded: (recipient: Bytes32, amount: bigint, coin: import("@kuiralabs/midnight-account").HeldCoin) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "withdraw_shielded_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "withdraw_shielded_with_k256">>;
63
- unprovenWithdrawShieldedToContract: (recipient: Bytes32, amount: bigint, coin: import("@kuiralabs/midnight-account").HeldCoin) => Promise<import("./compose.ts").UnprovenCall & {
64
- readonly landed: () => Promise<void>;
65
- }>;
66
- appendInbox: (entry: Uint8Array) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "append_inbox_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "append_inbox_with_k256">>;
67
- rotateEncKey: (newKey: Bytes32) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "rotate_enc_key_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "rotate_enc_key_with_k256">>;
68
- addDevice: (newPk: import("@kuiralabs/midnight-account").Point) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "add_device_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "add_device_with_k256">>;
69
- issueGrant: (grantId: Bytes32, scope: import("@kuiralabs/midnight-account").GrantScope) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "issue_grant_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "issue_grant_with_k256">>;
70
- revokeGrant: (grantId: Bytes32) => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "revoke_grant_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "revoke_grant_with_k256">>;
71
- revokeAllGrants: () => Promise<import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "revoke_all_grants_with_jubjub"> | import("@midnight-ntwrk/midnight-js-contracts").FinalizedCallTxData<import("../managed/Account/contract/index.js").Contract<import("./account.ts").AccountPrivateState, import("../managed/Account/contract/index.js").Witnesses<import("./account.ts").AccountPrivateState>>, "revoke_all_grants_with_k256">>;
72
- };
73
- expectChange: (change: SealedCoin) => void;
74
- settleChanges: () => Promise<number>;
75
- refreshCoins: () => Promise<readonly DiscoveredCoin[]>;
76
- reencryptHoldings: () => Promise<number>;
77
- rotateEncKey: () => Promise<number>;
78
- }>;
79
47
  export {};
@@ -6,10 +6,8 @@
6
6
  import { readFileSync, writeFileSync } from 'node:fs';
7
7
  import { fromHex, hexOf, sealInboxEntry, x25519KeyPair, x25519PublicOf } from '@kuiralabs/midnight-account';
8
8
  import { coinStore, contractCoinCommitment, readAccountZswap, walkInbox } from "./coins.js";
9
- import { connect } from "./connect.js";
10
9
  import { JubjubDevice, K256Device, recoverUseCounter } from "./device.js";
11
- import { GENESIS_SEED } from "./localnet.js";
12
- import { OWNER_PATHS, stateFile } from "./profile.js";
10
+ import { stateFile } from "./profile.js";
13
11
  import { ownerCalls } from "./ownerCalls.js";
14
12
  import { PRIVATE_STATE_ID } from "./providers.js";
15
13
  const changeToJson = (coin) => ({ nonce: hexOf(coin.nonce), color: hexOf(coin.color), value: coin.value.toString() });
@@ -133,6 +131,3 @@ export const openSessionOver = async (connection) => {
133
131
  rotateEncKey,
134
132
  };
135
133
  };
136
- // The owner's session paid by the genesis wallet, which the scripts that move
137
- // that wallet's own funds reach as `wallet`.
138
- export const openSession = async () => openSessionOver(await connect(readOwner().contractAddress, GENESIS_SEED, OWNER_PATHS));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuiralabs/midnight-account-chain",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "The account contract on a local ledger-9 network, over midnight-js 5: a wallet-free reader, connections, composed contract payments, devices and grantees, the grant mailbox and verifier-key maintenance.",
6
6
  "license": "Apache-2.0",
@@ -37,11 +37,11 @@
37
37
  "@midnight-ntwrk/midnight-js-node-zk-config-provider": "5.0.0-rc.1",
38
38
  "@midnight-ntwrk/midnight-js-protocol": "5.0.0-rc.1",
39
39
  "@midnight-ntwrk/midnight-js-types": "5.0.0-rc.1",
40
- "@midnight-ntwrk/testkit-js": "5.0.0-rc.1",
41
40
  "@midnightntwrk/ledger-v9": "1.0.0-rc.5",
42
41
  "@midnightntwrk/wallet-sdk": "2.0.0-rc.0"
43
42
  },
44
43
  "devDependencies": {
44
+ "@midnight-ntwrk/testkit-js": "5.0.0-rc.1",
45
45
  "@types/node": "24.13.2",
46
46
  "typescript": "6.0.3"
47
47
  },