create-mn-app 0.3.28 → 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/README.md +75 -89
- package/dist/utils/templates.js +4 -4
- package/package.json +1 -1
- package/templates/hello-world/README.md.template +160 -60
- package/templates/hello-world/_gitignore +6 -1
- package/templates/hello-world/contracts/hello-world.compact.template +1 -1
- package/templates/hello-world/docker-compose.yml.template +66 -5
- package/templates/hello-world/package.json.template +17 -15
- package/templates/hello-world/scripts/e2e-check.ts.template +124 -0
- package/templates/hello-world/src/check-balance.ts.template +26 -84
- package/templates/hello-world/src/cli.ts.template +40 -76
- package/templates/hello-world/src/deploy.ts.template +236 -348
- package/templates/hello-world/src/network.ts +320 -0
- package/templates/hello-world/src/setup.ts.template +37 -0
- package/templates/hello-world/src/wallet-state.ts +95 -0
- package/templates/hello-world/src/wallet.ts +189 -0
- package/templates/hello-world/tsconfig.json.template +1 -1
- package/dist/installers/wallet-generator.d.ts +0 -4
- package/dist/installers/wallet-generator.d.ts.map +0 -1
- package/dist/installers/wallet-generator.js +0 -78
- package/dist/installers/wallet-generator.js.map +0 -1
- package/dist/test.d.ts +0 -2
- package/dist/test.d.ts.map +0 -1
- package/dist/test.js +0 -65
- package/dist/test.js.map +0 -1
|
@@ -8,40 +8,42 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"compile": "compact compile contracts/hello-world.compact contracts/managed/hello-world",
|
|
10
10
|
"build": "npx tsc --noEmit || true",
|
|
11
|
-
"setup": "
|
|
11
|
+
"setup": "npx tsx src/setup.ts",
|
|
12
12
|
"deploy": "npx tsx src/deploy.ts",
|
|
13
13
|
"cli": "npx tsx src/cli.ts",
|
|
14
14
|
"check-balance": "npx tsx src/check-balance.ts",
|
|
15
|
+
"network": "npx tsx src/network.ts",
|
|
15
16
|
"proof-server:start": "docker compose up -d",
|
|
16
17
|
"proof-server:stop": "docker compose down",
|
|
17
|
-
"clean": "rm -rf contracts/managed
|
|
18
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
18
|
+
"clean": "rm -rf contracts/managed .midnight-state.json .midnight-wallet-state",
|
|
19
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
20
|
+
"test:e2e": "tsx scripts/e2e-check.ts"
|
|
19
21
|
},
|
|
20
22
|
"dependencies": {
|
|
21
23
|
"@midnight-ntwrk/compact-js": "2.5.0",
|
|
22
|
-
"rxjs": "^7.8.
|
|
23
|
-
"@midnight-ntwrk/compact-runtime": "0.
|
|
24
|
+
"rxjs": "^7.8.2",
|
|
25
|
+
"@midnight-ntwrk/compact-runtime": "0.16.0",
|
|
24
26
|
"@midnight-ntwrk/ledger-v8": "8.0.3",
|
|
25
|
-
"@midnight-ntwrk/midnight-js-contracts": "4.0.
|
|
26
|
-
"@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.0.
|
|
27
|
-
"@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.0.
|
|
28
|
-
"@midnight-ntwrk/midnight-js-level-private-state-provider": "4.0.
|
|
29
|
-
"@midnight-ntwrk/midnight-js-network-id": "4.0.
|
|
30
|
-
"@midnight-ntwrk/midnight-js-node-zk-config-provider": "4.0.
|
|
31
|
-
"@midnight-ntwrk/midnight-js-types": "4.0.
|
|
32
|
-
"@midnight-ntwrk/midnight-js-utils": "4.0.
|
|
27
|
+
"@midnight-ntwrk/midnight-js-contracts": "4.0.4",
|
|
28
|
+
"@midnight-ntwrk/midnight-js-http-client-proof-provider": "4.0.4",
|
|
29
|
+
"@midnight-ntwrk/midnight-js-indexer-public-data-provider": "4.0.4",
|
|
30
|
+
"@midnight-ntwrk/midnight-js-level-private-state-provider": "4.0.4",
|
|
31
|
+
"@midnight-ntwrk/midnight-js-network-id": "4.0.4",
|
|
32
|
+
"@midnight-ntwrk/midnight-js-node-zk-config-provider": "4.0.4",
|
|
33
|
+
"@midnight-ntwrk/midnight-js-types": "4.0.4",
|
|
34
|
+
"@midnight-ntwrk/midnight-js-utils": "4.0.4",
|
|
33
35
|
"@midnight-ntwrk/wallet-sdk-dust-wallet": "3.0.0",
|
|
34
36
|
"@midnight-ntwrk/wallet-sdk-facade": "3.0.0",
|
|
35
37
|
"@midnight-ntwrk/wallet-sdk-hd": "3.0.1",
|
|
36
38
|
"@midnight-ntwrk/wallet-sdk-shielded": "2.1.0",
|
|
37
39
|
"@midnight-ntwrk/wallet-sdk-unshielded-wallet": "2.1.0",
|
|
38
|
-
"ws": "^8.
|
|
40
|
+
"ws": "^8.20.0"
|
|
39
41
|
},
|
|
40
42
|
"devDependencies": {
|
|
41
43
|
"@types/node": "^22.0.0",
|
|
42
44
|
"@types/ws": "^8.18.1",
|
|
43
45
|
"tsx": "^4.21.0",
|
|
44
|
-
"typescript": "^
|
|
46
|
+
"typescript": "^6.0.3"
|
|
45
47
|
},
|
|
46
48
|
"engines": {
|
|
47
49
|
"node": ">=22.0.0"
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end smoke check for {{projectName}}.
|
|
3
|
+
*
|
|
4
|
+
* Reconnects to the deployed contract, reads its ledger state, and exits 0
|
|
5
|
+
* on success. Used by `npm run test:e2e` and by the project's CI workflows.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
10
|
+
import { WebSocket } from 'ws';
|
|
11
|
+
|
|
12
|
+
import { findDeployedContract } from '@midnight-ntwrk/midnight-js-contracts';
|
|
13
|
+
import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client-proof-provider';
|
|
14
|
+
import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
|
|
15
|
+
import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
|
|
16
|
+
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
|
|
17
|
+
import { resolveNetwork, getOrCreateSeed, getDeployment } from '../src/network';
|
|
18
|
+
import { createWallet, persistWalletState } from '../src/wallet';
|
|
19
|
+
import { CompiledContract } from '@midnight-ntwrk/compact-js';
|
|
20
|
+
|
|
21
|
+
// @ts-expect-error wallet sync requires WebSocket
|
|
22
|
+
globalThis.WebSocket = WebSocket;
|
|
23
|
+
|
|
24
|
+
// ─── Network configuration ─────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const { network, config: networkConfig } = resolveNetwork();
|
|
27
|
+
const SEED = getOrCreateSeed(network);
|
|
28
|
+
|
|
29
|
+
function fail(msg: string): never {
|
|
30
|
+
console.error(`❌ e2e-check failed: ${msg}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isHexAddress(s: unknown): s is string {
|
|
35
|
+
return typeof s === 'string' && /^[0-9a-fA-F]+$/.test(s) && s.length >= 32;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
// 1. Deployment sanity
|
|
40
|
+
const deployment = getDeployment(network);
|
|
41
|
+
if (!deployment) {
|
|
42
|
+
console.error(`No deploy on file for network ${network}.`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
if (!isHexAddress(deployment.address)) {
|
|
46
|
+
fail(`Deployment address missing or invalid: ${JSON.stringify(deployment, null, 2)}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 2. Build wallet and providers
|
|
50
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
51
|
+
const zkConfigPath = path.resolve(__dirname, '..', 'contracts', 'managed', 'hello-world');
|
|
52
|
+
const contractPath = path.join(zkConfigPath, 'contract', 'index.js');
|
|
53
|
+
if (!fs.existsSync(contractPath)) fail('Compiled contract missing — run `npm run compile`.');
|
|
54
|
+
const HelloWorld = await import(pathToFileURL(contractPath).href);
|
|
55
|
+
const compiledContract = CompiledContract.make('hello-world', HelloWorld.Contract).pipe(
|
|
56
|
+
CompiledContract.withVacantWitnesses,
|
|
57
|
+
CompiledContract.withCompiledFileAssets(zkConfigPath),
|
|
58
|
+
);
|
|
59
|
+
|
|
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);
|
|
65
|
+
|
|
66
|
+
const zkConfigProvider = new NodeZkConfigProvider(zkConfigPath);
|
|
67
|
+
const walletProvider = {
|
|
68
|
+
getCoinPublicKey: () => state.shielded.coinPublicKey.toHexString(),
|
|
69
|
+
getEncryptionPublicKey: () => state.shielded.encryptionPublicKey.toHexString(),
|
|
70
|
+
async balanceTx() {
|
|
71
|
+
throw new Error('e2e-check is read-only and should not balance transactions');
|
|
72
|
+
},
|
|
73
|
+
submitTx() {
|
|
74
|
+
throw new Error('e2e-check is read-only and should not submit transactions');
|
|
75
|
+
},
|
|
76
|
+
} as any;
|
|
77
|
+
|
|
78
|
+
const providers = {
|
|
79
|
+
privateStateProvider: levelPrivateStateProvider({
|
|
80
|
+
privateStateStoreName: 'hello-world-state',
|
|
81
|
+
accountId: walletCtx.unshieldedKeystore.getBech32Address().toString(),
|
|
82
|
+
// SDK requires ≥16 chars. e2e-check is read-only so we don't expose
|
|
83
|
+
// the env-var override here — match the deploy script's local-devnet default.
|
|
84
|
+
privateStoragePasswordProvider: () => 'Local-Devnet-Development-Placeholder-1',
|
|
85
|
+
}),
|
|
86
|
+
publicDataProvider: indexerPublicDataProvider(networkConfig.indexer, networkConfig.indexerWS),
|
|
87
|
+
zkConfigProvider,
|
|
88
|
+
proofProvider: httpClientProofProvider(networkConfig.proofServer, zkConfigProvider),
|
|
89
|
+
walletProvider,
|
|
90
|
+
midnightProvider: walletProvider,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// 3. Reconnect to the deployed contract — proves callTx interface is wired
|
|
94
|
+
try {
|
|
95
|
+
await findDeployedContract(providers, {
|
|
96
|
+
contractAddress: deployment.address,
|
|
97
|
+
compiledContract: compiledContract as any,
|
|
98
|
+
});
|
|
99
|
+
} catch (err: any) {
|
|
100
|
+
await walletCtx.wallet.stop();
|
|
101
|
+
fail(`findDeployedContract threw: ${err?.message ?? err}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 4. Read the on-chain contract state via the public data provider — proves
|
|
105
|
+
// the contract is indexed and queryable on the chain itself, not just that
|
|
106
|
+
// we know how to construct the local handle.
|
|
107
|
+
const onChainState = await providers.publicDataProvider.queryContractState(deployment.address);
|
|
108
|
+
if (!onChainState) {
|
|
109
|
+
await walletCtx.wallet.stop();
|
|
110
|
+
fail(`queryContractState returned null for ${deployment.address}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(`✅ e2e-check passed`);
|
|
114
|
+
console.log(` contractAddress: ${deployment.address}`);
|
|
115
|
+
console.log(` network: ${network}`);
|
|
116
|
+
|
|
117
|
+
await walletCtx.wallet.stop();
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
main().catch(async (err) => {
|
|
122
|
+
console.error(err);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
});
|
|
@@ -1,75 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Check wallet balance on Midnight
|
|
2
|
+
* Check wallet balance on the local Midnight devnet
|
|
3
3
|
*/
|
|
4
|
-
import * as fs from 'node:fs';
|
|
5
4
|
import { WebSocket } from 'ws';
|
|
6
|
-
import * as Rx from 'rxjs';
|
|
7
|
-
import { Buffer } from 'buffer';
|
|
8
5
|
|
|
9
6
|
// Midnight SDK imports
|
|
10
|
-
import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
|
|
11
|
-
import * as ledger from '@midnight-ntwrk/ledger-v8';
|
|
12
7
|
import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
|
|
16
|
-
import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
|
|
17
|
-
import { createKeystore, InMemoryTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
|
|
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
|
|
21
13
|
globalThis.WebSocket = WebSocket;
|
|
22
14
|
|
|
23
|
-
//
|
|
24
|
-
setNetworkId('preprod');
|
|
15
|
+
// ─── Network configuration ─────────────────────────────────────────────────────
|
|
25
16
|
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
indexer: 'https://indexer.preprod.midnight.network/api/v3/graphql',
|
|
29
|
-
indexerWS: 'wss://indexer.preprod.midnight.network/api/v3/graphql/ws',
|
|
30
|
-
node: 'https://rpc.preprod.midnight.network',
|
|
31
|
-
proofServer: 'http://127.0.0.1:6300',
|
|
32
|
-
faucetUrl: 'https://faucet.preprod.midnight.network/',
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// ─── Wallet Functions ──────────────────────────────────────────────────────────
|
|
36
|
-
|
|
37
|
-
function deriveKeys(seed: string) {
|
|
38
|
-
const hdWallet = HDWallet.fromSeed(Buffer.from(seed, 'hex'));
|
|
39
|
-
if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
|
|
40
|
-
const result = hdWallet.hdWallet.selectAccount(0).selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust]).deriveKeysAt(0);
|
|
41
|
-
if (result.type !== 'keysDerived') throw new Error('Key derivation failed');
|
|
42
|
-
hdWallet.hdWallet.clear();
|
|
43
|
-
return result.keys;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function createWallet(seed: string) {
|
|
47
|
-
const keys = deriveKeys(seed);
|
|
48
|
-
const networkId = getNetworkId();
|
|
49
|
-
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
|
|
50
|
-
const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);
|
|
51
|
-
const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], networkId);
|
|
52
|
-
|
|
53
|
-
const walletConfig = {
|
|
54
|
-
networkId,
|
|
55
|
-
indexerClientConnection: { indexerHttpUrl: CONFIG.indexer, indexerWsUrl: CONFIG.indexerWS },
|
|
56
|
-
provingServerUrl: new URL(CONFIG.proofServer),
|
|
57
|
-
relayURL: new URL(CONFIG.node.replace(/^http/, 'ws')),
|
|
58
|
-
txHistoryStorage: new InMemoryTransactionHistoryStorage(),
|
|
59
|
-
costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const wallet = await WalletFacade.init({
|
|
63
|
-
configuration: walletConfig,
|
|
64
|
-
shielded: async (config) => ShieldedWallet(config).startWithSecretKeys(shieldedSecretKeys),
|
|
65
|
-
unshielded: async (config) => UnshieldedWallet(config).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
|
|
66
|
-
dust: async (config) => DustWallet(config).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust),
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
await wallet.start(shieldedSecretKeys, dustSecretKey);
|
|
70
|
-
|
|
71
|
-
return { wallet, unshieldedKeystore };
|
|
72
|
-
}
|
|
17
|
+
const { network, config: networkConfig } = resolveNetwork();
|
|
18
|
+
const SEED = getOrCreateSeed(network);
|
|
73
19
|
|
|
74
20
|
// ─── Main ──────────────────────────────────────────────────────────────────────
|
|
75
21
|
|
|
@@ -78,23 +24,13 @@ async function main() {
|
|
|
78
24
|
console.log('║ Wallet Balance Checker ║');
|
|
79
25
|
console.log('╚══════════════════════════════════════════════════════════════╝\n');
|
|
80
26
|
|
|
81
|
-
// Check for deployment.json to get seed
|
|
82
|
-
if (!fs.existsSync('deployment.json')) {
|
|
83
|
-
console.error('❌ No deployment.json found! Run: npm run deploy\n');
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const deployment = JSON.parse(fs.readFileSync('deployment.json', 'utf-8'));
|
|
88
|
-
|
|
89
|
-
if (!fs.existsSync('.midnight-seed')) {
|
|
90
|
-
console.error('❌ No .midnight-seed file found! Run: npm run deploy\n');
|
|
91
|
-
process.exit(1);
|
|
92
|
-
}
|
|
93
|
-
const seed = fs.readFileSync('.midnight-seed', 'utf-8').trim();
|
|
94
|
-
|
|
95
27
|
try {
|
|
96
28
|
console.log(' Building wallet...');
|
|
97
|
-
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
|
+
}
|
|
98
34
|
|
|
99
35
|
console.log(' Syncing with network...');
|
|
100
36
|
console.log(' ℹ This may take several minutes depending on network size.');
|
|
@@ -104,33 +40,39 @@ async function main() {
|
|
|
104
40
|
const elapsed = Math.round((Date.now() - syncStart) / 1000);
|
|
105
41
|
process.stdout.write(`\r ⏳ Still syncing... (${elapsed}s elapsed) `);
|
|
106
42
|
}, 5000);
|
|
107
|
-
const state = await wallet.waitForSyncedState();
|
|
43
|
+
const state = await walletCtx.wallet.waitForSyncedState();
|
|
108
44
|
clearInterval(syncInterval);
|
|
109
45
|
process.stdout.write('\r ✓ Synced with network. \n');
|
|
110
46
|
|
|
111
|
-
const address = unshieldedKeystore.getBech32Address();
|
|
47
|
+
const address = walletCtx.unshieldedKeystore.getBech32Address();
|
|
112
48
|
const tNightBalance = state.unshielded.balances[unshieldedToken().raw] ?? 0n;
|
|
113
49
|
const dustBalance = state.dust.balance(new Date());
|
|
114
50
|
|
|
115
51
|
console.log('\n─── Wallet Details ─────────────────────────────────────────────\n');
|
|
116
52
|
console.log(` Address: ${address}`);
|
|
117
|
-
console.log(` Network:
|
|
53
|
+
console.log(` Network: ${networkConfig.networkId}\n`);
|
|
118
54
|
|
|
119
55
|
console.log('─── Balances ───────────────────────────────────────────────────\n');
|
|
120
56
|
console.log(` tNight: ${tNightBalance.toLocaleString()}`);
|
|
121
57
|
console.log(` DUST: ${dustBalance.toLocaleString()}\n`);
|
|
122
58
|
|
|
123
59
|
if (tNightBalance === 0n) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
60
|
+
if (network === 'undeployed') {
|
|
61
|
+
console.log(' ⚠ Wallet has no tNight. Make sure the local devnet is running');
|
|
62
|
+
console.log(' (npm run setup) — the genesis seed is pre-funded by the dev preset.\n');
|
|
63
|
+
} else if (networkConfig.faucet) {
|
|
64
|
+
console.log(` ⚠ Wallet has no tNight. Fund it from the faucet:`);
|
|
65
|
+
console.log(` ${networkConfig.faucet}`);
|
|
66
|
+
console.log(` Wallet address: ${address}\n`);
|
|
67
|
+
} else {
|
|
68
|
+
console.log(' ⚠ Wallet has no tNight.\n');
|
|
69
|
+
}
|
|
129
70
|
} else {
|
|
130
71
|
console.log(' ✅ Wallet is funded and ready!\n');
|
|
131
72
|
}
|
|
132
73
|
|
|
133
|
-
await
|
|
74
|
+
await persistWalletState(network, walletCtx);
|
|
75
|
+
await walletCtx.wallet.stop();
|
|
134
76
|
} catch (error) {
|
|
135
77
|
console.error('\n❌ Error:', error instanceof Error ? error.message : error);
|
|
136
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,30 +15,16 @@ 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 {
|
|
20
|
-
import
|
|
21
|
-
import { unshieldedToken } from '@midnight-ntwrk/ledger-v8';
|
|
22
|
-
import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
|
|
23
|
-
import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
|
|
24
|
-
import { HDWallet, Roles } from '@midnight-ntwrk/wallet-sdk-hd';
|
|
25
|
-
import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
|
|
26
|
-
import { createKeystore, InMemoryTransactionHistoryStorage, PublicKey, UnshieldedWallet } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
|
|
18
|
+
import { resolveNetwork, getOrCreateSeed, getDeployment } from './network';
|
|
19
|
+
import { createWallet, persistWalletState, unshieldedToken, type WalletContext } from './wallet';
|
|
27
20
|
import { CompiledContract } from '@midnight-ntwrk/compact-js';
|
|
28
21
|
|
|
29
22
|
// Enable WebSocket for GraphQL subscriptions
|
|
30
23
|
// @ts-expect-error Required for wallet sync
|
|
31
24
|
globalThis.WebSocket = WebSocket;
|
|
32
25
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Preprod network configuration
|
|
37
|
-
const CONFIG = {
|
|
38
|
-
indexer: 'https://indexer.preprod.midnight.network/api/v3/graphql',
|
|
39
|
-
indexerWS: 'wss://indexer.preprod.midnight.network/api/v3/graphql/ws',
|
|
40
|
-
node: 'https://rpc.preprod.midnight.network',
|
|
41
|
-
proofServer: 'http://127.0.0.1:6300',
|
|
42
|
-
};
|
|
26
|
+
const { network, config: networkConfig } = resolveNetwork();
|
|
27
|
+
const SEED = getOrCreateSeed(network);
|
|
43
28
|
|
|
44
29
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
45
30
|
const zkConfigPath = path.resolve(__dirname, '..', 'contracts', 'managed', 'hello-world');
|
|
@@ -60,47 +45,13 @@ const compiledContract = CompiledContract.make('hello-world', HelloWorld.Contrac
|
|
|
60
45
|
CompiledContract.withCompiledFileAssets(zkConfigPath),
|
|
61
46
|
);
|
|
62
47
|
|
|
63
|
-
// ───
|
|
64
|
-
|
|
65
|
-
function deriveKeys(seed: string) {
|
|
66
|
-
const hdWallet = HDWallet.fromSeed(Buffer.from(seed, 'hex'));
|
|
67
|
-
if (hdWallet.type !== 'seedOk') throw new Error('Invalid seed');
|
|
68
|
-
const result = hdWallet.hdWallet.selectAccount(0).selectRoles([Roles.Zswap, Roles.NightExternal, Roles.Dust]).deriveKeysAt(0);
|
|
69
|
-
if (result.type !== 'keysDerived') throw new Error('Key derivation failed');
|
|
70
|
-
hdWallet.hdWallet.clear();
|
|
71
|
-
return result.keys;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function createWallet(seed: string) {
|
|
75
|
-
const keys = deriveKeys(seed);
|
|
76
|
-
const networkId = getNetworkId();
|
|
77
|
-
const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(keys[Roles.Zswap]);
|
|
78
|
-
const dustSecretKey = ledger.DustSecretKey.fromSeed(keys[Roles.Dust]);
|
|
79
|
-
const unshieldedKeystore = createKeystore(keys[Roles.NightExternal], networkId);
|
|
80
|
-
|
|
81
|
-
const walletConfig = {
|
|
82
|
-
networkId,
|
|
83
|
-
indexerClientConnection: { indexerHttpUrl: CONFIG.indexer, indexerWsUrl: CONFIG.indexerWS },
|
|
84
|
-
provingServerUrl: new URL(CONFIG.proofServer),
|
|
85
|
-
relayURL: new URL(CONFIG.node.replace(/^http/, 'ws')),
|
|
86
|
-
txHistoryStorage: new InMemoryTransactionHistoryStorage(),
|
|
87
|
-
costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const wallet = await WalletFacade.init({
|
|
91
|
-
configuration: walletConfig,
|
|
92
|
-
shielded: async (config) => ShieldedWallet(config).startWithSecretKeys(shieldedSecretKeys),
|
|
93
|
-
unshielded: async (config) => UnshieldedWallet(config).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore)),
|
|
94
|
-
dust: async (config) => DustWallet(config).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust),
|
|
95
|
-
});
|
|
48
|
+
// ─── Providers ─────────────────────────────────────────────────────────────────
|
|
96
49
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
async function createProviders(walletCtx: ReturnType<typeof createWallet> extends Promise<infer T> ? T : never) {
|
|
103
|
-
const privateStatePassword = process.env.PRIVATE_STATE_PASSWORD?.trim() || 'development';
|
|
50
|
+
async function createProviders(walletCtx: WalletContext) {
|
|
51
|
+
// The SDK requires the private-state password to be at least 16 characters.
|
|
52
|
+
// The default below is a placeholder for local devnet only — set a strong
|
|
53
|
+
// password via PRIVATE_STATE_PASSWORD when you move to a non-local target.
|
|
54
|
+
const privateStatePassword = process.env.PRIVATE_STATE_PASSWORD?.trim() || 'Local-Devnet-Development-Placeholder-1';
|
|
104
55
|
|
|
105
56
|
const state = await walletCtx.wallet.waitForSyncedState();
|
|
106
57
|
|
|
@@ -130,9 +81,9 @@ async function createProviders(walletCtx: ReturnType<typeof createWallet> extend
|
|
|
130
81
|
accountId,
|
|
131
82
|
privateStoragePasswordProvider: () => privateStatePassword,
|
|
132
83
|
}),
|
|
133
|
-
publicDataProvider: indexerPublicDataProvider(
|
|
84
|
+
publicDataProvider: indexerPublicDataProvider(networkConfig.indexer, networkConfig.indexerWS),
|
|
134
85
|
zkConfigProvider,
|
|
135
|
-
proofProvider: httpClientProofProvider(
|
|
86
|
+
proofProvider: httpClientProofProvider(networkConfig.proofServer, zkConfigProvider),
|
|
136
87
|
walletProvider,
|
|
137
88
|
midnightProvider: walletProvider,
|
|
138
89
|
};
|
|
@@ -148,25 +99,23 @@ async function main() {
|
|
|
148
99
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
149
100
|
|
|
150
101
|
// Check for deployment
|
|
151
|
-
|
|
152
|
-
|
|
102
|
+
const deployment = getDeployment(network);
|
|
103
|
+
if (!deployment) {
|
|
104
|
+
console.error(`No deploy on file for network ${network}. Run \`npm run setup -- --network ${network}\` first.`);
|
|
153
105
|
process.exit(1);
|
|
154
106
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
console.log(` Contract: ${deployment.contractAddress}`);
|
|
158
|
-
console.log(` Network: ${deployment.network || 'preprod'}\n`);
|
|
107
|
+
console.log(` Contract: ${deployment.address}`);
|
|
108
|
+
console.log(` Network: ${network}\n`);
|
|
159
109
|
|
|
160
110
|
try {
|
|
161
|
-
|
|
162
|
-
if (!fs.existsSync('.midnight-seed')) {
|
|
163
|
-
console.error('❌ No .midnight-seed file found! Run: npm run deploy\n');
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}
|
|
166
|
-
const seed = fs.readFileSync('.midnight-seed', 'utf-8').trim();
|
|
111
|
+
const seed = SEED;
|
|
167
112
|
|
|
168
113
|
console.log(' Connecting to wallet...');
|
|
169
|
-
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
|
+
}
|
|
170
119
|
|
|
171
120
|
console.log(' Syncing with network...');
|
|
172
121
|
console.log(' ℹ This may take several minutes depending on network size.');
|
|
@@ -179,16 +128,30 @@ async function main() {
|
|
|
179
128
|
const state = await walletCtx.wallet.waitForSyncedState();
|
|
180
129
|
clearInterval(syncInterval);
|
|
181
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);
|
|
182
134
|
const balance = state.unshielded.balances[unshieldedToken().raw] ?? 0n;
|
|
183
135
|
console.log(` Balance: ${balance.toLocaleString()} tNight\n`);
|
|
184
136
|
|
|
137
|
+
// Surface a faucet hint when a public-network wallet has 0 tNIGHT.
|
|
138
|
+
// Reads (option 2) work without funds, but writes (option 1) need DUST
|
|
139
|
+
// generated from registered NIGHT — without this hint the next failure
|
|
140
|
+
// mode is a confusing "Insufficient Funds" deep inside the tx builder.
|
|
141
|
+
if (balance === 0n && network !== 'undeployed' && networkConfig.faucet) {
|
|
142
|
+
const address = walletCtx.unshieldedKeystore.getBech32Address();
|
|
143
|
+
console.log(' ⚠ Wallet has no tNight. Fund it from the faucet to send transactions:');
|
|
144
|
+
console.log(` ${networkConfig.faucet}`);
|
|
145
|
+
console.log(` Wallet address: ${address}\n`);
|
|
146
|
+
}
|
|
147
|
+
|
|
185
148
|
// Setup providers and connect to contract
|
|
186
149
|
console.log(' Connecting to contract...');
|
|
187
150
|
const providers = await createProviders(walletCtx);
|
|
188
151
|
|
|
189
152
|
const deployed: any = await findDeployedContract(providers, {
|
|
190
153
|
compiledContract: compiledContract as any,
|
|
191
|
-
contractAddress: deployment.
|
|
154
|
+
contractAddress: deployment.address,
|
|
192
155
|
});
|
|
193
156
|
|
|
194
157
|
console.log(' ✅ Connected!\n');
|
|
@@ -222,7 +185,7 @@ async function main() {
|
|
|
222
185
|
case '2': {
|
|
223
186
|
console.log('\n Reading message from blockchain...');
|
|
224
187
|
try {
|
|
225
|
-
const contractState = await providers.publicDataProvider.queryContractState(deployment.
|
|
188
|
+
const contractState = await providers.publicDataProvider.queryContractState(deployment.address);
|
|
226
189
|
if (contractState) {
|
|
227
190
|
const ledgerState = HelloWorld.ledger(contractState.data);
|
|
228
191
|
const message = Buffer.from(ledgerState.message).toString();
|
|
@@ -256,6 +219,7 @@ async function main() {
|
|
|
256
219
|
}
|
|
257
220
|
}
|
|
258
221
|
|
|
222
|
+
await persistWalletState(network, walletCtx);
|
|
259
223
|
await walletCtx.wallet.stop();
|
|
260
224
|
} catch (error) {
|
|
261
225
|
console.error('\n❌ Error:', error instanceof Error ? error.message : error);
|