@effectstream/midnight-contracts 0.100.27 → 0.101.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effectstream/midnight-contracts",
3
- "version": "0.100.27",
3
+ "version": "0.101.0",
4
4
  "license": "MIT",
5
5
  "homepage": "https://effectstream.github.io/docs/",
6
6
  "repository": {
@@ -21,7 +21,7 @@
21
21
  "./types": "./src/types.ts"
22
22
  },
23
23
  "dependencies": {
24
- "@effectstream/utils": "0.100.27",
24
+ "@effectstream/utils": "0.101.0",
25
25
  "@scure/bip39": "^2.0.1",
26
26
  "dotenv": "^16.4.7",
27
27
  "@midnight-ntwrk/zswap": "4.0.0",
package/src/deploy.ts CHANGED
@@ -92,6 +92,7 @@ export async function deployMidnightContract(
92
92
  config: DeployConfig,
93
93
  networkUrls?: NetworkUrls,
94
94
  seedOrMnemonic?: { seed: string; mnemonic: string },
95
+ opts?: { walletResult?: WalletResult },
95
96
  ): Promise<string> {
96
97
  checkEnvVariables();
97
98
 
@@ -141,30 +142,32 @@ export async function deployMidnightContract(
141
142
 
142
143
  setNetworkId(resolvedNetworkId);
143
144
 
144
- let walletResult: WalletResult | null = null;
145
+ let walletResult: WalletResult | null = opts?.walletResult ?? null;
145
146
  let providers: ReturnType<typeof configureMidnightNodeProviders> | null = null;
146
147
 
147
148
  try {
148
- log.info("Building wallet...");
149
- let seed;
150
- if (seedOrMnemonic?.seed) {
151
- seed = seedOrMnemonic.seed;
152
- } else if (seedOrMnemonic?.mnemonic) {
153
- seed = Buffer.from(
154
- await mnemonicToSeed(seedOrMnemonic.mnemonic),
155
- ).toString("hex");
156
- } else {
157
- seed = midnightNetworkConfig.walletSeed;
158
- }
159
- if (!seed) {
160
- throw new Error("No seed or mnemonic provided");
161
- }
149
+ if (!walletResult) {
150
+ log.info("Building wallet...");
151
+ let seed;
152
+ if (seedOrMnemonic?.seed) {
153
+ seed = seedOrMnemonic.seed;
154
+ } else if (seedOrMnemonic?.mnemonic) {
155
+ seed = Buffer.from(
156
+ await mnemonicToSeed(seedOrMnemonic.mnemonic),
157
+ ).toString("hex");
158
+ } else {
159
+ seed = midnightNetworkConfig.walletSeed;
160
+ }
161
+ if (!seed) {
162
+ throw new Error("No seed or mnemonic provided");
163
+ }
162
164
 
163
- walletResult = await buildWalletAndWaitForFunds(
164
- resolvedNetworkUrls,
165
- seed,
166
- resolvedNetworkId,
167
- );
165
+ walletResult = await buildWalletAndWaitForFunds(
166
+ resolvedNetworkUrls,
167
+ seed,
168
+ resolvedNetworkId,
169
+ );
170
+ }
168
171
 
169
172
  const {
170
173
  wallet,
@@ -174,7 +177,7 @@ export async function deployMidnightContract(
174
177
  walletDustSecretKey,
175
178
  dustAddress,
176
179
  unshieldedKeystore,
177
- } = walletResult;
180
+ } = walletResult!;
178
181
  const resolvedDustReceiverAddress =
179
182
  getEnv("MIDNIGHT_DUST_RECEIVER_ADDRESS") ?? dustAddress;
180
183
  if (resolvedDustReceiverAddress === dustAddress) {
@@ -0,0 +1,213 @@
1
+ // CLI entry for inspecting a Midnight wallet (prints addresses, optional balances).
2
+ //
3
+ // Split out of get-wallet-info.ts so that module stays browser-bundle-safe:
4
+ // `parseArgs` (node:util) and `dotenv` are Node-only, top-level imports. Because
5
+ // get-wallet-info.ts is reachable from frontends (via @effectstream/wallets →
6
+ // @effectstream/midnight-contracts/wallet-info → buildWalletFacade), those
7
+ // imports broke the browser bundle ("No matching export ... for import parseArgs").
8
+ // The reusable wallet/dust helpers stay in get-wallet-info.ts; only this CLI lives here.
9
+ //
10
+ // Run: bun run src/get-wallet-info-cli.ts [--seed <hex>] [--balance]
11
+ import dotenv from "dotenv";
12
+ import { parseArgs } from "node:util";
13
+ import { shieldedToken } from "@midnight-ntwrk/ledger-v8";
14
+ import { setNetworkId } from "@midnight-ntwrk/midnight-js-network-id";
15
+ import { NetworkId } from "@midnight-ntwrk/wallet-sdk-abstractions";
16
+ import * as Rx from "rxjs";
17
+ import type { NetworkUrls } from "./types.ts";
18
+ import { midnightNetworkConfig } from "./midnight-env.ts";
19
+ import { getEnv, args as getArgs, exit, isNotFoundError } from "@effectstream/utils/runtime";
20
+ import {
21
+ buildWalletFacade,
22
+ getInitialShieldedState,
23
+ getInitialUnshieldedState,
24
+ resolveWalletSyncTimeoutMs,
25
+ syncAndWaitForFunds,
26
+ resolveFacadeDustBalance,
27
+ waitForDustFunds,
28
+ registerNightForDust,
29
+ } from "./get-wallet-info.ts";
30
+
31
+ const log = console;
32
+
33
+ async function main() {
34
+ const { values: parsedArgs } = parseArgs({
35
+ args: getArgs(),
36
+ options: {
37
+ seed: { type: "string" },
38
+ balance: { type: "boolean", default: false },
39
+ },
40
+ strict: false,
41
+ });
42
+
43
+ const result = dotenv.config({ path: ".env.testnet", override: true });
44
+ if (result.error) {
45
+ if (!isNotFoundError(result.error)) {
46
+ log.warn(`Failed to load .env.testnet: ${result.error}`);
47
+ }
48
+ }
49
+
50
+ const envSeed = getEnv("MIDNIGHT_WALLET_SEED");
51
+ const argSeed = parsedArgs.seed;
52
+ let seed = argSeed || envSeed;
53
+
54
+ if (!seed) {
55
+ log.info("No seed provided. Generating a new random 32-byte hex seed...");
56
+ const randomBytes = new Uint8Array(32);
57
+ crypto.getRandomValues(randomBytes);
58
+ seed = Array.from(randomBytes)
59
+ .map((b) => b.toString(16).padStart(2, "0"))
60
+ .join("");
61
+ log.info("==========================================");
62
+ log.info(`GENERATED SEED: ${seed}`);
63
+ log.info("SAVE THIS SEED! YOU WILL NEED IT TO RESTORE THIS WALLET.");
64
+ log.info("==========================================");
65
+ } else {
66
+ log.info("Using provided seed: " + seed);
67
+ }
68
+
69
+ if (seed?.startsWith("000000000000000000000000000000000000000000000000000000000000000")) {
70
+ log.warn("⚠️ Genesis seeds (0x000...001, 0x000...002, etc.) only have funds on 'undeployed' local networks!");
71
+ log.warn("⚠️ For testnet/preview networks, you need to:");
72
+ log.warn(" 1. Generate a new wallet seed, OR");
73
+ log.warn(" 2. Request funds from a faucet for this wallet");
74
+ }
75
+
76
+ const indexer = getEnv("MIDNIGHT_INDEXER_URL") || midnightNetworkConfig.indexer;
77
+ const indexerWS = getEnv("MIDNIGHT_INDEXER_WS_URL") || midnightNetworkConfig.indexerWS;
78
+ const node = getEnv("MIDNIGHT_NODE_URL") || midnightNetworkConfig.node;
79
+ const proofServer = getEnv("MIDNIGHT_PROOF_SERVER_URL") || midnightNetworkConfig.proofServer;
80
+
81
+ const networkUrls: Required<NetworkUrls> = {
82
+ id: "placeholder-value",
83
+ indexer,
84
+ indexerWS,
85
+ node,
86
+ proofServer
87
+ };
88
+
89
+ const networkIdRaw = getEnv("MIDNIGHT_NETWORK_ID") || "undeployed";
90
+
91
+ let networkId: NetworkId.NetworkId;
92
+ switch (networkIdRaw.toLowerCase()) {
93
+ case "undeployed":
94
+ networkId = NetworkId.NetworkId.Undeployed;
95
+ break;
96
+ case "testnet":
97
+ case "testnet-02":
98
+ networkId = NetworkId.NetworkId.TestNet;
99
+ break;
100
+ case "devnet":
101
+ case "qanet":
102
+ networkId = NetworkId.NetworkId.DevNet;
103
+ break;
104
+ case "preview":
105
+ networkId = "preview" as NetworkId.NetworkId;
106
+ log.info(`Using preview network (addresses will have mn_addr_preview prefix)`);
107
+ break;
108
+ default:
109
+ log.warn(`Unknown network ID "${networkIdRaw}", using as-is. Valid values: undeployed, testnet, devnet, preview`);
110
+ networkId = networkIdRaw as NetworkId.NetworkId;
111
+ }
112
+ networkUrls.id = networkId;
113
+
114
+ log.info(`Using network ID: ${networkId}`);
115
+ log.info(`Indexer: ${indexer}`);
116
+ log.info(`Indexer WS: ${indexerWS}`);
117
+ log.info(`Node: ${node}`);
118
+ setNetworkId(networkId);
119
+
120
+ try {
121
+ log.info("Building wallet...");
122
+ const walletResult = await buildWalletFacade(networkUrls, seed, networkId);
123
+
124
+ const initialState = await getInitialShieldedState(walletResult.wallet.shielded);
125
+ const shieldedAddress = initialState.address.coinPublicKeyString();
126
+
127
+ log.info("==========================================");
128
+ log.info("Wallet Addresses");
129
+ log.info("==========================================");
130
+ log.info(`Shielded Address: ${shieldedAddress}`);
131
+ log.info(`Unshielded Address: ${walletResult.unshieldedAddress}`);
132
+ log.info(`Dust Address: ${walletResult.dustAddress}`);
133
+ log.info("==========================================");
134
+
135
+ if (parsedArgs.balance) {
136
+ log.info("==========================================");
137
+ log.info("Fetching Balances...");
138
+ log.info("==========================================");
139
+
140
+ let shieldedBalance = 0n;
141
+ let unshieldedBalance = 0n;
142
+ let dustBalance = 0n;
143
+
144
+ const tokenId = shieldedToken().tag;
145
+ shieldedBalance = initialState.balances[tokenId] ?? 0n;
146
+
147
+ const syncTimeoutMs = resolveWalletSyncTimeoutMs();
148
+
149
+ if (shieldedBalance === 0n) {
150
+ log.info("Shielded balance is 0. Waiting for wallet sync to confirm funds...");
151
+ try {
152
+ const synced = await syncAndWaitForFunds(walletResult.wallet);
153
+ shieldedBalance = synced.shieldedBalance;
154
+ unshieldedBalance = synced.unshieldedBalance;
155
+ dustBalance = synced.dustBalance;
156
+ } catch (e) {
157
+ log.warn(`Sync timed out or failed: ${e instanceof Error ? e.message : String(e)}`);
158
+ }
159
+ } else {
160
+ try {
161
+ const unshieldedState = await getInitialUnshieldedState((walletResult.wallet as any).unshielded);
162
+ const uBalances = (unshieldedState?.balances ?? {}) as Record<string, bigint>;
163
+ unshieldedBalance = Object.values(uBalances).reduce(
164
+ (acc: bigint, v) => acc + (v ?? 0n),
165
+ 0n
166
+ );
167
+ } catch(_e) { /* ignore */ }
168
+
169
+ try {
170
+ const state = await Rx.firstValueFrom(walletResult.wallet.state());
171
+ dustBalance = resolveFacadeDustBalance(state, new Date());
172
+
173
+ if (dustBalance === 0n) {
174
+ log.info("Dust balance is 0. Attempting to sync dust wallet...");
175
+ try {
176
+ dustBalance = await waitForDustFunds(walletResult.wallet, { timeoutMs: syncTimeoutMs });
177
+ } catch(_e) {
178
+ log.warn("Dust sync timed out or returned no funds.");
179
+ }
180
+ }
181
+
182
+ if (dustBalance === 0n && unshieldedBalance > 0n) {
183
+ log.info("Dust is 0 but unshielded funds available. Registering for dust generation...");
184
+ const success = await registerNightForDust(walletResult);
185
+ if (success) {
186
+ dustBalance = await waitForDustFunds(walletResult.wallet, { timeoutMs: 30000 });
187
+ }
188
+ }
189
+ } catch (_e) {
190
+ // ignore
191
+ }
192
+ }
193
+
194
+ log.info("==========================================");
195
+ log.info("Balances");
196
+ log.info("==========================================");
197
+ log.info(`Shielded Balance: ${shieldedBalance} NIGHT`);
198
+ log.info(`Dust Balance: ${dustBalance} DUST`);
199
+ log.info(`Unshielded Balance: ${unshieldedBalance} NIGHT`);
200
+ }
201
+
202
+ log.info("==========================================");
203
+
204
+ await walletResult.wallet.stop();
205
+ } catch (error) {
206
+ log.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
207
+ exit(1);
208
+ }
209
+ }
210
+
211
+ if (import.meta.main) {
212
+ main();
213
+ }
@@ -1,5 +1,3 @@
1
- import dotenv from "dotenv";
2
- import { parseArgs } from "node:util";
3
1
  // graphql-ws has a dangling promise in its subscribe flow: when a subscription ends,
4
2
  // the socket closes normally (code 1000) but the internal throwOnClose promise rejects
5
3
  // with no handler. In Bun, unhandled rejections crash the process (exit 1).
@@ -969,7 +967,7 @@ export function getInitialShieldedState(
969
967
  /**
970
968
  * Get initial state of unshielded wallet
971
969
  */
972
- function getInitialUnshieldedState(
970
+ export function getInitialUnshieldedState(
973
971
  unshieldedWallet: any
974
972
  ): Promise<any> {
975
973
  if (!unshieldedWallet) return Promise.resolve(null);
@@ -1055,189 +1053,3 @@ export async function registerNightForDust(
1055
1053
  return false;
1056
1054
  }
1057
1055
  }
1058
-
1059
- // ============================================================================
1060
- // Main
1061
- // ============================================================================
1062
-
1063
- async function main() {
1064
- const { values: parsedArgs } = parseArgs({
1065
- args: getArgs(),
1066
- options: {
1067
- seed: { type: "string" },
1068
- balance: { type: "boolean", default: false },
1069
- },
1070
- strict: false,
1071
- });
1072
-
1073
- const result = dotenv.config({ path: ".env.testnet", override: true });
1074
- if (result.error) {
1075
- if (!isNotFoundError(result.error)) {
1076
- log.warn(`Failed to load .env.testnet: ${result.error}`);
1077
- }
1078
- }
1079
-
1080
- const envSeed = getEnv("MIDNIGHT_WALLET_SEED");
1081
- const argSeed = parsedArgs.seed;
1082
- let seed = argSeed || envSeed;
1083
-
1084
- if (!seed) {
1085
- log.info("No seed provided. Generating a new random 32-byte hex seed...");
1086
- const randomBytes = new Uint8Array(32);
1087
- crypto.getRandomValues(randomBytes);
1088
- seed = Array.from(randomBytes)
1089
- .map((b) => b.toString(16).padStart(2, "0"))
1090
- .join("");
1091
- log.info("==========================================");
1092
- log.info(`GENERATED SEED: ${seed}`);
1093
- log.info("SAVE THIS SEED! YOU WILL NEED IT TO RESTORE THIS WALLET.");
1094
- log.info("==========================================");
1095
- } else {
1096
- log.info("Using provided seed: " + seed);
1097
- }
1098
-
1099
- if (seed?.startsWith("000000000000000000000000000000000000000000000000000000000000000")) {
1100
- log.warn("⚠️ Genesis seeds (0x000...001, 0x000...002, etc.) only have funds on 'undeployed' local networks!");
1101
- log.warn("⚠️ For testnet/preview networks, you need to:");
1102
- log.warn(" 1. Generate a new wallet seed, OR");
1103
- log.warn(" 2. Request funds from a faucet for this wallet");
1104
- }
1105
-
1106
- const indexer = getEnv("MIDNIGHT_INDEXER_URL") || midnightNetworkConfig.indexer;
1107
- const indexerWS = getEnv("MIDNIGHT_INDEXER_WS_URL") || midnightNetworkConfig.indexerWS;
1108
- const node = getEnv("MIDNIGHT_NODE_URL") || midnightNetworkConfig.node;
1109
- const proofServer = getEnv("MIDNIGHT_PROOF_SERVER_URL") || midnightNetworkConfig.proofServer;
1110
-
1111
- const networkUrls: Required<NetworkUrls> = {
1112
- id: "placeholder-value",
1113
- indexer,
1114
- indexerWS,
1115
- node,
1116
- proofServer
1117
- };
1118
-
1119
- const networkIdRaw = getEnv("MIDNIGHT_NETWORK_ID") || "undeployed";
1120
-
1121
- let networkId: NetworkId.NetworkId;
1122
- switch (networkIdRaw.toLowerCase()) {
1123
- case "undeployed":
1124
- networkId = NetworkId.NetworkId.Undeployed;
1125
- break;
1126
- case "testnet":
1127
- case "testnet-02":
1128
- networkId = NetworkId.NetworkId.TestNet;
1129
- break;
1130
- case "devnet":
1131
- case "qanet":
1132
- networkId = NetworkId.NetworkId.DevNet;
1133
- break;
1134
- case "preview":
1135
- networkId = "preview" as NetworkId.NetworkId;
1136
- log.info(`Using preview network (addresses will have mn_addr_preview prefix)`);
1137
- break;
1138
- default:
1139
- log.warn(`Unknown network ID "${networkIdRaw}", using as-is. Valid values: undeployed, testnet, devnet, preview`);
1140
- networkId = networkIdRaw as NetworkId.NetworkId;
1141
- }
1142
- networkUrls.id = networkId;
1143
-
1144
- log.info(`Using network ID: ${networkId}`);
1145
- log.info(`Indexer: ${indexer}`);
1146
- log.info(`Indexer WS: ${indexerWS}`);
1147
- log.info(`Node: ${node}`);
1148
- setNetworkId(networkId);
1149
-
1150
- try {
1151
- log.info("Building wallet...");
1152
- const walletResult = await buildWalletFacade(networkUrls, seed, networkId);
1153
-
1154
- const initialState = await getInitialShieldedState(walletResult.wallet.shielded);
1155
- const shieldedAddress = initialState.address.coinPublicKeyString();
1156
-
1157
- log.info("==========================================");
1158
- log.info("Wallet Addresses");
1159
- log.info("==========================================");
1160
- log.info(`Shielded Address: ${shieldedAddress}`);
1161
- log.info(`Unshielded Address: ${walletResult.unshieldedAddress}`);
1162
- log.info(`Dust Address: ${walletResult.dustAddress}`);
1163
- log.info("==========================================");
1164
-
1165
- if (parsedArgs.balance) {
1166
- log.info("==========================================");
1167
- log.info("Fetching Balances...");
1168
- log.info("==========================================");
1169
-
1170
- let shieldedBalance = 0n;
1171
- let unshieldedBalance = 0n;
1172
- let dustBalance = 0n;
1173
-
1174
- const tokenId = shieldedToken().tag;
1175
- shieldedBalance = initialState.balances[tokenId] ?? 0n;
1176
-
1177
- const syncTimeoutMs = resolveWalletSyncTimeoutMs();
1178
-
1179
- if (shieldedBalance === 0n) {
1180
- log.info("Shielded balance is 0. Waiting for wallet sync to confirm funds...");
1181
- try {
1182
- const synced = await syncAndWaitForFunds(walletResult.wallet);
1183
- shieldedBalance = synced.shieldedBalance;
1184
- unshieldedBalance = synced.unshieldedBalance;
1185
- dustBalance = synced.dustBalance;
1186
- } catch (e) {
1187
- log.warn(`Sync timed out or failed: ${e instanceof Error ? e.message : String(e)}`);
1188
- }
1189
- } else {
1190
- try {
1191
- const unshieldedState = await getInitialUnshieldedState((walletResult.wallet as any).unshielded);
1192
- const uBalances = (unshieldedState?.balances ?? {}) as Record<string, bigint>;
1193
- unshieldedBalance = Object.values(uBalances).reduce(
1194
- (acc: bigint, v) => acc + (v ?? 0n),
1195
- 0n
1196
- );
1197
- } catch(_e) { /* ignore */ }
1198
-
1199
- try {
1200
- const state = await Rx.firstValueFrom(walletResult.wallet.state());
1201
- dustBalance = resolveFacadeDustBalance(state, new Date());
1202
-
1203
- if (dustBalance === 0n) {
1204
- log.info("Dust balance is 0. Attempting to sync dust wallet...");
1205
- try {
1206
- dustBalance = await waitForDustFunds(walletResult.wallet, { timeoutMs: syncTimeoutMs });
1207
- } catch(_e) {
1208
- log.warn("Dust sync timed out or returned no funds.");
1209
- }
1210
- }
1211
-
1212
- if (dustBalance === 0n && unshieldedBalance > 0n) {
1213
- log.info("Dust is 0 but unshielded funds available. Registering for dust generation...");
1214
- const success = await registerNightForDust(walletResult);
1215
- if (success) {
1216
- dustBalance = await waitForDustFunds(walletResult.wallet, { timeoutMs: 30000 });
1217
- }
1218
- }
1219
- } catch (_e) {
1220
- // ignore
1221
- }
1222
- }
1223
-
1224
- log.info("==========================================");
1225
- log.info("Balances");
1226
- log.info("==========================================");
1227
- log.info(`Shielded Balance: ${shieldedBalance} NIGHT`);
1228
- log.info(`Dust Balance: ${dustBalance} DUST`);
1229
- log.info(`Unshielded Balance: ${unshieldedBalance} NIGHT`);
1230
- }
1231
-
1232
- log.info("==========================================");
1233
-
1234
- await walletResult.wallet.stop();
1235
- } catch (error) {
1236
- log.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
1237
- exit(1);
1238
- }
1239
- }
1240
-
1241
- if (import.meta.main) {
1242
- main();
1243
- }