@claritydao/midnight-agora-sdk 0.2.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +58 -55
  2. package/dist/browser-providers.d.ts +24 -19
  3. package/dist/browser-providers.d.ts.map +1 -1
  4. package/dist/browser-providers.js +654 -139
  5. package/dist/browser-providers.js.map +1 -1
  6. package/dist/common-types.d.ts +13 -24
  7. package/dist/common-types.d.ts.map +1 -1
  8. package/dist/common-types.js +1 -1
  9. package/dist/common-types.js.map +1 -1
  10. package/dist/governor-api.d.ts +42 -17
  11. package/dist/governor-api.d.ts.map +1 -1
  12. package/dist/governor-api.js +233 -248
  13. package/dist/governor-api.js.map +1 -1
  14. package/dist/index.d.ts +11 -10
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +10 -9
  17. package/dist/index.js.map +1 -1
  18. package/dist/manager.d.ts +11 -11
  19. package/dist/manager.d.ts.map +1 -1
  20. package/dist/manager.js +17 -20
  21. package/dist/manager.js.map +1 -1
  22. package/dist/types.d.ts +9 -17
  23. package/dist/types.d.ts.map +1 -1
  24. package/dist/types.js.map +1 -1
  25. package/package.json +26 -26
  26. package/.prettierrc +0 -1
  27. package/LICENSE +0 -0
  28. package/eslint.config.mjs +0 -48
  29. package/src/browser-providers.ts +0 -278
  30. package/src/common-types.ts +0 -51
  31. package/src/errors.ts +0 -124
  32. package/src/governor-api.ts +0 -948
  33. package/src/index.ts +0 -51
  34. package/src/manager.ts +0 -205
  35. package/src/types.ts +0 -403
  36. package/src/utils/index.ts +0 -60
  37. package/src/validators.ts +0 -245
  38. package/test/README.md +0 -409
  39. package/test/errors.test.ts +0 -179
  40. package/test/integration/governor-api.test.ts +0 -370
  41. package/test/mocks/providers.ts +0 -130
  42. package/test/types.test.ts +0 -112
  43. package/test/utils.test.ts +0 -111
  44. package/test/validators.test.ts +0 -368
  45. package/test/wasm-init.test.ts +0 -132
  46. package/tsconfig.json +0 -18
  47. package/vitest.config.ts +0 -9
