create-mn-app 0.3.9 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/create-app.d.ts.map +1 -1
- package/dist/create-app.js +3 -15
- package/dist/create-app.js.map +1 -1
- package/dist/installers/proof-server-setup.d.ts.map +1 -1
- package/dist/installers/proof-server-setup.js +2 -2
- package/dist/installers/proof-server-setup.js.map +1 -1
- package/dist/test.js +2 -4
- package/dist/test.js.map +1 -1
- package/dist/utils/template-manager.d.ts.map +1 -1
- package/dist/utils/template-manager.js +0 -3
- package/dist/utils/template-manager.js.map +1 -1
- package/package.json +1 -1
- package/templates/hello-world/README.md.template +6 -15
- package/templates/hello-world/docker-compose.yml.template +8 -0
- package/templates/hello-world/package.json.template +24 -27
- package/templates/hello-world/src/check-balance.ts.template +121 -84
- package/templates/hello-world/src/cli.ts.template +225 -145
- package/templates/hello-world/src/deploy.ts.template +236 -182
- package/templates/hello-world/_env.template +0 -0
- package/templates/hello-world/nodemon.json.template +0 -7
- package/templates/hello-world/src/health-check.ts.template +0 -127
- package/templates/hello-world/src/providers/midnight-providers.ts.template +0 -50
- package/templates/hello-world/src/utils/environment.ts.template +0 -76
|
@@ -1,188 +1,268 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
/**
|
|
2
|
+
* CLI for interacting with {{projectName}} contract
|
|
3
|
+
*/
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { stdin, stdout } from 'node:process';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
|
+
import { WebSocket } from 'ws';
|
|
10
|
+
import * as Rx from 'rxjs';
|
|
11
|
+
import { Buffer } from 'buffer';
|
|
12
|
+
|
|
13
|
+
// Midnight SDK imports
|
|
14
|
+
import { findDeployedContract } from '@midnight-ntwrk/midnight-js-contracts';
|
|
15
|
+
import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client-proof-provider';
|
|
16
|
+
import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
|
|
17
|
+
import { levelPrivateStateProvider } from '@midnight-ntwrk/midnight-js-level-private-state-provider';
|
|
18
|
+
import { NodeZkConfigProvider } from '@midnight-ntwrk/midnight-js-node-zk-config-provider';
|
|
19
|
+
import { setNetworkId, getNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
|
|
20
|
+
import * as ledger from '@midnight-ntwrk/ledger-v7';
|
|
21
|
+
import { unshieldedToken } from '@midnight-ntwrk/ledger-v7';
|
|
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';
|
|
27
|
+
import { CompiledContract } from '@midnight-ntwrk/compact-js';
|
|
28
|
+
|
|
29
|
+
// Enable WebSocket for GraphQL subscriptions
|
|
30
|
+
// @ts-expect-error Required for wallet sync
|
|
21
31
|
globalThis.WebSocket = WebSocket;
|
|
22
32
|
|
|
23
|
-
//
|
|
24
|
-
|
|
33
|
+
// Set network to preprod
|
|
34
|
+
setNetworkId('preprod');
|
|
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
|
+
};
|
|
43
|
+
|
|
44
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
45
|
+
const zkConfigPath = path.resolve(__dirname, '..', 'contracts', 'managed', 'hello-world');
|
|
46
|
+
|
|
47
|
+
// Load compiled contract
|
|
48
|
+
const contractPath = path.join(zkConfigPath, 'contract', 'index.js');
|
|
49
|
+
|
|
50
|
+
// Check if contract is compiled
|
|
51
|
+
if (!fs.existsSync(contractPath)) {
|
|
52
|
+
console.error('\n❌ Contract not compiled! Run: npm run compile\n');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const HelloWorld = await import(pathToFileURL(contractPath).href);
|
|
57
|
+
|
|
58
|
+
const compiledContract = CompiledContract.make('hello-world', HelloWorld.Contract).pipe(
|
|
59
|
+
CompiledContract.withVacantWitnesses,
|
|
60
|
+
CompiledContract.withCompiledFileAssets(zkConfigPath),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// ─── Wallet Functions ──────────────────────────────────────────────────────────
|
|
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
|
+
};
|
|
87
|
+
|
|
88
|
+
const shieldedWallet = ShieldedWallet(walletConfig).startWithSecretKeys(shieldedSecretKeys);
|
|
89
|
+
const unshieldedWallet = UnshieldedWallet({
|
|
90
|
+
networkId,
|
|
91
|
+
indexerClientConnection: walletConfig.indexerClientConnection,
|
|
92
|
+
txHistoryStorage: new InMemoryTransactionHistoryStorage(),
|
|
93
|
+
}).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore));
|
|
94
|
+
const dustWallet = DustWallet({
|
|
95
|
+
...walletConfig,
|
|
96
|
+
costParameters: { additionalFeeOverhead: 300_000_000_000_000n, feeBlocksMargin: 5 },
|
|
97
|
+
}).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust);
|
|
98
|
+
|
|
99
|
+
const wallet = new WalletFacade(shieldedWallet, unshieldedWallet, dustWallet);
|
|
100
|
+
await wallet.start(shieldedSecretKeys, dustSecretKey);
|
|
101
|
+
|
|
102
|
+
return { wallet, shieldedSecretKeys, dustSecretKey, unshieldedKeystore };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Workaround for wallet SDK signRecipe bug
|
|
106
|
+
function signTransactionIntents(tx: { intents?: Map<number, any> }, signFn: (payload: Uint8Array) => ledger.Signature, proofMarker: 'proof' | 'pre-proof'): void {
|
|
107
|
+
if (!tx.intents || tx.intents.size === 0) return;
|
|
108
|
+
for (const segment of tx.intents.keys()) {
|
|
109
|
+
const intent = tx.intents.get(segment);
|
|
110
|
+
if (!intent) continue;
|
|
111
|
+
const cloned = ledger.Intent.deserialize<ledger.SignatureEnabled, ledger.Proofish, ledger.PreBinding>('signature', proofMarker, 'pre-binding', intent.serialize());
|
|
112
|
+
const sigData = cloned.signatureData(segment);
|
|
113
|
+
const signature = signFn(sigData);
|
|
114
|
+
if (cloned.fallibleUnshieldedOffer) {
|
|
115
|
+
const sigs = cloned.fallibleUnshieldedOffer.inputs.map((_: any, i: number) => cloned.fallibleUnshieldedOffer!.signatures.at(i) ?? signature);
|
|
116
|
+
cloned.fallibleUnshieldedOffer = cloned.fallibleUnshieldedOffer.addSignatures(sigs);
|
|
117
|
+
}
|
|
118
|
+
if (cloned.guaranteedUnshieldedOffer) {
|
|
119
|
+
const sigs = cloned.guaranteedUnshieldedOffer.inputs.map((_: any, i: number) => cloned.guaranteedUnshieldedOffer!.signatures.at(i) ?? signature);
|
|
120
|
+
cloned.guaranteedUnshieldedOffer = cloned.guaranteedUnshieldedOffer.addSignatures(sigs);
|
|
121
|
+
}
|
|
122
|
+
tx.intents.set(segment, cloned);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function createProviders(walletCtx: ReturnType<typeof createWallet> extends Promise<infer T> ? T : never) {
|
|
127
|
+
const state = await Rx.firstValueFrom(walletCtx.wallet.state().pipe(Rx.filter((s) => s.isSynced)));
|
|
128
|
+
|
|
129
|
+
const walletProvider = {
|
|
130
|
+
getCoinPublicKey: () => state.shielded.coinPublicKey.toHexString(),
|
|
131
|
+
getEncryptionPublicKey: () => state.shielded.encryptionPublicKey.toHexString(),
|
|
132
|
+
async balanceTx(tx: any, ttl?: Date) {
|
|
133
|
+
const recipe = await walletCtx.wallet.balanceUnboundTransaction(
|
|
134
|
+
tx,
|
|
135
|
+
{ shieldedSecretKeys: walletCtx.shieldedSecretKeys, dustSecretKey: walletCtx.dustSecretKey },
|
|
136
|
+
{ ttl: ttl ?? new Date(Date.now() + 30 * 60 * 1000) },
|
|
137
|
+
);
|
|
138
|
+
const signFn = (payload: Uint8Array) => walletCtx.unshieldedKeystore.signData(payload);
|
|
139
|
+
signTransactionIntents(recipe.baseTransaction, signFn, 'proof');
|
|
140
|
+
if (recipe.balancingTransaction) signTransactionIntents(recipe.balancingTransaction, signFn, 'pre-proof');
|
|
141
|
+
return walletCtx.wallet.finalizeRecipe(recipe);
|
|
142
|
+
},
|
|
143
|
+
submitTx: (tx: any) => walletCtx.wallet.submitTransaction(tx) as any,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const zkConfigProvider = new NodeZkConfigProvider(zkConfigPath);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
privateStateProvider: levelPrivateStateProvider({ privateStateStoreName: 'hello-world-state', walletProvider }),
|
|
150
|
+
publicDataProvider: indexerPublicDataProvider(CONFIG.indexer, CONFIG.indexerWS),
|
|
151
|
+
zkConfigProvider,
|
|
152
|
+
proofProvider: httpClientProofProvider(CONFIG.proofServer, zkConfigProvider),
|
|
153
|
+
walletProvider,
|
|
154
|
+
midnightProvider: walletProvider,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─── Main CLI ──────────────────────────────────────────────────────────────────
|
|
25
159
|
|
|
26
160
|
async function main() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
161
|
+
console.log('\n╔══════════════════════════════════════════════════════════════╗');
|
|
162
|
+
console.log('║ {{projectName}} CLI ║');
|
|
163
|
+
console.log('╚══════════════════════════════════════════════════════════════╝\n');
|
|
164
|
+
|
|
165
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
166
|
+
|
|
167
|
+
// Check for deployment
|
|
168
|
+
if (!fs.existsSync('deployment.json')) {
|
|
169
|
+
console.error('❌ No deployment.json found! Run: npm run deploy\n');
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
31
172
|
|
|
32
|
-
|
|
173
|
+
const deployment = JSON.parse(fs.readFileSync('deployment.json', 'utf-8'));
|
|
174
|
+
console.log(` Contract: ${deployment.contractAddress}`);
|
|
175
|
+
console.log(` Network: ${deployment.network || 'preprod'}\n`);
|
|
33
176
|
|
|
34
177
|
try {
|
|
35
|
-
//
|
|
36
|
-
|
|
178
|
+
// Create wallet from saved seed
|
|
179
|
+
console.log(' Connecting to wallet...');
|
|
180
|
+
const walletCtx = await createWallet(deployment.seed);
|
|
37
181
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
182
|
+
console.log(' Syncing with network...');
|
|
183
|
+
const state = await Rx.firstValueFrom(walletCtx.wallet.state().pipe(Rx.throttleTime(5000), Rx.filter((s) => s.isSynced)));
|
|
184
|
+
const balance = state.unshielded.balances[unshieldedToken().raw] ?? 0n;
|
|
185
|
+
console.log(` Balance: ${balance.toLocaleString()} tNight\n`);
|
|
43
186
|
|
|
44
|
-
|
|
45
|
-
console.log(
|
|
46
|
-
|
|
47
|
-
const contractName =
|
|
48
|
-
deployment.contractName || process.env.CONTRACT_NAME || "hello-world";
|
|
49
|
-
const walletSeed = process.env.WALLET_SEED!;
|
|
50
|
-
|
|
51
|
-
console.log("Connecting to Midnight network...");
|
|
52
|
-
|
|
53
|
-
// Build wallet
|
|
54
|
-
const wallet = await WalletBuilder.buildFromSeed(
|
|
55
|
-
networkConfig.indexer,
|
|
56
|
-
networkConfig.indexerWS,
|
|
57
|
-
networkConfig.proofServer,
|
|
58
|
-
networkConfig.node,
|
|
59
|
-
walletSeed,
|
|
60
|
-
getZswapNetworkId(),
|
|
61
|
-
"info"
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
wallet.start();
|
|
65
|
-
|
|
66
|
-
// Wait for sync
|
|
67
|
-
await Rx.firstValueFrom(
|
|
68
|
-
wallet.state().pipe(Rx.filter((s) => s.syncProgress?.synced === true))
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
// Load contract
|
|
72
|
-
const contractPath = path.join(process.cwd(), "contracts");
|
|
73
|
-
const contractModulePath = path.join(
|
|
74
|
-
contractPath,
|
|
75
|
-
"managed",
|
|
76
|
-
contractName,
|
|
77
|
-
"contract",
|
|
78
|
-
"index.cjs"
|
|
79
|
-
);
|
|
80
|
-
const HelloWorldModule = await import(contractModulePath);
|
|
81
|
-
const contractInstance = new HelloWorldModule.Contract({});
|
|
82
|
-
|
|
83
|
-
// Create wallet provider
|
|
84
|
-
const walletState = await Rx.firstValueFrom(wallet.state());
|
|
85
|
-
|
|
86
|
-
const walletProvider = {
|
|
87
|
-
coinPublicKey: walletState.coinPublicKey,
|
|
88
|
-
encryptionPublicKey: walletState.encryptionPublicKey,
|
|
89
|
-
balanceTx(tx: any, newCoins: any) {
|
|
90
|
-
return wallet
|
|
91
|
-
.balanceTransaction(
|
|
92
|
-
ZswapTransaction.deserialize(
|
|
93
|
-
tx.serialize(getLedgerNetworkId()),
|
|
94
|
-
getZswapNetworkId()
|
|
95
|
-
),
|
|
96
|
-
newCoins
|
|
97
|
-
)
|
|
98
|
-
.then((tx) => wallet.proveTransaction(tx))
|
|
99
|
-
.then((zswapTx) =>
|
|
100
|
-
Transaction.deserialize(
|
|
101
|
-
zswapTx.serialize(getZswapNetworkId()),
|
|
102
|
-
getLedgerNetworkId()
|
|
103
|
-
)
|
|
104
|
-
)
|
|
105
|
-
.then(createBalancedTx);
|
|
106
|
-
},
|
|
107
|
-
submitTx(tx: any) {
|
|
108
|
-
return wallet.submitTransaction(tx);
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// Configure providers
|
|
113
|
-
const providers = MidnightProviders.create({
|
|
114
|
-
contractName,
|
|
115
|
-
walletProvider,
|
|
116
|
-
networkConfig,
|
|
117
|
-
});
|
|
187
|
+
// Setup providers and connect to contract
|
|
188
|
+
console.log(' Connecting to contract...');
|
|
189
|
+
const providers = await createProviders(walletCtx);
|
|
118
190
|
|
|
119
|
-
// Connect to contract
|
|
120
191
|
const deployed: any = await findDeployedContract(providers, {
|
|
192
|
+
compiledContract,
|
|
121
193
|
contractAddress: deployment.contractAddress,
|
|
122
|
-
|
|
123
|
-
privateStateId: "helloWorldState",
|
|
194
|
+
privateStateId: 'helloWorldState',
|
|
124
195
|
initialPrivateState: {},
|
|
125
196
|
});
|
|
126
197
|
|
|
127
|
-
console.log(
|
|
198
|
+
console.log(' ✅ Connected!\n');
|
|
128
199
|
|
|
129
|
-
//
|
|
200
|
+
// Interactive CLI loop
|
|
130
201
|
let running = true;
|
|
131
202
|
while (running) {
|
|
132
|
-
console.log(
|
|
133
|
-
console.log(
|
|
134
|
-
console.log(
|
|
135
|
-
console.log(
|
|
203
|
+
console.log('─── Menu ───────────────────────────────────────────────────────');
|
|
204
|
+
console.log(' 1. Store a message');
|
|
205
|
+
console.log(' 2. Read current message');
|
|
206
|
+
console.log(' 3. Check wallet balance');
|
|
207
|
+
console.log(' 4. Exit\n');
|
|
136
208
|
|
|
137
|
-
const choice = await rl.question(
|
|
209
|
+
const choice = await rl.question(' Your choice: ');
|
|
138
210
|
|
|
139
|
-
switch (choice) {
|
|
140
|
-
case
|
|
141
|
-
|
|
142
|
-
|
|
211
|
+
switch (choice.trim()) {
|
|
212
|
+
case '1': {
|
|
213
|
+
const message = await rl.question(' Enter your message: ');
|
|
214
|
+
console.log('\n Submitting transaction (this may take 30-60 seconds)...');
|
|
143
215
|
try {
|
|
144
|
-
const tx = await deployed.callTx.storeMessage(
|
|
145
|
-
console.log(
|
|
146
|
-
console.log(`
|
|
147
|
-
console.log(`
|
|
148
|
-
console.log(`Block height: ${tx.public.blockHeight}\n`);
|
|
216
|
+
const tx = await deployed.callTx.storeMessage(message);
|
|
217
|
+
console.log(`\n ✅ Message stored: "${message}"`);
|
|
218
|
+
console.log(` Transaction ID: ${tx.public.txId}`);
|
|
219
|
+
console.log(` Block height: ${tx.public.blockHeight}\n`);
|
|
149
220
|
} catch (error) {
|
|
150
|
-
console.error(
|
|
221
|
+
console.error('\n ❌ Failed:', error instanceof Error ? error.message : error);
|
|
151
222
|
}
|
|
152
223
|
break;
|
|
224
|
+
}
|
|
153
225
|
|
|
154
|
-
case
|
|
155
|
-
console.log(
|
|
226
|
+
case '2': {
|
|
227
|
+
console.log('\n Reading message from blockchain...');
|
|
156
228
|
try {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const message = Buffer.from(ledger.message).toString();
|
|
163
|
-
console.log(`📋 Current message: "${message}"\n`);
|
|
229
|
+
const contractState = await providers.publicDataProvider.queryContractState(deployment.contractAddress);
|
|
230
|
+
if (contractState) {
|
|
231
|
+
const ledgerState = HelloWorld.ledger(contractState.data);
|
|
232
|
+
const message = Buffer.from(ledgerState.message).toString();
|
|
233
|
+
console.log(`\n 📋 Current message: "${message}"\n`);
|
|
164
234
|
} else {
|
|
165
|
-
console.log(
|
|
235
|
+
console.log('\n 📋 No message found (contract state empty)\n');
|
|
166
236
|
}
|
|
167
237
|
} catch (error) {
|
|
168
|
-
console.error(
|
|
238
|
+
console.error('\n ❌ Failed:', error instanceof Error ? error.message : error);
|
|
169
239
|
}
|
|
170
240
|
break;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
case '3': {
|
|
244
|
+
console.log('\n Checking balance...');
|
|
245
|
+
const currentState = await Rx.firstValueFrom(walletCtx.wallet.state().pipe(Rx.filter((s) => s.isSynced)));
|
|
246
|
+
const currentBalance = currentState.unshielded.balances[unshieldedToken().raw] ?? 0n;
|
|
247
|
+
const dustBalance = currentState.dust.walletBalance(new Date());
|
|
248
|
+
console.log(`\n tNight: ${currentBalance.toLocaleString()}`);
|
|
249
|
+
console.log(` DUST: ${dustBalance.toLocaleString()}\n`);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
171
252
|
|
|
172
|
-
case
|
|
253
|
+
case '4':
|
|
173
254
|
running = false;
|
|
174
|
-
console.log(
|
|
255
|
+
console.log('\n 👋 Goodbye!\n');
|
|
175
256
|
break;
|
|
176
257
|
|
|
177
258
|
default:
|
|
178
|
-
console.log(
|
|
259
|
+
console.log('\n ❌ Invalid choice. Please enter 1-4.\n');
|
|
179
260
|
}
|
|
180
261
|
}
|
|
181
262
|
|
|
182
|
-
|
|
183
|
-
await wallet.close();
|
|
263
|
+
await walletCtx.wallet.stop();
|
|
184
264
|
} catch (error) {
|
|
185
|
-
console.error(
|
|
265
|
+
console.error('\n❌ Error:', error instanceof Error ? error.message : error);
|
|
186
266
|
} finally {
|
|
187
267
|
rl.close();
|
|
188
268
|
}
|