@claritydao/midnight-agora-sdk 0.1.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.
- package/README.md +58 -55
- package/dist/browser-providers.d.ts +24 -19
- package/dist/browser-providers.d.ts.map +1 -1
- package/dist/browser-providers.js +654 -136
- package/dist/browser-providers.js.map +1 -1
- package/dist/common-types.d.ts +13 -24
- package/dist/common-types.d.ts.map +1 -1
- package/dist/common-types.js +1 -1
- package/dist/common-types.js.map +1 -1
- package/dist/governor-api.d.ts +42 -17
- package/dist/governor-api.d.ts.map +1 -1
- package/dist/governor-api.js +233 -248
- package/dist/governor-api.js.map +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -9
- package/dist/index.js.map +1 -1
- package/dist/manager.d.ts +11 -9
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +17 -17
- package/dist/manager.js.map +1 -1
- package/dist/types.d.ts +9 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +26 -26
- package/.prettierrc +0 -1
- package/LICENSE +0 -0
- package/eslint.config.mjs +0 -48
- package/src/browser-providers.ts +0 -274
- package/src/common-types.ts +0 -51
- package/src/errors.ts +0 -124
- package/src/governor-api.ts +0 -948
- package/src/index.ts +0 -51
- package/src/manager.ts +0 -203
- package/src/types.ts +0 -403
- package/src/utils/index.ts +0 -60
- package/src/validators.ts +0 -245
- package/test/README.md +0 -409
- package/test/errors.test.ts +0 -179
- package/test/integration/governor-api.test.ts +0 -370
- package/test/mocks/providers.ts +0 -130
- package/test/types.test.ts +0 -112
- package/test/utils.test.ts +0 -111
- package/test/validators.test.ts +0 -368
- package/test/wasm-init.test.ts +0 -132
- package/tsconfig.json +0 -18
- package/vitest.config.ts +0 -9
|
@@ -1,147 +1,665 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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) {
|
|
31
40
|
return {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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(),
|
|
49
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.');
|
|
50
214
|
}
|
|
215
|
+
const contractScope = includeContractAddress ? `${contractAddress}:` : '';
|
|
216
|
+
return `${prefix}${accountId}:${contractScope}${key}`;
|
|
51
217
|
};
|
|
52
|
-
};
|
|
53
|
-
/**
|
|
54
|
-
* Creates a WalletProvider from the connected wallet API.
|
|
55
|
-
*/
|
|
56
|
-
const createWalletProvider = (wallet, addresses) => {
|
|
57
|
-
// Note: The bech32m addresses need to be converted to the expected key types
|
|
58
|
-
// This conversion may need adjustment based on the actual key format expected
|
|
59
|
-
const coinPublicKey = addresses.shieldedCoinPublicKey;
|
|
60
|
-
const encryptionPublicKey = addresses.shieldedEncryptionPublicKey;
|
|
61
218
|
return {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
return
|
|
67
|
-
},
|
|
68
|
-
async
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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}:`;
|
|
78
260
|
return {
|
|
79
|
-
|
|
80
|
-
|
|
261
|
+
version: 1,
|
|
262
|
+
entries: listStorageKeys(privateStatePrefix).map((key) => ({
|
|
263
|
+
key,
|
|
264
|
+
value: assertBrowserStorage().getItem(key),
|
|
265
|
+
})),
|
|
81
266
|
};
|
|
82
|
-
}
|
|
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
|
+
},
|
|
83
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
|
+
}
|
|
84
369
|
};
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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;
|
|
146
661
|
};
|
|
662
|
+
export function stripHexPrefix(value) {
|
|
663
|
+
return value.startsWith('0x') ? value.slice(2) : value;
|
|
664
|
+
}
|
|
147
665
|
//# sourceMappingURL=browser-providers.js.map
|