@@ -1,150 +1,665 @@
1
- import { concatMap, filter, firstValueFrom, interval, map, of, take, tap, throwError, timeout, catchError } from "rxjs";
2
- import { pipe as fnPipe } from "fp-ts/function";
3
- import { levelPrivateStateProvider } from "@midnight-ntwrk/midnight-js-level-private-state-provider";
4
- import { FetchZkConfigProvider } from "@midnight-ntwrk/midnight-js-fetch-zk-config-provider";
5
- import { httpClientProofProvider } from "@midnight-ntwrk/midnight-js-http-client-proof-provider";
6
- import { indexerPublicDataProvider } from "@midnight-ntwrk/midnight-js-indexer-public-data-provider";
7
- import semver from "semver";
8
- import { getNetworkId, setNetworkId } from "@midnight-ntwrk/midnight-js-network-id";
9
- // Helper functions for hex encoding/decoding
10
- const toHexString = (bytes) => Array.from(bytes)
11
- .map((b) => b.toString(16).padStart(2, "0"))
12
- .join("");
13
- const _fromHexString = (hex) => new Uint8Array(hex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
14
- /**
15
- * Initializes and returns the providers required for Governor contract interactions in a browser environment.
16
- *
17
- * @param logger The logger instance for logging.
18
- * @param networkId The network ID to connect to (e.g., 'testnet', 'mainnet').
19
- * @returns A promise that resolves to configured GovernorProviders.
20
- *
21
- * @remarks
22
- * This function connects to the Midnight Lace wallet extension and retrieves network configuration
23
- * from the wallet. The network ID is specified by the caller.
24
- */
25
- export const initializeProviders = async (logger, networkId = "testnet") => {
26
- const { wallet, config } = await connectToWallet(logger, networkId);
27
- const zkConfigPath = window.location.origin;
28
- logger.info(`Connecting to wallet with network ID: ${getNetworkId()}`);
29
- // Get wallet addresses
30
- const addresses = await wallet.getShieldedAddresses();
31
- // Create wallet provider first (needed for private state encryption)
32
- const walletProvider = createWalletProvider(wallet, addresses);
1
+ import { FetchZkConfigProvider } from '@midnight-ntwrk/midnight-js-fetch-zk-config-provider';
2
+ import { httpClientProofProvider } from '@midnight-ntwrk/midnight-js-http-client-proof-provider';
3
+ import { indexerPublicDataProvider } from '@midnight-ntwrk/midnight-js-indexer-public-data-provider';
4
+ import { encodeCoinPublicKey, LedgerParameters, Transaction } from '@midnight-ntwrk/ledger-v8';
5
+ import { setNetworkId } from '@midnight-ntwrk/midnight-js-network-id';
6
+ import { createProofProvider } from '@midnight-ntwrk/midnight-js-types';
7
+ import { mainnet, MidnightBech32m, ShieldedAddress, ShieldedCoinPublicKey, ShieldedEncryptionPublicKey, UnshieldedAddress, } from '@midnight-ntwrk/wallet-sdk-address-format';
8
+ const PRIVATE_STATE_STORAGE_KEY_PREFIX = 'midnight-private-state:';
9
+ const SIGNING_KEY_STORAGE_KEY_PREFIX = 'midnight-signing-key:';
10
+ const MIDNIGHT_HINT_METHODS = [
11
+ 'getProvingProvider',
12
+ 'balanceUnsealedTransaction',
13
+ 'balanceSealedTransaction',
14
+ 'submitTransaction',
15
+ 'getConfiguration',
16
+ 'getShieldedAddresses',
17
+ 'getUnshieldedAddress',
18
+ ];
19
+ function bytesToHex(bytes) {
20
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
21
+ }
22
+ function hexToBytes(hex) {
23
+ const normalized = stripHexPrefix(hex);
24
+ if (normalized.length % 2 !== 0) {
25
+ throw new Error('Hex transaction payload has an odd number of characters');
26
+ }
27
+ const bytes = new Uint8Array(normalized.length / 2);
28
+ for (let index = 0; index < normalized.length; index += 2) {
29
+ bytes[index / 2] = Number.parseInt(normalized.slice(index, index + 2), 16);
30
+ }
31
+ return bytes;
32
+ }
33
+ function serializeTxToHex(tx) {
34
+ return bytesToHex(tx.serialize());
35
+ }
36
+ function deserializeFinalizedTransaction(txHex) {
37
+ return Transaction.deserialize('signature', 'proof', 'binding', hexToBytes(txHex));
38
+ }
39
+ function formatSyntheticCost(cost) {
33
40
  return {
34
- privateStateProvider: levelPrivateStateProvider({
35
- privateStateStoreName: "governor-private-state",
36
- walletProvider: walletProvider
37
- }),
38
- zkConfigProvider: new FetchZkConfigProvider(zkConfigPath, fetch.bind(window)),
39
- proofProvider: config.proverServerUri
40
- ? httpClientProofProvider(config.proverServerUri)
41
- : httpClientProofProvider(""), // Fallback - proving may be done by wallet
42
- publicDataProvider: indexerPublicDataProvider(config.indexerUri, config.indexerWsUri),
43
- walletProvider,
44
- midnightProvider: {
45
- async submitTx(tx) {
46
- // Convert transaction to hex string for the connector API
47
- const serialized = tx.serialize();
48
- const hexString = toHexString(serialized);
49
- await wallet.submitTransaction(hexString);
50
- // Return the transaction hash as the ID
51
- return tx.transactionHash();
41
+ readTime: cost.readTime.toString(),
42
+ computeTime: cost.computeTime.toString(),
43
+ blockUsage: cost.blockUsage.toString(),
44
+ bytesWritten: cost.bytesWritten.toString(),
45
+ bytesChurned: cost.bytesChurned.toString(),
46
+ };
47
+ }
48
+ function getTransactionCostDebug(tx) {
49
+ const ledgerParameters = LedgerParameters.initialParameters();
50
+ const cost = tx.cost(ledgerParameters);
51
+ try {
52
+ return {
53
+ parameterSource: 'LedgerParameters.initialParameters()',
54
+ cost: formatSyntheticCost(cost),
55
+ normalized: ledgerParameters.normalizeFullness(cost),
56
+ exceedsBlockLimits: false,
57
+ };
58
+ }
59
+ catch (error) {
60
+ return {
61
+ parameterSource: 'LedgerParameters.initialParameters()',
62
+ cost: formatSyntheticCost(cost),
63
+ exceedsBlockLimits: true,
64
+ error: error instanceof Error ? error.message : String(error),
65
+ };
66
+ }
67
+ }
68
+ function getSubmittedTransactionCostDebug(txHex) {
69
+ try {
70
+ return getTransactionCostDebug(deserializeFinalizedTransaction(txHex));
71
+ }
72
+ catch (error) {
73
+ return {
74
+ parameterSource: 'LedgerParameters.initialParameters()',
75
+ error: error instanceof Error ? error.message : String(error),
76
+ };
77
+ }
78
+ }
79
+ function getErrorDetails(error) {
80
+ if (error instanceof Error) {
81
+ return error.message;
82
+ }
83
+ try {
84
+ return JSON.stringify(error);
85
+ }
86
+ catch {
87
+ return String(error);
88
+ }
89
+ }
90
+ function getErrorDebug(error) {
91
+ if (!(error instanceof Error)) {
92
+ return error;
93
+ }
94
+ const enumerableFields = Object.fromEntries(Object.entries(error).map(([key, value]) => [
95
+ key,
96
+ value instanceof Error ? getErrorDebug(value) : value,
97
+ ]));
98
+ return {
99
+ name: error.name,
100
+ message: error.message,
101
+ cause: 'cause' in error ? getErrorDebug(error.cause) : undefined,
102
+ stack: error.stack,
103
+ ...enumerableFields,
104
+ };
105
+ }
106
+ function isTemporarilyBannedError(error) {
107
+ return getErrorDetails(error).toLowerCase().includes('temporarily banned');
108
+ }
109
+ function isUserRejectedError(error) {
110
+ const details = getErrorDetails(error).toLowerCase();
111
+ const debug = JSON.stringify(getErrorDebug(error)).toLowerCase();
112
+ return details.includes('user rejected') || details.includes('rejected transaction') || debug.includes('user rejected');
113
+ }
114
+ function stringifyBigIntRecord(record) {
115
+ return Object.fromEntries(Object.entries(record).map(([key, value]) => [key, value.toString()]));
116
+ }
117
+ async function getWalletFundingDebug(wallet) {
118
+ const [dust, shieldedBalances, unshieldedBalances] = await Promise.allSettled([
119
+ wallet.getDustBalance(),
120
+ wallet.getShieldedBalances(),
121
+ wallet.getUnshieldedBalances(),
122
+ ]);
123
+ return {
124
+ dust: dust.status === 'fulfilled'
125
+ ? {
126
+ balance: dust.value.balance.toString(),
127
+ cap: dust.value.cap.toString(),
52
128
  }
129
+ : {
130
+ error: getErrorDetails(dust.reason),
131
+ errorDebug: getErrorDebug(dust.reason),
132
+ },
133
+ shieldedBalances: shieldedBalances.status === 'fulfilled'
134
+ ? stringifyBigIntRecord(shieldedBalances.value)
135
+ : {
136
+ error: getErrorDetails(shieldedBalances.reason),
137
+ errorDebug: getErrorDebug(shieldedBalances.reason),
138
+ },
139
+ unshieldedBalances: unshieldedBalances.status === 'fulfilled'
140
+ ? stringifyBigIntRecord(unshieldedBalances.value)
141
+ : {
142
+ error: getErrorDetails(unshieldedBalances.reason),
143
+ errorDebug: getErrorDebug(unshieldedBalances.reason),
144
+ },
145
+ };
146
+ }
147
+ function describeWalletFundingDebug(fundingDebug) {
148
+ const dust = 'balance' in fundingDebug.dust ? fundingDebug.dust : undefined;
149
+ if (!dust) {
150
+ return 'Wallet funding diagnostics could not read dust balance.';
151
+ }
152
+ return `Wallet dust balance: ${dust.balance} / cap ${dust.cap}.`;
153
+ }
154
+ async function withTimeout(promise, timeoutMs, message) {
155
+ let timeoutId;
156
+ const timeout = new Promise((_, reject) => {
157
+ timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
158
+ });
159
+ try {
160
+ return await Promise.race([promise, timeout]);
161
+ }
162
+ finally {
163
+ if (timeoutId) {
164
+ clearTimeout(timeoutId);
165
+ }
166
+ }
167
+ }
168
+ function encodeStoredValue(value) {
169
+ return JSON.stringify(value, (_key, currentValue) => {
170
+ if (typeof currentValue === 'bigint') {
171
+ return {
172
+ __type: 'bigint',
173
+ value: currentValue.toString(),
174
+ };
175
+ }
176
+ if (currentValue instanceof Uint8Array) {
177
+ return {
178
+ __type: 'Uint8Array',
179
+ value: Array.from(currentValue),
180
+ };
181
+ }
182
+ return currentValue;
183
+ });
184
+ }
185
+ function decodeStoredValue(value) {
186
+ if (!value) {
187
+ return null;
188
+ }
189
+ return JSON.parse(value, (_key, currentValue) => {
190
+ if (currentValue && typeof currentValue === 'object' && currentValue.__type === 'bigint') {
191
+ return BigInt(currentValue.value);
192
+ }
193
+ if (currentValue &&
194
+ typeof currentValue === 'object' &&
195
+ currentValue.__type === 'Uint8Array' &&
196
+ Array.isArray(currentValue.value)) {
197
+ return new Uint8Array(currentValue.value);
198
+ }
199
+ return currentValue;
200
+ });
201
+ }
202
+ function assertBrowserStorage() {
203
+ if (typeof localStorage === 'undefined') {
204
+ throw new Error('Governor browser providers require localStorage');
205
+ }
206
+ return localStorage;
207
+ }
208
+ function createLocalStoragePrivateStateProvider(accountId) {
209
+ let contractAddress = null;
210
+ const listStorageKeys = (prefix) => Object.keys(assertBrowserStorage()).filter((key) => key.startsWith(prefix));
211
+ const getScopedStorageKey = (prefix, key, includeContractAddress) => {
212
+ if (includeContractAddress && !contractAddress) {
213
+ throw new Error('Contract address not set. Call setContractAddress() before accessing private state.');
53
214
  }
215
+ const contractScope = includeContractAddress ? `${contractAddress}:` : '';
216
+ return `${prefix}${accountId}:${contractScope}${key}`;
54
217
  };
55
- };
56
- /**
57
- * Creates a WalletProvider from the connected wallet API.
58
- */
59
- const createWalletProvider = (wallet, addresses) => {
60
- // Note: The bech32m addresses need to be converted to the expected key types
61
- // This conversion may need adjustment based on the actual key format expected
62
- const coinPublicKey = addresses.shieldedCoinPublicKey;
63
- const encryptionPublicKey = addresses.shieldedEncryptionPublicKey;
64
218
  return {
65
- getCoinPublicKey() {
66
- return coinPublicKey;
67
- },
68
- getEncryptionPublicKey() {
69
- return encryptionPublicKey;
70
- },
71
- async balanceTx(tx, _newCoins, _ttl) {
72
- // Use the wallet's balancing capability
73
- // Convert Uint8Array to hex string for the connector API
74
- const serialized = tx.serialize();
75
- const hexString = toHexString(serialized);
76
- const _result = await wallet.balanceUnsealedTransaction(hexString);
77
- // The wallet returns a balanced transaction as a hex string
78
- // We need to convert it back and return as NothingToProve
79
- // Note: The actual deserialization may need the Transaction.deserialize with proper markers
80
- // For now, we return the original transaction as the wallet handles proving internally
219
+ setContractAddress(address) {
220
+ contractAddress = address;
221
+ },
222
+ async get(key) {
223
+ return decodeStoredValue(assertBrowserStorage().getItem(getScopedStorageKey(PRIVATE_STATE_STORAGE_KEY_PREFIX, key, true)));
224
+ },
225
+ async set(key, value) {
226
+ assertBrowserStorage().setItem(getScopedStorageKey(PRIVATE_STATE_STORAGE_KEY_PREFIX, key, true), encodeStoredValue(value));
227
+ },
228
+ async remove(key) {
229
+ assertBrowserStorage().removeItem(getScopedStorageKey(PRIVATE_STATE_STORAGE_KEY_PREFIX, key, true));
230
+ },
231
+ async clear() {
232
+ if (!contractAddress) {
233
+ throw new Error('Contract address not set. Call setContractAddress() before clearing private state.');
234
+ }
235
+ const privateStatePrefix = `${PRIVATE_STATE_STORAGE_KEY_PREFIX}${accountId}:${contractAddress}:`;
236
+ for (const key of listStorageKeys(privateStatePrefix)) {
237
+ assertBrowserStorage().removeItem(key);
238
+ }
239
+ },
240
+ async getSigningKey(address) {
241
+ return decodeStoredValue(assertBrowserStorage().getItem(getScopedStorageKey(SIGNING_KEY_STORAGE_KEY_PREFIX, address, false)));
242
+ },
243
+ async setSigningKey(address, key) {
244
+ assertBrowserStorage().setItem(getScopedStorageKey(SIGNING_KEY_STORAGE_KEY_PREFIX, address, false), encodeStoredValue(key));
245
+ },
246
+ async removeSigningKey(address) {
247
+ assertBrowserStorage().removeItem(getScopedStorageKey(SIGNING_KEY_STORAGE_KEY_PREFIX, address, false));
248
+ },
249
+ async clearSigningKeys() {
250
+ const signingKeyPrefix = `${SIGNING_KEY_STORAGE_KEY_PREFIX}${accountId}:`;
251
+ for (const key of listStorageKeys(signingKeyPrefix)) {
252
+ assertBrowserStorage().removeItem(key);
253
+ }
254
+ },
255
+ async exportPrivateStates() {
256
+ if (!contractAddress) {
257
+ throw new Error('Contract address not set. Call setContractAddress() before exporting private state.');
258
+ }
259
+ const privateStatePrefix = `${PRIVATE_STATE_STORAGE_KEY_PREFIX}${accountId}:${contractAddress}:`;
81
260
  return {
82
- type: "NothingToProve",
83
- transaction: tx // The wallet should have balanced this
261
+ version: 1,
262
+ entries: listStorageKeys(privateStatePrefix).map((key) => ({
263
+ key,
264
+ value: assertBrowserStorage().getItem(key),
265
+ })),
84
266
  };
85
- }
267
+ },
268
+ async importPrivateStates(exportData) {
269
+ const entries = exportData &&
270
+ typeof exportData === 'object' &&
271
+ Array.isArray(exportData.entries)
272
+ ? exportData.entries
273
+ : [];
274
+ let imported = 0;
275
+ for (const entry of entries) {
276
+ if (!entry?.key || !entry.key.startsWith(PRIVATE_STATE_STORAGE_KEY_PREFIX)) {
277
+ continue;
278
+ }
279
+ if (typeof entry.value === 'string') {
280
+ assertBrowserStorage().setItem(entry.key, entry.value);
281
+ imported += 1;
282
+ }
283
+ }
284
+ return {
285
+ imported,
286
+ skipped: entries.length - imported,
287
+ overwritten: 0,
288
+ };
289
+ },
290
+ async exportSigningKeys() {
291
+ const signingKeyPrefix = `${SIGNING_KEY_STORAGE_KEY_PREFIX}${accountId}:`;
292
+ return {
293
+ version: 1,
294
+ entries: listStorageKeys(signingKeyPrefix).map((key) => ({
295
+ key,
296
+ value: assertBrowserStorage().getItem(key),
297
+ })),
298
+ };
299
+ },
300
+ async importSigningKeys(exportData) {
301
+ const entries = exportData &&
302
+ typeof exportData === 'object' &&
303
+ Array.isArray(exportData.entries)
304
+ ? exportData.entries
305
+ : [];
306
+ let imported = 0;
307
+ for (const entry of entries) {
308
+ if (!entry?.key || !entry.key.startsWith(SIGNING_KEY_STORAGE_KEY_PREFIX)) {
309
+ continue;
310
+ }
311
+ if (typeof entry.value === 'string') {
312
+ assertBrowserStorage().setItem(entry.key, entry.value);
313
+ imported += 1;
314
+ }
315
+ }
316
+ return {
317
+ imported,
318
+ skipped: entries.length - imported,
319
+ overwritten: 0,
320
+ };
321
+ },
86
322
  };
323
+ }
324
+ function getZkConfigBaseUrl(override, profile) {
325
+ if (typeof window === 'undefined') {
326
+ throw new Error('ZK config base URL requires a browser environment');
327
+ }
328
+ const root = override ? resolveBrowserUrl(override) : `${window.location.origin}/`;
329
+ return new URL(`${profile}/`, root.endsWith('/') ? root : `${root}/`).toString().replace(/\/$/, '');
330
+ }
331
+ function resolveBrowserUrl(url) {
332
+ if (typeof window === 'undefined') {
333
+ return url;
334
+ }
335
+ return new URL(url, window.location.origin).toString();
336
+ }
337
+ function normalizeGovernorArtifactUrl(input) {
338
+ if (typeof input !== 'string') {
339
+ return input;
340
+ }
341
+ const match = input.match(/^(.*\/(?:keys|zkir)\/)[^/#]+#([^?#]+)$/);
342
+ if (!match) {
343
+ return input;
344
+ }
345
+ const [, artifactBaseUrl, circuitFileName] = match;
346
+ return `${artifactBaseUrl}${circuitFileName}`;
347
+ }
348
+ const fetchGovernorArtifact = async (input, init) => {
349
+ const normalizedInput = normalizeGovernorArtifactUrl(input);
350
+ try {
351
+ const response = await fetch(normalizedInput, init);
352
+ if (!response.ok) {
353
+ console.warn('[ZK] Artifact fetch returned non-OK response', {
354
+ url: String(normalizedInput),
355
+ status: response.status,
356
+ statusText: response.statusText,
357
+ });
358
+ }
359
+ return response;
360
+ }
361
+ catch (error) {
362
+ console.error('[ZK] Artifact fetch failed', {
363
+ url: String(normalizedInput),
364
+ error: getErrorDetails(error),
365
+ errorDebug: getErrorDebug(error),
366
+ });
367
+ throw error;
368
+ }
87
369
  };
88
- /**
89
- * Connects to the Midnight Lace wallet extension.
90
- *
91
- * @param logger The logger instance for logging.
92
- * @param networkId The network ID to connect to.
93
- * @returns A promise that resolves to the wallet API and configuration.
94
- */
95
- const connectToWallet = (logger, networkId) => {
96
- const COMPATIBLE_CONNECTOR_API_VERSION = "4.x";
97
- return firstValueFrom(fnPipe(interval(100), map(() => {
98
- // Find wallet in window.midnight
99
- const midnight = window.midnight;
100
- if (!midnight)
101
- return undefined;
102
- // The new API uses rdns keys instead of named keys like mnLace
103
- // Look for any available wallet
104
- const walletKeys = Object.keys(midnight).filter((key) => key !== "addEventListener");
105
- if (walletKeys.length === 0)
106
- return undefined;
107
- return midnight[walletKeys[0]];
108
- }), tap((connectorAPI) => {
109
- logger.info(connectorAPI, "Check for wallet connector API");
110
- }), filter((connectorAPI) => !!connectorAPI), concatMap((connectorAPI) => semver.satisfies(connectorAPI.apiVersion, COMPATIBLE_CONNECTOR_API_VERSION)
111
- ? of(connectorAPI)
112
- : throwError(() => {
113
- logger.error({
114
- expected: COMPATIBLE_CONNECTOR_API_VERSION,
115
- actual: connectorAPI.apiVersion
116
- }, "Incompatible version of wallet connector API");
117
- return new Error(`Incompatible version of Midnight Lace wallet found. Require '${COMPATIBLE_CONNECTOR_API_VERSION}', got '${connectorAPI.apiVersion}'.`);
118
- })), tap((connectorAPI) => {
119
- logger.info(connectorAPI, "Compatible wallet connector API found. Connecting.");
120
- }), take(1), timeout({
121
- first: 1_000,
122
- with: () => throwError(() => {
123
- logger.error("Could not find wallet connector API");
124
- return new Error("Could not find Midnight Lace wallet. Extension installed?");
125
- })
126
- }), concatMap(async (connectorAPI) => {
127
- // Connect to the wallet with the specified network ID
128
- const connectedAPI = await connectorAPI.connect(networkId);
129
- // Set the network ID globally
130
- setNetworkId(networkId);
131
- logger.info("Connected to wallet");
132
- return { walletConnectorAPI: connectedAPI, connectorAPI };
133
- }), timeout({
134
- first: 30_000, // Increased timeout for user approval
135
- with: () => throwError(() => {
136
- logger.error("Wallet connector API has failed to respond");
137
- return new Error("Midnight Lace wallet has failed to respond. Extension enabled?");
138
- })
139
- }), catchError((error, apis) => error
140
- ? throwError(() => {
141
- logger.error("Unable to connect to wallet");
142
- return new Error("Application is not authorized or connection failed");
143
- })
144
- : apis), concatMap(async ({ walletConnectorAPI }) => {
145
- const config = await walletConnectorAPI.getConfiguration();
146
- logger.info("Connected to wallet connector API and retrieved service configuration");
147
- return { wallet: walletConnectorAPI, config };
148
- })));
370
+ function getBech32mNetworkId(value, networkId) {
371
+ if (networkId) {
372
+ return networkId;
373
+ }
374
+ const parsed = MidnightBech32m.parse(value);
375
+ return parsed.network === mainnet ? 'mainnet' : String(parsed.network);
376
+ }
377
+ function normalizeShieldedCoinPublicKey(value, networkId) {
378
+ if (!value.startsWith('mn_')) {
379
+ return value;
380
+ }
381
+ const parsed = MidnightBech32m.parse(value);
382
+ const resolvedNetworkId = getBech32mNetworkId(value, networkId);
383
+ if (parsed.type === 'shield-cpk') {
384
+ return ShieldedCoinPublicKey.codec.decode(resolvedNetworkId, parsed).toHexString();
385
+ }
386
+ if (parsed.type === 'shield-addr') {
387
+ return parsed.decode(ShieldedAddress, resolvedNetworkId).coinPublicKeyString();
388
+ }
389
+ throw new Error(`Expected shielded coin public key or shielded address, got ${parsed.type}`);
390
+ }
391
+ function normalizeShieldedEncryptionPublicKey(value, networkId) {
392
+ if (!value.startsWith('mn_')) {
393
+ return value;
394
+ }
395
+ const parsed = MidnightBech32m.parse(value);
396
+ const resolvedNetworkId = getBech32mNetworkId(value, networkId);
397
+ if (parsed.type === 'shield-epk') {
398
+ return ShieldedEncryptionPublicKey.codec.decode(resolvedNetworkId, parsed).toHexString();
399
+ }
400
+ if (parsed.type === 'shield-addr') {
401
+ return parsed.decode(ShieldedAddress, resolvedNetworkId).encryptionPublicKeyString();
402
+ }
403
+ throw new Error(`Expected shielded encryption public key or shielded address, got ${parsed.type}`);
404
+ }
405
+ export function parseMidnightRecipientAddress(value, networkId) {
406
+ const trimmed = value.trim();
407
+ if (!trimmed) {
408
+ throw new Error('Recipient address is required');
409
+ }
410
+ if (trimmed.startsWith('mn_')) {
411
+ const parsed = MidnightBech32m.parse(trimmed);
412
+ const resolvedNetworkId = getBech32mNetworkId(trimmed, networkId);
413
+ if (parsed.type === 'shield-cpk' || parsed.type === 'shield-addr') {
414
+ const encoded = encodeCoinPublicKey(normalizeShieldedCoinPublicKey(trimmed, resolvedNetworkId));
415
+ if (encoded.length !== 32) {
416
+ throw new Error(`Midnight recipient public key must be 32 bytes, got ${encoded.length}`);
417
+ }
418
+ return encoded;
419
+ }
420
+ if (parsed.type === 'addr') {
421
+ return hexToBytes(parsed.decode(UnshieldedAddress, resolvedNetworkId).hexString);
422
+ }
423
+ throw new Error(`Expected shielded address, shielded coin public key, or unshielded address, got ${parsed.type}`);
424
+ }
425
+ const recipient = hexToBytes(trimmed);
426
+ if (recipient.length !== 32) {
427
+ throw new Error(`Recipient hex must encode exactly 32 bytes, got ${recipient.length}`);
428
+ }
429
+ return recipient;
430
+ }
431
+ async function getAccountId(wallet, override) {
432
+ if (override) {
433
+ return override;
434
+ }
435
+ const { unshieldedAddress } = await wallet.getUnshieldedAddress();
436
+ return unshieldedAddress;
437
+ }
438
+ async function balanceWithWallet(wallet, tx) {
439
+ const unsealedTx = serializeTxToHex(tx);
440
+ try {
441
+ console.log('[MIDNIGHT] Requesting wallet balance', { mode: 'unsealed', bytes: unsealedTx.length / 2 });
442
+ const result = await wallet.balanceUnsealedTransaction(unsealedTx);
443
+ console.log('[MIDNIGHT] Wallet balance succeeded', { mode: 'unsealed' });
444
+ return result;
445
+ }
446
+ catch (unsealedError) {
447
+ const fundingDebug = await getWalletFundingDebug(wallet);
448
+ console.error('[MIDNIGHT] Wallet balance failed', {
449
+ mode: 'unsealed',
450
+ error: getErrorDetails(unsealedError),
451
+ errorDebug: getErrorDebug(unsealedError),
452
+ funding: fundingDebug,
453
+ });
454
+ const walletError = getErrorDetails(unsealedError) || 'Wallet failed to balance unsealed transaction without returning an error message';
455
+ throw new Error(`${walletError}. ${describeWalletFundingDebug(fundingDebug)}`);
456
+ }
457
+ }
458
+ async function createGovernorProofProvider(wallet, config, zkConfigProvider, profile, proofServerUriOverride) {
459
+ const makeLoggedProvider = (source, provider, fallback) => ({
460
+ proveTx: async (unprovenTx, proveTxConfig) => {
461
+ console.log('[MIDNIGHT] Proving transaction', { profile, source });
462
+ try {
463
+ const provenTx = await provider.proveTx(unprovenTx, proveTxConfig);
464
+ console.log('[MIDNIGHT] Proved transaction', { profile, source });
465
+ return provenTx;
466
+ }
467
+ catch (error) {
468
+ console.error('[MIDNIGHT] Proof provider failed', {
469
+ profile,
470
+ source,
471
+ error: getErrorDetails(error),
472
+ errorDebug: getErrorDebug(error),
473
+ fallback: fallback ? 'available' : 'none',
474
+ });
475
+ if (!fallback) {
476
+ throw error;
477
+ }
478
+ console.warn('[MIDNIGHT] Retrying proof with fallback provider', { profile, from: source, to: 'http' });
479
+ return fallback.proveTx(unprovenTx, proveTxConfig);
480
+ }
481
+ },
482
+ });
483
+ if (proofServerUriOverride) {
484
+ return makeLoggedProvider('http-override', httpClientProofProvider(resolveBrowserUrl(proofServerUriOverride), zkConfigProvider));
485
+ }
486
+ const httpFallbackProvider = config.proverServerUri
487
+ ? httpClientProofProvider(resolveBrowserUrl(config.proverServerUri), zkConfigProvider)
488
+ : undefined;
489
+ try {
490
+ const walletProvingProvider = await wallet.getProvingProvider(zkConfigProvider.asKeyMaterialProvider());
491
+ return makeLoggedProvider('wallet', createProofProvider(walletProvingProvider), httpFallbackProvider);
492
+ }
493
+ catch (walletProofError) {
494
+ if (config.proverServerUri) {
495
+ return makeLoggedProvider('http-config', httpFallbackProvider);
496
+ }
497
+ throw new Error(`No proof provider available. Wallet getProvingProvider failed: ${getErrorDetails(walletProofError)}. Provide proofServerUri or configure a wallet proofstation.`);
498
+ }
499
+ }
500
+ function getInjectedInitialAPI(walletName) {
501
+ if (typeof window === 'undefined' || !window.midnight) {
502
+ throw new Error('Could not find Midnight wallet. Extension installed?');
503
+ }
504
+ const entries = Object.entries(window.midnight);
505
+ const normalizedWalletName = walletName?.toLowerCase();
506
+ const found = walletName
507
+ ? entries.find(([key, api]) => key.toLowerCase() === normalizedWalletName ||
508
+ api.name.toLowerCase() === normalizedWalletName ||
509
+ api.rdns.toLowerCase() === normalizedWalletName)?.[1] ??
510
+ window.midnight[walletName]
511
+ : entries[0]?.[1];
512
+ if (!found) {
513
+ throw new Error(walletName ? `Could not find Midnight wallet '${walletName}'` : 'Could not find Midnight wallet');
514
+ }
515
+ return found;
516
+ }
517
+ export async function connectMidnightWallet(initialAPI, networkId) {
518
+ const connectableInitialAPI = initialAPI;
519
+ console.log('[MIDNIGHT] Requesting wallet connection', {
520
+ walletName: initialAPI.name,
521
+ rdns: initialAPI.rdns,
522
+ apiVersion: initialAPI.apiVersion,
523
+ networkId,
524
+ });
525
+ const connectedAPI = await withTimeout(networkId ? connectableInitialAPI.connect(networkId) : connectableInitialAPI.connect(), 30_000, `Wallet '${initialAPI.name}' did not resolve connect(${networkId ? `'${networkId}'` : ''}) within 30 seconds. ` +
526
+ 'Open the wallet extension, approve any pending site connection prompt, verify the wallet is on the requested network, then retry.');
527
+ if (!connectedAPI) {
528
+ throw new Error('Failed to connect to Midnight wallet');
529
+ }
530
+ console.log('[MIDNIGHT] Wallet connection resolved', {
531
+ walletName: initialAPI.name,
532
+ networkId,
533
+ });
534
+ if (typeof connectedAPI.hintUsage === 'function') {
535
+ try {
536
+ console.log('[MIDNIGHT] Sending wallet usage hints');
537
+ await connectedAPI.hintUsage(MIDNIGHT_HINT_METHODS);
538
+ console.log('[MIDNIGHT] Wallet usage hints resolved');
539
+ }
540
+ catch {
541
+ // Wallets may reject hints independently of the actual connection.
542
+ console.warn('[MIDNIGHT] Wallet usage hints were rejected or ignored');
543
+ }
544
+ }
545
+ return connectedAPI;
546
+ }
547
+ export async function encodeWalletUserId(walletOrCoinPublicKey, networkId) {
548
+ const coinPublicKey = typeof walletOrCoinPublicKey === 'string'
549
+ ? normalizeShieldedCoinPublicKey(walletOrCoinPublicKey, networkId)
550
+ : normalizeShieldedCoinPublicKey((await walletOrCoinPublicKey.getShieldedAddresses()).shieldedCoinPublicKey, (await walletOrCoinPublicKey.getConfiguration()).networkId);
551
+ const encoded = encodeCoinPublicKey(coinPublicKey);
552
+ if (encoded.length !== 32) {
553
+ throw new Error(`Midnight wallet public key must be 32 bytes, got ${encoded.length}`);
554
+ }
555
+ return encoded;
556
+ }
557
+ export async function initializeGovernorProviders(wallet, options = {}) {
558
+ const [config, shieldedAddresses, accountId] = await Promise.all([
559
+ wallet.getConfiguration(),
560
+ wallet.getShieldedAddresses(),
561
+ getAccountId(wallet, options.accountId),
562
+ ]);
563
+ setNetworkId(config.networkId);
564
+ const walletPublicKey = await encodeWalletUserId(shieldedAddresses.shieldedCoinPublicKey, config.networkId);
565
+ const baseZkConfigProvider = new FetchZkConfigProvider(getZkConfigBaseUrl(options.zkConfigBaseUrl, 'base'), fetchGovernorArtifact);
566
+ const fullZkConfigProvider = new FetchZkConfigProvider(getZkConfigBaseUrl(options.zkConfigBaseUrl, 'full'), fetchGovernorArtifact);
567
+ const [baseProofProvider, fullProofProvider] = await Promise.all([
568
+ createGovernorProofProvider(wallet, config, baseZkConfigProvider, 'base', options.proofServerUri),
569
+ createGovernorProofProvider(wallet, config, fullZkConfigProvider, 'full', options.proofServerUri),
570
+ ]);
571
+ const finalizedTxHexById = new Map();
572
+ const providers = {
573
+ privateStateProvider: createLocalStoragePrivateStateProvider(accountId),
574
+ zkConfigProvider: fullZkConfigProvider,
575
+ proofProvider: fullProofProvider,
576
+ publicDataProvider: indexerPublicDataProvider(config.indexerUri, config.indexerWsUri),
577
+ walletProvider: {
578
+ balanceTx: async (tx) => {
579
+ const result = await balanceWithWallet(wallet, tx);
580
+ if (!result.tx || typeof result.tx !== 'string') {
581
+ throw new Error('Wallet returned an empty balanced transaction');
582
+ }
583
+ const finalizedTx = deserializeFinalizedTransaction(result.tx);
584
+ const txId = finalizedTx.identifiers()[0];
585
+ if (txId) {
586
+ finalizedTxHexById.set(txId, stripHexPrefix(result.tx));
587
+ }
588
+ return finalizedTx;
589
+ },
590
+ getCoinPublicKey: () => shieldedAddresses.shieldedCoinPublicKey,
591
+ getEncryptionPublicKey: () => shieldedAddresses.shieldedEncryptionPublicKey,
592
+ },
593
+ midnightProvider: {
594
+ submitTx: async (tx) => {
595
+ const txId = tx.identifiers()[0];
596
+ if (!txId) {
597
+ throw new Error('Unable to derive transaction identifier');
598
+ }
599
+ const txHex = finalizedTxHexById.get(txId) ?? serializeTxToHex(tx);
600
+ const submissionDetails = {
601
+ txId,
602
+ bytes: txHex.length / 2,
603
+ source: finalizedTxHexById.has(txId) ? 'wallet-balanced' : 'serialized',
604
+ ledgerCost: getSubmittedTransactionCostDebug(txHex),
605
+ };
606
+ console.log('[MIDNIGHT] Submitting transaction', submissionDetails);
607
+ try {
608
+ await wallet.submitTransaction(txHex);
609
+ }
610
+ catch (error) {
611
+ if (isTemporarilyBannedError(error)) {
612
+ console.warn('[MIDNIGHT] Transaction is temporarily banned; watching for existing submission', {
613
+ ...submissionDetails,
614
+ error: getErrorDetails(error),
615
+ errorDebug: getErrorDebug(error),
616
+ });
617
+ finalizedTxHexById.delete(txId);
618
+ return txId;
619
+ }
620
+ console.error('[MIDNIGHT] Submit transaction failed', {
621
+ ...submissionDetails,
622
+ error: getErrorDetails(error),
623
+ errorDebug: getErrorDebug(error),
624
+ });
625
+ throw error;
626
+ }
627
+ finalizedTxHexById.delete(txId);
628
+ return txId;
629
+ },
630
+ },
631
+ governorProfiles: {
632
+ base: {
633
+ zkConfigProvider: baseZkConfigProvider,
634
+ proofProvider: baseProofProvider,
635
+ },
636
+ full: {
637
+ zkConfigProvider: fullZkConfigProvider,
638
+ proofProvider: fullProofProvider,
639
+ },
640
+ },
641
+ };
642
+ if (options.contractAddress) {
643
+ providers.privateStateProvider.setContractAddress(options.contractAddress);
644
+ }
645
+ return {
646
+ providers,
647
+ config,
648
+ walletPublicKey,
649
+ accountId,
650
+ };
651
+ }
652
+ export const initializeProviders = async (logger, options = {}) => {
653
+ const initialAPI = options.initialAPI ?? getInjectedInitialAPI(options.walletName);
654
+ logger.info({ walletName: initialAPI.name, networkId: options.networkId }, options.networkId ? 'Connecting Midnight wallet with requested network' : 'Connecting Midnight wallet with configured network');
655
+ const wallet = await connectMidnightWallet(initialAPI, options.networkId);
656
+ logger.info({ walletName: initialAPI.name }, 'Connected Midnight wallet');
657
+ logger.info({ walletName: initialAPI.name }, 'Initializing governor providers');
658
+ const { providers } = await initializeGovernorProviders(wallet, options);
659
+ logger.info({ walletName: initialAPI.name }, 'Initialized governor providers');
660
+ return providers;
149
661
  };
662
+ export function stripHexPrefix(value) {
663
+ return value.startsWith('0x') ? value.slice(2) : value;
664
+ }
150
665
  //# sourceMappingURL=browser-providers.js.map