create-mn-app 0.4.0 → 0.4.1
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/package.json +1 -1
- package/templates/hello-world/README.md.template +16 -1
- package/templates/hello-world/_gitignore +3 -0
- package/templates/hello-world/package.json.template +1 -1
- package/templates/hello-world/scripts/e2e-check.ts.template +11 -45
- package/templates/hello-world/src/check-balance.ts.template +10 -55
- package/templates/hello-world/src/cli.ts.template +12 -50
- package/templates/hello-world/src/deploy.ts.template +12 -55
- package/templates/hello-world/src/wallet-state.ts +95 -0
- package/templates/hello-world/src/wallet.ts +189 -0
package/package.json
CHANGED
|
@@ -128,6 +128,19 @@ npm run network undeployed # or: npm run setup -- --network undeployed
|
|
|
128
128
|
Your preview/preprod wallet seeds and deploy addresses stay in
|
|
129
129
|
`.midnight-state.json`. Switch back later, and they're still there.
|
|
130
130
|
|
|
131
|
+
### Wallet sync cache
|
|
132
|
+
|
|
133
|
+
After each `deploy`, `cli`, or `check-balance` run, the scripts serialize the
|
|
134
|
+
wallet's synced state to `.midnight-wallet-state/<network>/` (gitignored).
|
|
135
|
+
The next run on the same network restores from that snapshot and only catches
|
|
136
|
+
up to the latest block instead of replaying from genesis — meaningful on
|
|
137
|
+
`preview` / `preprod` where a from-seed sync takes minutes.
|
|
138
|
+
|
|
139
|
+
If the cache is stale or corrupt (e.g. after an SDK upgrade with an
|
|
140
|
+
incompatible state format) the wallet falls back to a fresh from-seed sync
|
|
141
|
+
with a one-line warning. `npm run clean` removes the cache along with other
|
|
142
|
+
generated state.
|
|
143
|
+
|
|
131
144
|
## Available scripts
|
|
132
145
|
|
|
133
146
|
| Script | Description |
|
|
@@ -138,7 +151,7 @@ Your preview/preprod wallet seeds and deploy addresses stay in
|
|
|
138
151
|
| `npm run cli` | Interactive CLI to call circuits on the deployed contract. |
|
|
139
152
|
| `npm run check-balance` | Print the genesis-seed wallet's NIGHT and DUST balances. |
|
|
140
153
|
| `npm run test:e2e` | Smoke + read-back check against the deployed contract. |
|
|
141
|
-
| `npm run clean` | Remove `contracts/managed
|
|
154
|
+
| `npm run clean` | Remove `contracts/managed/`, `.midnight-state.json`, and `.midnight-wallet-state/`. |
|
|
142
155
|
| `npm run proof-server:start` / `:stop` | Compose lifecycle for just the proof-server service. |
|
|
143
156
|
|
|
144
157
|
## Project structure
|
|
@@ -151,12 +164,14 @@ Your preview/preprod wallet seeds and deploy addresses stay in
|
|
|
151
164
|
│ └── e2e-check.ts # smoke + read-back
|
|
152
165
|
├── src/
|
|
153
166
|
│ ├── network.ts # network selection + state file management
|
|
167
|
+
│ ├── wallet.ts # wallet construction + sync-state cache
|
|
154
168
|
│ ├── setup.ts # orchestrator for `npm run setup`
|
|
155
169
|
│ ├── deploy.ts # deploy the contract
|
|
156
170
|
│ ├── cli.ts # interact with deployed contract
|
|
157
171
|
│ └── check-balance.ts # NIGHT / DUST balance
|
|
158
172
|
├── docker-compose.yml # node + indexer + proof-server
|
|
159
173
|
├── .midnight-state.json # written by deploy (gitignored)
|
|
174
|
+
├── .midnight-wallet-state/ # serialized sync state per network (gitignored)
|
|
160
175
|
├── package.json
|
|
161
176
|
└── tsconfig.json
|
|
162
177
|
```
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"network": "npx tsx src/network.ts",
|
|
16
16
|
"proof-server:start": "docker compose up -d",
|
|
17
17
|
"proof-server:stop": "docker compose down",
|
|
18
|
-
"clean": "rm -rf contracts/managed .midnight-state.json",
|
|
18
|
+
"clean": "rm -rf contracts/managed .midnight-state.json .midnight-wallet-state",
|
|
19
19
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
20
20
|
"test:e2e": "tsx scripts/e2e-check.ts"
|
|
21
21
|
},
|
|
@@ -8,21 +8,14 @@ import * as fs from 'node:fs';
|
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
10
10
|
import { WebSocket } from 'ws';
|
|
11
|
-
import { Buffer } from 'buffer';
|
|
12
11
|
|
|
13
12
|
import { findDeployedContract } from '@midnight-ntwrk/midnight-js-contracts';
|
|
14
13
|
import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client-proof-provider';
|
|
15
14
|
import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
|
|
16
15
|
import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
|
|
17
16
|
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
|
|
18
|
-
import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
|
|
19
17
|
import { resolveNetwork, getOrCreateSeed, getDeployment } from '../src/network';
|
|
20
|
-
import
|
|
21
|
-
import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
|
|
22
|
-
import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
|
|
23
|
-
import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
|
|
24
|
-
import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
|
|
25
|
-
import { createKeystore, NoOpTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
|
|
18
|
+
import { createWallet, persistWalletState } from '../src/wallet';
|
|
26
19
|
import { CompiledContract } from '@midnight-ntwrk/compact-js';
|
|
27
20
|
|
|
28
21
|
// @ts-expect-error wallet sync requires WebSocket
|
|
@@ -31,7 +24,6 @@ globalThis.WebSocket = WebSocket;
|
|
|
31
24
|
// ─── Network configuration ─────────────────────────────────────────────────────
|
|
32
25
|
|
|
33
26
|
const { network, config: networkConfig } = resolveNetwork();
|
|
34
|
-
setNetworkId(networkConfig.networkId);
|
|
35
27
|
const SEED = getOrCreateSeed(network);
|
|
36
28
|
|
|
37
29
|
function fail(msg: string): never {
|
|
@@ -54,7 +46,7 @@ async function main() {
|
|
|
54
46
|
fail(`Deployment address missing or invalid: ${JSON.stringify(deployment, null, 2)}`);
|
|
55
47
|
}
|
|
56
48
|
|
|
57
|
-
// 2. Build wallet
|
|
49
|
+
// 2. Build wallet and providers
|
|
58
50
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
59
51
|
const zkConfigPath = path.resolve(__dirname, '..', 'contracts', 'managed', 'hello-world');
|
|
60
52
|
const contractPath = path.join(zkConfigPath, 'contract', 'index.js');
|
|
@@ -65,37 +57,11 @@ async function main() {
|
|
|
65
57
|
CompiledContract.withCompiledFileAssets(zkConfigPath),
|
|
66
58
|
);
|
|
67
59
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.deriveKeysAt(0);
|
|
74
|
-
if (derived.type !== 'keysDerived') fail('Key derivation failed.');
|
|
75
|
-
hd.hdWallet.clear();
|
|
76
|
-
|
|
77
|
-
const networkId = getNetworkId();
|
|
78
|
-
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(derived.keys[Roles.Zswap]);
|
|
79
|
-
const dustSecretKey = ledger.DustSecretKey.fromSeed(derived.keys[Roles.Dust]);
|
|
80
|
-
const unshieldedKeystore = createKeystore(derived.keys[Roles.NightExternal], networkId);
|
|
81
|
-
|
|
82
|
-
const wallet = await WalletFacade.init({
|
|
83
|
-
configuration: {
|
|
84
|
-
networkId,
|
|
85
|
-
indexerClientConnection: { indexerHttpUrl: networkConfig.indexer, indexerWsUrl: networkConfig.indexerWS },
|
|
86
|
-
provingServerUrl: new URL(networkConfig.proofServer),
|
|
87
|
-
relayURL: new URL(networkConfig.node.replace(/^http/, 'ws')),
|
|
88
|
-
txHistoryStorage: new NoOpTransactionHistoryStorage(),
|
|
89
|
-
costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
|
|
90
|
-
},
|
|
91
|
-
shielded: async (c) => ShieldedWallet(c).startWithSecretKeys(shieldedSecretKeys),
|
|
92
|
-
unshielded: async (c) =>
|
|
93
|
-
UnshieldedWallet(c).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
|
|
94
|
-
dust: async (c) =>
|
|
95
|
-
DustWallet(c).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust),
|
|
96
|
-
});
|
|
97
|
-
await wallet.start(shieldedSecretKeys, dustSecretKey);
|
|
98
|
-
const state = await wallet.waitForSyncedState();
|
|
60
|
+
const walletCtx = await createWallet({ network, networkConfig, seed: SEED });
|
|
61
|
+
const state = await walletCtx.wallet.waitForSyncedState();
|
|
62
|
+
// Persist the sync state — saves time on the next e2e-check invocation in CI
|
|
63
|
+
// when run against the same persistent wallet directory.
|
|
64
|
+
await persistWalletState(network, walletCtx);
|
|
99
65
|
|
|
100
66
|
const zkConfigProvider = new NodeZkConfigProvider(zkConfigPath);
|
|
101
67
|
const walletProvider = {
|
|
@@ -112,7 +78,7 @@ async function main() {
|
|
|
112
78
|
const providers = {
|
|
113
79
|
privateStateProvider: levelPrivateStateProvider({
|
|
114
80
|
privateStateStoreName: 'hello-world-state',
|
|
115
|
-
accountId: unshieldedKeystore.getBech32Address().toString(),
|
|
81
|
+
accountId: walletCtx.unshieldedKeystore.getBech32Address().toString(),
|
|
116
82
|
// SDK requires ≥16 chars. e2e-check is read-only so we don't expose
|
|
117
83
|
// the env-var override here — match the deploy script's local-devnet default.
|
|
118
84
|
privateStoragePasswordProvider: () => 'Local-Devnet-Development-Placeholder-1',
|
|
@@ -131,7 +97,7 @@ async function main() {
|
|
|
131
97
|
compiledContract: compiledContract as any,
|
|
132
98
|
});
|
|
133
99
|
} catch (err: any) {
|
|
134
|
-
await wallet.stop();
|
|
100
|
+
await walletCtx.wallet.stop();
|
|
135
101
|
fail(`findDeployedContract threw: ${err?.message ?? err}`);
|
|
136
102
|
}
|
|
137
103
|
|
|
@@ -140,7 +106,7 @@ async function main() {
|
|
|
140
106
|
// we know how to construct the local handle.
|
|
141
107
|
const onChainState = await providers.publicDataProvider.queryContractState(deployment.address);
|
|
142
108
|
if (!onChainState) {
|
|
143
|
-
await wallet.stop();
|
|
109
|
+
await walletCtx.wallet.stop();
|
|
144
110
|
fail(`queryContractState returned null for ${deployment.address}`);
|
|
145
111
|
}
|
|
146
112
|
|
|
@@ -148,7 +114,7 @@ async function main() {
|
|
|
148
114
|
console.log(` contractAddress: ${deployment.address}`);
|
|
149
115
|
console.log(` network: ${network}`);
|
|
150
116
|
|
|
151
|
-
await wallet.stop();
|
|
117
|
+
await walletCtx.wallet.stop();
|
|
152
118
|
process.exit(0);
|
|
153
119
|
}
|
|
154
120
|
|
|
@@ -2,19 +2,11 @@
|
|
|
2
2
|
* Check wallet balance on the local Midnight devnet
|
|
3
3
|
*/
|
|
4
4
|
import { WebSocket } from 'ws';
|
|
5
|
-
import * as Rx from 'rxjs';
|
|
6
|
-
import { Buffer } from 'buffer';
|
|
7
5
|
|
|
8
6
|
// Midnight SDK imports
|
|
9
|
-
import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
|
|
10
|
-
import * as ledger from '@midnight-ntwrk/ledger-v8';
|
|
11
7
|
import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
|
|
12
|
-
import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
|
|
13
|
-
import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
|
|
14
|
-
import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
|
|
15
|
-
import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
|
|
16
|
-
import { createKeystore, NoOpTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
|
|
17
8
|
import { resolveNetwork, getOrCreateSeed } from './network';
|
|
9
|
+
import { createWallet, persistWalletState } from './wallet';
|
|
18
10
|
|
|
19
11
|
// Enable WebSocket for GraphQL subscriptions
|
|
20
12
|
// @ts-expect-error Required for wallet sync
|
|
@@ -23,48 +15,8 @@ globalThis.WebSocket = WebSocket;
|
|
|
23
15
|
// ─── Network configuration ─────────────────────────────────────────────────────
|
|
24
16
|
|
|
25
17
|
const { network, config: networkConfig } = resolveNetwork();
|
|
26
|
-
setNetworkId(networkConfig.networkId);
|
|
27
18
|
const SEED = getOrCreateSeed(network);
|
|
28
19
|
|
|
29
|
-
// ─── Wallet Functions ──────────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
function deriveKeys(seed: string) {
|
|
32
|
-
const hdWallet = HDWallet.fromSeed(Buffer.from(seed, 'hex'));
|
|
33
|
-
if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
|
|
34
|
-
const result = hdWallet.hdWallet.selectAccount(0).selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust]).deriveKeysAt(0);
|
|
35
|
-
if (result.type !== 'keysDerived') throw new Error('Key derivation failed');
|
|
36
|
-
hdWallet.hdWallet.clear();
|
|
37
|
-
return result.keys;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function createWallet(seed: string) {
|
|
41
|
-
const keys = deriveKeys(seed);
|
|
42
|
-
const networkId = getNetworkId();
|
|
43
|
-
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
|
|
44
|
-
const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);
|
|
45
|
-
const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], networkId);
|
|
46
|
-
|
|
47
|
-
const walletConfig = {
|
|
48
|
-
networkId,
|
|
49
|
-
indexerClientConnection: { indexerHttpUrl: networkConfig.indexer, indexerWsUrl: networkConfig.indexerWS },
|
|
50
|
-
provingServerUrl: new URL(networkConfig.proofServer),
|
|
51
|
-
relayURL: new URL(networkConfig.node.replace(/^http/, 'ws')),
|
|
52
|
-
txHistoryStorage: new NoOpTransactionHistoryStorage(),
|
|
53
|
-
costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const wallet = await WalletFacade.init({
|
|
57
|
-
configuration: walletConfig,
|
|
58
|
-
shielded: async (config) => ShieldedWallet(config).startWithSecretKeys(shieldedSecretKeys),
|
|
59
|
-
unshielded: async (config) => UnshieldedWallet(config).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
|
|
60
|
-
dust: async (config) => DustWallet(config).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
await wallet.start(shieldedSecretKeys, dustSecretKey);
|
|
64
|
-
|
|
65
|
-
return { wallet, unshieldedKeystore };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
20
|
// ─── Main ──────────────────────────────────────────────────────────────────────
|
|
69
21
|
|
|
70
22
|
async function main() {
|
|
@@ -72,11 +24,13 @@ async function main() {
|
|
|
72
24
|
console.log('║ Wallet Balance Checker ║');
|
|
73
25
|
console.log('╚══════════════════════════════════════════════════════════════╝\n');
|
|
74
26
|
|
|
75
|
-
const seed = SEED;
|
|
76
|
-
|
|
77
27
|
try {
|
|
78
28
|
console.log(' Building wallet...');
|
|
79
|
-
const
|
|
29
|
+
const walletCtx = await createWallet({ network, networkConfig, seed: SEED });
|
|
30
|
+
const restoredCount = Object.values(walletCtx.restored).filter(Boolean).length;
|
|
31
|
+
if (restoredCount > 0) {
|
|
32
|
+
console.log(` Restored ${restoredCount}/3 child wallets from .midnight-wallet-state — sync will resume from saved point.`);
|
|
33
|
+
}
|
|
80
34
|
|
|
81
35
|
console.log(' Syncing with network...');
|
|
82
36
|
console.log(' ℹ This may take several minutes depending on network size.');
|
|
@@ -86,11 +40,11 @@ async function main() {
|
|
|
86
40
|
const elapsed = Math.round((Date.now() - syncStart) / 1000);
|
|
87
41
|
process.stdout.write(`\r ⏳ Still syncing... (${elapsed}s elapsed) `);
|
|
88
42
|
}, 5000);
|
|
89
|
-
const state = await wallet.waitForSyncedState();
|
|
43
|
+
const state = await walletCtx.wallet.waitForSyncedState();
|
|
90
44
|
clearInterval(syncInterval);
|
|
91
45
|
process.stdout.write('\r ✓ Synced with network. \n');
|
|
92
46
|
|
|
93
|
-
const address = unshieldedKeystore.getBech32Address();
|
|
47
|
+
const address = walletCtx.unshieldedKeystore.getBech32Address();
|
|
94
48
|
const tNightBalance = state.unshielded.balances[unshieldedToken().raw] ?? 0n;
|
|
95
49
|
const dustBalance = state.dust.balance(new Date());
|
|
96
50
|
|
|
@@ -117,7 +71,8 @@ async function main() {
|
|
|
117
71
|
console.log(' ✅ Wallet is funded and ready!\n');
|
|
118
72
|
}
|
|
119
73
|
|
|
120
|
-
await
|
|
74
|
+
await persistWalletState(network, walletCtx);
|
|
75
|
+
await walletCtx.wallet.stop();
|
|
121
76
|
} catch (error) {
|
|
122
77
|
console.error('\n❌ Error:', error instanceof Error ? error.message : error);
|
|
123
78
|
process.exit(1);
|
|
@@ -7,7 +7,6 @@ import * as fs from 'node:fs';
|
|
|
7
7
|
import * as path from 'node:path';
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
9
|
import { WebSocket } from 'ws';
|
|
10
|
-
import * as Rx from 'rxjs';
|
|
11
10
|
import { Buffer } from 'buffer';
|
|
12
11
|
|
|
13
12
|
// Midnight SDK imports
|
|
@@ -16,15 +15,8 @@ import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client
|
|
|
16
15
|
import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
|
|
17
16
|
import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
|
|
18
17
|
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
|
|
19
|
-
import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
|
|
20
18
|
import { resolveNetwork, getOrCreateSeed, getDeployment } from './network';
|
|
21
|
-
import
|
|
22
|
-
import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
|
|
23
|
-
import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
|
|
24
|
-
import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
|
|
25
|
-
import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
|
|
26
|
-
import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
|
|
27
|
-
import { createKeystore, NoOpTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
|
|
19
|
+
import { createWallet, persistWalletState, unshieldedToken, type WalletContext } from './wallet';
|
|
28
20
|
import { CompiledContract } from '@midnight-ntwrk/compact-js';
|
|
29
21
|
|
|
30
22
|
// Enable WebSocket for GraphQL subscriptions
|
|
@@ -32,7 +24,6 @@ import { CompiledContract } from '@midnight-ntwrk/compact-js';
|
|
|
32
24
|
globalThis.WebSocket = WebSocket;
|
|
33
25
|
|
|
34
26
|
const { network, config: networkConfig } = resolveNetwork();
|
|
35
|
-
setNetworkId(networkConfig.networkId);
|
|
36
27
|
const SEED = getOrCreateSeed(network);
|
|
37
28
|
|
|
38
29
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -54,46 +45,9 @@ const compiledContract = CompiledContract.make('hello-world', HelloWorld.Contrac
|
|
|
54
45
|
CompiledContract.withCompiledFileAssets(zkConfigPath),
|
|
55
46
|
);
|
|
56
47
|
|
|
57
|
-
// ───
|
|
48
|
+
// ─── Providers ─────────────────────────────────────────────────────────────────
|
|
58
49
|
|
|
59
|
-
function
|
|
60
|
-
const hdWallet = HDWallet.fromSeed(Buffer.from(seed, 'hex'));
|
|
61
|
-
if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
|
|
62
|
-
const result = hdWallet.hdWallet.selectAccount(0).selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust]).deriveKeysAt(0);
|
|
63
|
-
if (result.type !== 'keysDerived') throw new Error('Key derivation failed');
|
|
64
|
-
hdWallet.hdWallet.clear();
|
|
65
|
-
return result.keys;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function createWallet(seed: string) {
|
|
69
|
-
const keys = deriveKeys(seed);
|
|
70
|
-
const networkId = getNetworkId();
|
|
71
|
-
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
|
|
72
|
-
const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);
|
|
73
|
-
const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], networkId);
|
|
74
|
-
|
|
75
|
-
const walletConfig = {
|
|
76
|
-
networkId,
|
|
77
|
-
indexerClientConnection: { indexerHttpUrl: networkConfig.indexer, indexerWsUrl: networkConfig.indexerWS },
|
|
78
|
-
provingServerUrl: new URL(networkConfig.proofServer),
|
|
79
|
-
relayURL: new URL(networkConfig.node.replace(/^http/, 'ws')),
|
|
80
|
-
txHistoryStorage: new NoOpTransactionHistoryStorage(),
|
|
81
|
-
costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const wallet = await WalletFacade.init({
|
|
85
|
-
configuration: walletConfig,
|
|
86
|
-
shielded: async (config) => ShieldedWallet(config).startWithSecretKeys(shieldedSecretKeys),
|
|
87
|
-
unshielded: async (config) => UnshieldedWallet(config).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
|
|
88
|
-
dust: async (config) => DustWallet(config).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust),
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
await wallet.start(shieldedSecretKeys, dustSecretKey);
|
|
92
|
-
|
|
93
|
-
return { wallet, shieldedSecretKeys, dustSecretKey, unshieldedKeystore };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async function createProviders(walletCtx: ReturnType<typeof createWallet> extends Promise<infer T> ? T : never) {
|
|
50
|
+
async function createProviders(walletCtx: WalletContext) {
|
|
97
51
|
// The SDK requires the private-state password to be at least 16 characters.
|
|
98
52
|
// The default below is a placeholder for local devnet only — set a strong
|
|
99
53
|
// password via PRIVATE_STATE_PASSWORD when you move to a non-local target.
|
|
@@ -157,7 +111,11 @@ async function main() {
|
|
|
157
111
|
const seed = SEED;
|
|
158
112
|
|
|
159
113
|
console.log(' Connecting to wallet...');
|
|
160
|
-
const walletCtx = await createWallet(seed);
|
|
114
|
+
const walletCtx = await createWallet({ network, networkConfig, seed });
|
|
115
|
+
const restoredCount = Object.values(walletCtx.restored).filter(Boolean).length;
|
|
116
|
+
if (restoredCount > 0) {
|
|
117
|
+
console.log(` Restored ${restoredCount}/3 child wallets from .midnight-wallet-state — sync will resume from saved point.`);
|
|
118
|
+
}
|
|
161
119
|
|
|
162
120
|
console.log(' Syncing with network...');
|
|
163
121
|
console.log(' ℹ This may take several minutes depending on network size.');
|
|
@@ -170,6 +128,9 @@ async function main() {
|
|
|
170
128
|
const state = await walletCtx.wallet.waitForSyncedState();
|
|
171
129
|
clearInterval(syncInterval);
|
|
172
130
|
process.stdout.write('\r ✓ Synced with network. \n');
|
|
131
|
+
|
|
132
|
+
// Persist sync state so the next run doesn't have to redo this work.
|
|
133
|
+
await persistWalletState(network, walletCtx);
|
|
173
134
|
const balance = state.unshielded.balances[unshieldedToken().raw] ?? 0n;
|
|
174
135
|
console.log(` Balance: ${balance.toLocaleString()} tNight\n`);
|
|
175
136
|
|
|
@@ -258,6 +219,7 @@ async function main() {
|
|
|
258
219
|
}
|
|
259
220
|
}
|
|
260
221
|
|
|
222
|
+
await persistWalletState(network, walletCtx);
|
|
261
223
|
await walletCtx.wallet.stop();
|
|
262
224
|
} catch (error) {
|
|
263
225
|
console.error('\n❌ Error:', error instanceof Error ? error.message : error);
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
import * as fs from 'node:fs';
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import { resolveNetwork, getOrCreateSeed, recordDeployment } from './network';
|
|
10
|
+
import { createWallet, persistWalletState, unshieldedToken, type WalletContext } from './wallet';
|
|
10
11
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
11
12
|
import { WebSocket } from 'ws';
|
|
12
13
|
import * as Rx from 'rxjs';
|
|
13
|
-
import { Buffer } from 'buffer';
|
|
14
14
|
|
|
15
15
|
// Midnight SDK imports
|
|
16
16
|
import { deployContract } from '@midnight-ntwrk/midnight-js-contracts';
|
|
@@ -18,14 +18,6 @@ import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client
|
|
|
18
18
|
import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
|
|
19
19
|
import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
|
|
20
20
|
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
|
|
21
|
-
import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
|
|
22
|
-
import * as ledger from '@midnight-ntwrk/ledger-v8';
|
|
23
|
-
import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
|
|
24
|
-
import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
|
|
25
|
-
import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
|
|
26
|
-
import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
|
|
27
|
-
import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
|
|
28
|
-
import { createKeystore, NoOpTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
|
|
29
21
|
import { CompiledContract } from '@midnight-ntwrk/compact-js';
|
|
30
22
|
|
|
31
23
|
// @ts-expect-error Required for wallet sync
|
|
@@ -37,7 +29,6 @@ globalThis.WebSocket = WebSocket;
|
|
|
37
29
|
// 'undeployed' (local devnet). Switch networks with: npm run network <name>
|
|
38
30
|
|
|
39
31
|
const { network, config: networkConfig } = resolveNetwork();
|
|
40
|
-
setNetworkId(networkConfig.networkId);
|
|
41
32
|
const SEED = getOrCreateSeed(network);
|
|
42
33
|
|
|
43
34
|
// ─── Proof server readiness ────────────────────────────────────────────────────
|
|
@@ -86,51 +77,9 @@ const compiledContract = CompiledContract.make('hello-world', HelloWorld.Contrac
|
|
|
86
77
|
CompiledContract.withCompiledFileAssets(zkConfigPath),
|
|
87
78
|
);
|
|
88
79
|
|
|
89
|
-
// ───
|
|
90
|
-
|
|
91
|
-
function deriveKeys(seed: string) {
|
|
92
|
-
const hdWallet = HDWallet.fromSeed(Buffer.from(seed, 'hex'));
|
|
93
|
-
if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
|
|
94
|
-
const result = hdWallet.hdWallet
|
|
95
|
-
.selectAccount(0)
|
|
96
|
-
.selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust])
|
|
97
|
-
.deriveKeysAt(0);
|
|
98
|
-
if (result.type !== 'keysDerived') throw new Error('Key derivation failed');
|
|
99
|
-
hdWallet.hdWallet.clear();
|
|
100
|
-
return result.keys;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function createWallet(seed: string) {
|
|
104
|
-
const keys = deriveKeys(seed);
|
|
105
|
-
const networkId = getNetworkId();
|
|
106
|
-
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
|
|
107
|
-
const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);
|
|
108
|
-
const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], networkId);
|
|
109
|
-
|
|
110
|
-
const walletConfig = {
|
|
111
|
-
networkId,
|
|
112
|
-
indexerClientConnection: { indexerHttpUrl: networkConfig.indexer, indexerWsUrl: networkConfig.indexerWS },
|
|
113
|
-
provingServerUrl: new URL(networkConfig.proofServer),
|
|
114
|
-
relayURL: new URL(networkConfig.node.replace(/^http/, 'ws')),
|
|
115
|
-
txHistoryStorage: new NoOpTransactionHistoryStorage(),
|
|
116
|
-
costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const wallet = await WalletFacade.init({
|
|
120
|
-
configuration: walletConfig,
|
|
121
|
-
shielded: async (config) => ShieldedWallet(config).startWithSecretKeys(shieldedSecretKeys),
|
|
122
|
-
unshielded: async (config) =>
|
|
123
|
-
UnshieldedWallet(config).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
|
|
124
|
-
dust: async (config) =>
|
|
125
|
-
DustWallet(config).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust),
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
await wallet.start(shieldedSecretKeys, dustSecretKey);
|
|
80
|
+
// ─── Providers ─────────────────────────────────────────────────────────────────
|
|
129
81
|
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async function createProviders(walletCtx: ReturnType<typeof createWallet> extends Promise<infer T> ? T : never) {
|
|
82
|
+
async function createProviders(walletCtx: WalletContext) {
|
|
134
83
|
// The SDK requires the private-state password to be at least 16 characters.
|
|
135
84
|
// The default below is a placeholder for local devnet only — set a strong
|
|
136
85
|
// password via PRIVATE_STATE_PASSWORD when you move to a non-local target.
|
|
@@ -182,7 +131,11 @@ async function main() {
|
|
|
182
131
|
|
|
183
132
|
console.log('─── Wallet setup ───────────────────────────────────────────────\n');
|
|
184
133
|
console.log(' Creating wallet...');
|
|
185
|
-
const walletCtx = await createWallet(seed);
|
|
134
|
+
const walletCtx = await createWallet({ network, networkConfig, seed });
|
|
135
|
+
const restoredCount = Object.values(walletCtx.restored).filter(Boolean).length;
|
|
136
|
+
if (restoredCount > 0) {
|
|
137
|
+
console.log(` Restored ${restoredCount}/3 child wallets from .midnight-wallet-state — sync will resume from saved point.`);
|
|
138
|
+
}
|
|
186
139
|
|
|
187
140
|
console.log(' Syncing with network...');
|
|
188
141
|
console.log(' ℹ This may take several minutes depending on network size.');
|
|
@@ -196,6 +149,9 @@ async function main() {
|
|
|
196
149
|
clearInterval(syncInterval);
|
|
197
150
|
process.stdout.write('\r ✓ Synced with network. \n');
|
|
198
151
|
|
|
152
|
+
// Persist sync state now so a later deploy failure doesn't waste the sync work.
|
|
153
|
+
await persistWalletState(network, walletCtx);
|
|
154
|
+
|
|
199
155
|
const address = walletCtx.unshieldedKeystore.getBech32Address();
|
|
200
156
|
let balance = state.unshielded.balances[unshieldedToken().raw] ?? 0n;
|
|
201
157
|
console.log(`\n Wallet Address: ${address}`);
|
|
@@ -391,6 +347,7 @@ async function main() {
|
|
|
391
347
|
recordDeployment(network, contractAddress, address.toString());
|
|
392
348
|
console.log(' Saved to .midnight-state.json\n');
|
|
393
349
|
|
|
350
|
+
await persistWalletState(network, walletCtx);
|
|
394
351
|
await walletCtx.wallet.stop();
|
|
395
352
|
console.log('─── Deployment complete ────────────────────────────────────────\n');
|
|
396
353
|
console.log(' Next: npm run cli\n');
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Wallet sync-state persistence.
|
|
2
|
+
//
|
|
3
|
+
// Mirrors network.ts: no template substitutions, all I/O via function
|
|
4
|
+
// parameters, no SDK imports — keeps the module unit-testable from the
|
|
5
|
+
// create-mn-app workspace (which doesn't install @midnight-ntwrk/* packages).
|
|
6
|
+
//
|
|
7
|
+
// Why: without persistence, every `npm run deploy` / `npm run cli` rebuilds
|
|
8
|
+
// each child wallet from seed and re-syncs against the chain. On public
|
|
9
|
+
// networks (preview, preprod) that's minutes per run — and painful on retries
|
|
10
|
+
// after a transient failure. The SDK exposes serializeState() and restore()
|
|
11
|
+
// on each child wallet class; wallet.ts is the glue that uses them, and this
|
|
12
|
+
// file is the on-disk format underneath.
|
|
13
|
+
|
|
14
|
+
import * as fs from 'node:fs';
|
|
15
|
+
import * as path from 'node:path';
|
|
16
|
+
|
|
17
|
+
import type { NetworkId } from './network';
|
|
18
|
+
|
|
19
|
+
export const WALLET_STATE_DIR = '.midnight-wallet-state';
|
|
20
|
+
export const WALLET_STATE_VERSION = 1 as const;
|
|
21
|
+
|
|
22
|
+
export type ChildKind = 'shielded' | 'unshielded' | 'dust';
|
|
23
|
+
export const CHILD_KINDS: readonly ChildKind[] = ['shielded', 'unshielded', 'dust'] as const;
|
|
24
|
+
|
|
25
|
+
export interface PersistedWalletState {
|
|
26
|
+
shielded?: unknown;
|
|
27
|
+
unshielded?: unknown;
|
|
28
|
+
dust?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface FsOptions {
|
|
32
|
+
cwd?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function networkDir(network: NetworkId, opts: FsOptions = {}): string {
|
|
36
|
+
return path.join(opts.cwd ?? process.cwd(), WALLET_STATE_DIR, network);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function statePath(network: NetworkId, kind: ChildKind, opts: FsOptions = {}): string {
|
|
40
|
+
return path.join(networkDir(network, opts), `${kind}.json`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function atomicWrite(file: string, content: string): void {
|
|
44
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
45
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
46
|
+
fs.writeFileSync(tmp, content);
|
|
47
|
+
fs.renameSync(tmp, file);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface VersionedState<T> {
|
|
51
|
+
version: typeof WALLET_STATE_VERSION;
|
|
52
|
+
state: T;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readVersionedState<T>(file: string): T | undefined {
|
|
56
|
+
if (!fs.existsSync(file)) return undefined;
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8')) as VersionedState<T>;
|
|
59
|
+
if (!parsed || typeof parsed !== 'object' || parsed.version !== WALLET_STATE_VERSION) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return parsed.state;
|
|
63
|
+
} catch {
|
|
64
|
+
// Corrupt file — caller falls back to from-seed sync; we'll overwrite on save.
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function writeVersionedState<T>(file: string, state: T): void {
|
|
70
|
+
const payload: VersionedState<T> = { version: WALLET_STATE_VERSION, state };
|
|
71
|
+
atomicWrite(file, `${JSON.stringify(payload)}\n`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function loadWalletState(network: NetworkId, opts: FsOptions = {}): PersistedWalletState {
|
|
75
|
+
return {
|
|
76
|
+
shielded: readVersionedState(statePath(network, 'shielded', opts)),
|
|
77
|
+
unshielded: readVersionedState(statePath(network, 'unshielded', opts)),
|
|
78
|
+
dust: readVersionedState<string>(statePath(network, 'dust', opts)),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function saveWalletState(
|
|
83
|
+
network: NetworkId,
|
|
84
|
+
state: PersistedWalletState,
|
|
85
|
+
opts: FsOptions = {},
|
|
86
|
+
): void {
|
|
87
|
+
if (state.shielded !== undefined) writeVersionedState(statePath(network, 'shielded', opts), state.shielded);
|
|
88
|
+
if (state.unshielded !== undefined) writeVersionedState(statePath(network, 'unshielded', opts), state.unshielded);
|
|
89
|
+
if (state.dust !== undefined) writeVersionedState(statePath(network, 'dust', opts), state.dust);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function clearWalletState(network: NetworkId, opts: FsOptions = {}): void {
|
|
93
|
+
const dir = networkDir(network, opts);
|
|
94
|
+
if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
|
|
95
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Wallet construction + sync-state restore.
|
|
2
|
+
//
|
|
3
|
+
// Mirrors network.ts in structure. The on-disk format and pure I/O live in
|
|
4
|
+
// wallet-state.ts (unit-tested from the scaffolder workspace, no SDK deps);
|
|
5
|
+
// this file is the glue between that format and the wallet SDK.
|
|
6
|
+
|
|
7
|
+
import { Buffer } from 'buffer';
|
|
8
|
+
|
|
9
|
+
import * as ledger from '@midnight-ntwrk/ledger-v8';
|
|
10
|
+
import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
|
|
11
|
+
import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
|
|
12
|
+
import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
|
|
13
|
+
import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
|
|
14
|
+
import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
|
|
15
|
+
import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
|
|
16
|
+
import {
|
|
17
|
+
createKeystore,
|
|
18
|
+
NoOpTransactionHistoryStorage,
|
|
19
|
+
PublicKey,
|
|
20
|
+
UnshieldedWallet,
|
|
21
|
+
} from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
|
|
22
|
+
|
|
23
|
+
import type { NetworkConfig, NetworkId } from './network';
|
|
24
|
+
import {
|
|
25
|
+
CHILD_KINDS,
|
|
26
|
+
loadWalletState,
|
|
27
|
+
saveWalletState,
|
|
28
|
+
type ChildKind,
|
|
29
|
+
type PersistedWalletState,
|
|
30
|
+
} from './wallet-state';
|
|
31
|
+
|
|
32
|
+
export { unshieldedToken };
|
|
33
|
+
export type { PersistedWalletState };
|
|
34
|
+
export {
|
|
35
|
+
loadWalletState,
|
|
36
|
+
saveWalletState,
|
|
37
|
+
clearWalletState,
|
|
38
|
+
WALLET_STATE_DIR,
|
|
39
|
+
WALLET_STATE_VERSION,
|
|
40
|
+
} from './wallet-state';
|
|
41
|
+
|
|
42
|
+
function deriveKeys(seed: string) {
|
|
43
|
+
const hdWallet = HDWallet.fromSeed(Buffer.from(seed, 'hex'));
|
|
44
|
+
if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
|
|
45
|
+
const result = hdWallet.hdWallet
|
|
46
|
+
.selectAccount(0)
|
|
47
|
+
.selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust])
|
|
48
|
+
.deriveKeysAt(0);
|
|
49
|
+
if (result.type !== 'keysDerived') throw new Error('Key derivation failed');
|
|
50
|
+
hdWallet.hdWallet.clear();
|
|
51
|
+
return result.keys;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface WalletContext {
|
|
55
|
+
wallet: Awaited<ReturnType<typeof WalletFacade.init>>;
|
|
56
|
+
shieldedSecretKeys: ReturnType<typeof ledger.ZswapSecretKeys.fromSeed>;
|
|
57
|
+
dustSecretKey: ReturnType<typeof ledger.DustSecretKey.fromSeed>;
|
|
58
|
+
unshieldedKeystore: ReturnType<typeof createKeystore>;
|
|
59
|
+
restored: { shielded: boolean; unshielded: boolean; dust: boolean };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface CreateWalletOptions {
|
|
63
|
+
network: NetworkId;
|
|
64
|
+
networkConfig: NetworkConfig;
|
|
65
|
+
seed: string;
|
|
66
|
+
/**
|
|
67
|
+
* Whether to attempt to restore each child wallet from saved state.
|
|
68
|
+
* Defaults to true. Pass false to force a from-seed sync (used by tests).
|
|
69
|
+
*/
|
|
70
|
+
restore?: boolean;
|
|
71
|
+
cwd?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function warnRestoreFailure(kind: ChildKind, err: unknown): void {
|
|
75
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
76
|
+
process.stderr.write(` ⚠ Could not restore ${kind} wallet state (${msg}); falling back to fresh sync.\n`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build the wallet facade, restoring each child from saved state when
|
|
81
|
+
* available and falling back to a from-seed start when not (or when restore
|
|
82
|
+
* throws, e.g. after an SDK upgrade with an incompatible state format).
|
|
83
|
+
*
|
|
84
|
+
* Caller is responsible for `await wallet.waitForSyncedState()` afterwards.
|
|
85
|
+
*/
|
|
86
|
+
export async function createWallet(opts: CreateWalletOptions): Promise<WalletContext> {
|
|
87
|
+
setNetworkId(opts.networkConfig.networkId);
|
|
88
|
+
|
|
89
|
+
const keys = deriveKeys(opts.seed);
|
|
90
|
+
const networkId = getNetworkId();
|
|
91
|
+
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
|
|
92
|
+
const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);
|
|
93
|
+
const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], networkId);
|
|
94
|
+
|
|
95
|
+
const saved: PersistedWalletState = opts.restore === false
|
|
96
|
+
? {}
|
|
97
|
+
: loadWalletState(opts.network, { cwd: opts.cwd });
|
|
98
|
+
|
|
99
|
+
const restored = { shielded: false, unshielded: false, dust: false };
|
|
100
|
+
|
|
101
|
+
const walletConfig = {
|
|
102
|
+
networkId,
|
|
103
|
+
indexerClientConnection: {
|
|
104
|
+
indexerHttpUrl: opts.networkConfig.indexer,
|
|
105
|
+
indexerWsUrl: opts.networkConfig.indexerWS,
|
|
106
|
+
},
|
|
107
|
+
provingServerUrl: new URL(opts.networkConfig.proofServer),
|
|
108
|
+
relayURL: new URL(opts.networkConfig.node.replace(/^http/, 'ws')),
|
|
109
|
+
txHistoryStorage: new NoOpTransactionHistoryStorage(),
|
|
110
|
+
costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const wallet = await WalletFacade.init({
|
|
114
|
+
configuration: walletConfig,
|
|
115
|
+
shielded: async (config) => {
|
|
116
|
+
const cls = ShieldedWallet(config);
|
|
117
|
+
if (saved.shielded !== undefined) {
|
|
118
|
+
try {
|
|
119
|
+
const restoredWallet = await (cls as any).restore(saved.shielded);
|
|
120
|
+
restored.shielded = true;
|
|
121
|
+
return restoredWallet;
|
|
122
|
+
} catch (err) {
|
|
123
|
+
warnRestoreFailure('shielded', err);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return cls.startWithSecretKeys(shieldedSecretKeys);
|
|
127
|
+
},
|
|
128
|
+
unshielded: async (config) => {
|
|
129
|
+
const cls = UnshieldedWallet(config);
|
|
130
|
+
if (saved.unshielded !== undefined) {
|
|
131
|
+
try {
|
|
132
|
+
const restoredWallet = await (cls as any).restore(saved.unshielded);
|
|
133
|
+
restored.unshielded = true;
|
|
134
|
+
return restoredWallet;
|
|
135
|
+
} catch (err) {
|
|
136
|
+
warnRestoreFailure('unshielded', err);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return cls.startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore));
|
|
140
|
+
},
|
|
141
|
+
dust: async (config) => {
|
|
142
|
+
const cls = DustWallet(config);
|
|
143
|
+
if (saved.dust !== undefined) {
|
|
144
|
+
try {
|
|
145
|
+
const restoredWallet = await (cls as any).restore(saved.dust);
|
|
146
|
+
restored.dust = true;
|
|
147
|
+
return restoredWallet;
|
|
148
|
+
} catch (err) {
|
|
149
|
+
warnRestoreFailure('dust', err);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return cls.startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust);
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await wallet.start(shieldedSecretKeys, dustSecretKey);
|
|
157
|
+
|
|
158
|
+
return { wallet, shieldedSecretKeys, dustSecretKey, unshieldedKeystore, restored };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Serialize each child wallet's current state and persist it for the next run.
|
|
163
|
+
* Safe to call multiple times. Logs but does not throw on individual failures —
|
|
164
|
+
* losing one child's state means the next run re-syncs that child only.
|
|
165
|
+
*/
|
|
166
|
+
export async function persistWalletState(
|
|
167
|
+
network: NetworkId,
|
|
168
|
+
ctx: WalletContext,
|
|
169
|
+
cwd?: string,
|
|
170
|
+
): Promise<void> {
|
|
171
|
+
const next: PersistedWalletState = {};
|
|
172
|
+
|
|
173
|
+
for (const kind of CHILD_KINDS) {
|
|
174
|
+
try {
|
|
175
|
+
const child = (ctx.wallet as unknown as Record<ChildKind, { serializeState: () => Promise<unknown> }>)[kind];
|
|
176
|
+
const serialized = await child.serializeState();
|
|
177
|
+
if (kind === 'dust') {
|
|
178
|
+
next.dust = serialized as string;
|
|
179
|
+
} else {
|
|
180
|
+
next[kind] = serialized;
|
|
181
|
+
}
|
|
182
|
+
} catch (err) {
|
|
183
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
184
|
+
process.stderr.write(` ⚠ Could not serialize ${kind} wallet state (${msg}); next run will re-sync.\n`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
saveWalletState(network, next, { cwd });
|
|
189
|
+
}
|