@miden-sdk/react 0.13.2 → 0.14.0-alpha
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/CLAUDE.md +28 -0
- package/README.md +402 -6
- package/dist/index.d.mts +275 -23
- package/dist/index.d.ts +275 -23
- package/dist/index.js +1168 -396
- package/dist/index.mjs +1154 -383
- package/package.json +9 -2
package/dist/index.mjs
CHANGED
|
@@ -29,6 +29,7 @@ var initialState = {
|
|
|
29
29
|
notes: [],
|
|
30
30
|
consumableNotes: [],
|
|
31
31
|
assetMetadata: /* @__PURE__ */ new Map(),
|
|
32
|
+
noteFirstSeen: /* @__PURE__ */ new Map(),
|
|
32
33
|
isLoadingAccounts: false,
|
|
33
34
|
isLoadingNotes: false
|
|
34
35
|
};
|
|
@@ -56,8 +57,74 @@ var useMidenStore = create()((set) => ({
|
|
|
56
57
|
newMap.set(accountId, account);
|
|
57
58
|
return { accountDetails: newMap };
|
|
58
59
|
}),
|
|
59
|
-
setNotes: (notes) => set(
|
|
60
|
+
setNotes: (notes) => set((state) => {
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const newFirstSeen = /* @__PURE__ */ new Map();
|
|
63
|
+
for (const note of notes) {
|
|
64
|
+
try {
|
|
65
|
+
const id = note.id().toString();
|
|
66
|
+
newFirstSeen.set(id, state.noteFirstSeen.get(id) ?? now);
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { notes, noteFirstSeen: newFirstSeen };
|
|
71
|
+
}),
|
|
72
|
+
setNotesIfChanged: (notes) => set((state) => {
|
|
73
|
+
const safeId = (n) => {
|
|
74
|
+
try {
|
|
75
|
+
return n.id().toString();
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const prevIds = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const n of state.notes) {
|
|
82
|
+
const id = safeId(n);
|
|
83
|
+
if (id) prevIds.add(id);
|
|
84
|
+
}
|
|
85
|
+
const newIds = /* @__PURE__ */ new Set();
|
|
86
|
+
for (const n of notes) {
|
|
87
|
+
const id = safeId(n);
|
|
88
|
+
if (id) newIds.add(id);
|
|
89
|
+
}
|
|
90
|
+
if (prevIds.size === newIds.size && [...prevIds].every((id) => newIds.has(id))) {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const newFirstSeen = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const note of notes) {
|
|
96
|
+
try {
|
|
97
|
+
const id = note.id().toString();
|
|
98
|
+
newFirstSeen.set(id, state.noteFirstSeen.get(id) ?? now);
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { notes, noteFirstSeen: newFirstSeen };
|
|
103
|
+
}),
|
|
60
104
|
setConsumableNotes: (consumableNotes) => set({ consumableNotes }),
|
|
105
|
+
setConsumableNotesIfChanged: (consumableNotes) => set((state) => {
|
|
106
|
+
const safeId = (n) => {
|
|
107
|
+
try {
|
|
108
|
+
return n.inputNoteRecord().id().toString();
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const prevIds = /* @__PURE__ */ new Set();
|
|
114
|
+
for (const n of state.consumableNotes) {
|
|
115
|
+
const id = safeId(n);
|
|
116
|
+
if (id) prevIds.add(id);
|
|
117
|
+
}
|
|
118
|
+
const newIds = /* @__PURE__ */ new Set();
|
|
119
|
+
for (const n of consumableNotes) {
|
|
120
|
+
const id = safeId(n);
|
|
121
|
+
if (id) newIds.add(id);
|
|
122
|
+
}
|
|
123
|
+
if (prevIds.size === newIds.size && [...prevIds].every((id) => newIds.has(id))) {
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
return { consumableNotes };
|
|
127
|
+
}),
|
|
61
128
|
setAssetMetadata: (assetId, metadata) => set((state) => {
|
|
62
129
|
const newMap = new Map(state.assetMetadata);
|
|
63
130
|
newMap.set(assetId, metadata);
|
|
@@ -72,6 +139,7 @@ var useAccountsStore = () => useMidenStore((state) => state.accounts);
|
|
|
72
139
|
var useNotesStore = () => useMidenStore((state) => state.notes);
|
|
73
140
|
var useConsumableNotesStore = () => useMidenStore((state) => state.consumableNotes);
|
|
74
141
|
var useAssetMetadataStore = () => useMidenStore((state) => state.assetMetadata);
|
|
142
|
+
var useNoteFirstSeenStore = () => useMidenStore((state) => state.noteFirstSeen);
|
|
75
143
|
|
|
76
144
|
// src/utils/accountParsing.ts
|
|
77
145
|
import { AccountId, Address } from "@miden-sdk/miden-sdk";
|
|
@@ -95,6 +163,19 @@ var parseAccountId = (value) => {
|
|
|
95
163
|
const normalized = normalizeAccountIdInput(value);
|
|
96
164
|
return parseAccountIdFromString(normalized);
|
|
97
165
|
};
|
|
166
|
+
function isFaucetId(accountId) {
|
|
167
|
+
try {
|
|
168
|
+
let hex = typeof accountId.toHex === "function" ? accountId.toHex() : String(accountId);
|
|
169
|
+
if (hex.startsWith("0x") || hex.startsWith("0X")) {
|
|
170
|
+
hex = hex.slice(2);
|
|
171
|
+
}
|
|
172
|
+
const firstByte = parseInt(hex.slice(0, 2), 16);
|
|
173
|
+
const accountType = firstByte >> 4 & 3;
|
|
174
|
+
return accountType === 2 || accountType === 3;
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
98
179
|
var parseAddress = (value, accountId) => {
|
|
99
180
|
const normalized = normalizeAccountIdInput(value);
|
|
100
181
|
if (isBech32Input(normalized)) {
|
|
@@ -207,16 +288,17 @@ import {
|
|
|
207
288
|
useMemo,
|
|
208
289
|
useState
|
|
209
290
|
} from "react";
|
|
210
|
-
import { WebClient } from "@miden-sdk/miden-sdk";
|
|
291
|
+
import { WasmWebClient as WebClient } from "@miden-sdk/miden-sdk";
|
|
211
292
|
|
|
212
293
|
// src/types/index.ts
|
|
294
|
+
import { AuthScheme } from "@miden-sdk/miden-sdk";
|
|
213
295
|
var DEFAULTS = {
|
|
214
296
|
RPC_URL: void 0,
|
|
215
297
|
// Will use SDK's testnet default
|
|
216
298
|
AUTO_SYNC_INTERVAL: 15e3,
|
|
217
299
|
STORAGE_MODE: "private",
|
|
218
300
|
WALLET_MUTABLE: true,
|
|
219
|
-
AUTH_SCHEME:
|
|
301
|
+
AUTH_SCHEME: AuthScheme.AuthRpoFalcon512,
|
|
220
302
|
NOTE_TYPE: "private",
|
|
221
303
|
FAUCET_DECIMALS: 8
|
|
222
304
|
};
|
|
@@ -320,38 +402,41 @@ function useSigner() {
|
|
|
320
402
|
}
|
|
321
403
|
|
|
322
404
|
// src/utils/signerAccount.ts
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
return AccountType.FungibleFaucet;
|
|
332
|
-
case "NonFungibleFaucet":
|
|
333
|
-
return AccountType.NonFungibleFaucet;
|
|
334
|
-
default:
|
|
335
|
-
return AccountType.RegularAccountImmutableCode;
|
|
336
|
-
}
|
|
405
|
+
var WASM_ACCOUNT_TYPE = {
|
|
406
|
+
FungibleFaucet: 0,
|
|
407
|
+
NonFungibleFaucet: 1,
|
|
408
|
+
RegularAccountImmutableCode: 2,
|
|
409
|
+
RegularAccountUpdatableCode: 3
|
|
410
|
+
};
|
|
411
|
+
function getAccountType(accountType) {
|
|
412
|
+
return WASM_ACCOUNT_TYPE[accountType] ?? WASM_ACCOUNT_TYPE.RegularAccountImmutableCode;
|
|
337
413
|
}
|
|
338
414
|
function isPrivateStorageMode(storageMode) {
|
|
339
415
|
return storageMode.toString() === "private";
|
|
340
416
|
}
|
|
341
417
|
async function initializeSignerAccount(client, config) {
|
|
342
|
-
const { AccountBuilder, AccountComponent, Word } = await import("@miden-sdk/miden-sdk");
|
|
418
|
+
const { AccountBuilder, AccountComponent, AuthScheme: AuthScheme2, Word: Word2 } = await import("@miden-sdk/miden-sdk");
|
|
343
419
|
await client.syncState();
|
|
344
|
-
const commitmentWord =
|
|
420
|
+
const commitmentWord = Word2.deserialize(config.publicKeyCommitment);
|
|
345
421
|
const seed = config.accountSeed ?? crypto.getRandomValues(new Uint8Array(32));
|
|
346
|
-
const accountType =
|
|
347
|
-
|
|
348
|
-
const buildResult = builder.withAuthComponent(
|
|
422
|
+
const accountType = getAccountType(config.accountType);
|
|
423
|
+
let builder = new AccountBuilder(seed).withAuthComponent(
|
|
349
424
|
AccountComponent.createAuthComponentFromCommitment(
|
|
350
425
|
commitmentWord,
|
|
351
|
-
|
|
352
|
-
// ECDSA auth scheme (K256/Keccak)
|
|
426
|
+
AuthScheme2.AuthEcdsaK256Keccak
|
|
353
427
|
)
|
|
354
|
-
).accountType(accountType).storageMode(config.storageMode).withBasicWalletComponent()
|
|
428
|
+
).accountType(accountType).storageMode(config.storageMode).withBasicWalletComponent();
|
|
429
|
+
if (config.customComponents?.length) {
|
|
430
|
+
for (const component of config.customComponents) {
|
|
431
|
+
if (component == null || typeof component.getProcedures !== "function") {
|
|
432
|
+
throw new Error(
|
|
433
|
+
"Each entry in customComponents must be an AccountComponent instance created via AccountComponent.compile(), AccountComponent.fromPackage(), or AccountComponent.fromLibrary()."
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
builder = builder.withComponent(component);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const buildResult = builder.build();
|
|
355
440
|
const account = buildResult.account;
|
|
356
441
|
const accountId = account.id();
|
|
357
442
|
if (!isPrivateStorageMode(config.storageMode)) {
|
|
@@ -455,9 +540,6 @@ function MidenProvider({
|
|
|
455
540
|
}
|
|
456
541
|
return;
|
|
457
542
|
}
|
|
458
|
-
if (!signerContext) {
|
|
459
|
-
isInitializedRef.current = true;
|
|
460
|
-
}
|
|
461
543
|
let cancelled = false;
|
|
462
544
|
const initClient = async () => {
|
|
463
545
|
await runExclusive(async () => {
|
|
@@ -517,6 +599,9 @@ function MidenProvider({
|
|
|
517
599
|
}
|
|
518
600
|
}
|
|
519
601
|
if (!cancelled) {
|
|
602
|
+
if (!signerContext) {
|
|
603
|
+
isInitializedRef.current = true;
|
|
604
|
+
}
|
|
520
605
|
setClient(webClient);
|
|
521
606
|
}
|
|
522
607
|
} catch (error) {
|
|
@@ -531,6 +616,7 @@ function MidenProvider({
|
|
|
531
616
|
initClient();
|
|
532
617
|
return () => {
|
|
533
618
|
cancelled = true;
|
|
619
|
+
isInitializedRef.current = false;
|
|
534
620
|
};
|
|
535
621
|
}, [
|
|
536
622
|
runExclusive,
|
|
@@ -646,16 +732,6 @@ function useAccounts() {
|
|
|
646
732
|
refetch
|
|
647
733
|
};
|
|
648
734
|
}
|
|
649
|
-
function isFaucetId(accountId) {
|
|
650
|
-
try {
|
|
651
|
-
const hex = typeof accountId.toHex === "function" ? accountId.toHex() : String(accountId);
|
|
652
|
-
const firstByte = parseInt(hex.slice(0, 2), 16);
|
|
653
|
-
const accountType = firstByte >> 4 & 3;
|
|
654
|
-
return accountType === 2 || accountType === 3;
|
|
655
|
-
} catch {
|
|
656
|
-
return false;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
735
|
|
|
660
736
|
// src/hooks/useAccount.ts
|
|
661
737
|
import { useCallback as useCallback3, useEffect as useEffect4, useState as useState2, useMemo as useMemo3 } from "react";
|
|
@@ -682,17 +758,12 @@ var getRpcClient = (rpcUrl) => {
|
|
|
682
758
|
return null;
|
|
683
759
|
}
|
|
684
760
|
};
|
|
685
|
-
var fetchAssetMetadata = async (
|
|
761
|
+
var fetchAssetMetadata = async (rpcClient, assetId) => {
|
|
686
762
|
try {
|
|
687
763
|
const accountId = parseAccountId(assetId);
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const fetched = await rpcClient.getAccountDetails(accountId);
|
|
692
|
-
account = fetched.account?.();
|
|
693
|
-
} catch {
|
|
694
|
-
}
|
|
695
|
-
}
|
|
764
|
+
if (!isFaucetId(accountId)) return null;
|
|
765
|
+
const fetched = await rpcClient.getAccountDetails(accountId);
|
|
766
|
+
const account = fetched.account?.();
|
|
696
767
|
if (!account) return null;
|
|
697
768
|
const faucet = BasicFungibleFaucetComponent.fromAccount(account);
|
|
698
769
|
const symbol = faucet.symbol().toString();
|
|
@@ -703,8 +774,6 @@ var fetchAssetMetadata = async (client, rpcClient, assetId) => {
|
|
|
703
774
|
}
|
|
704
775
|
};
|
|
705
776
|
function useAssetMetadata(assetIds = []) {
|
|
706
|
-
const { client, isReady, runExclusive } = useMiden();
|
|
707
|
-
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
708
777
|
const assetMetadata = useAssetMetadataStore();
|
|
709
778
|
const setAssetMetadata = useMidenStore((state) => state.setAssetMetadata);
|
|
710
779
|
const rpcUrl = useMidenStore((state) => state.config.rpcUrl);
|
|
@@ -714,32 +783,19 @@ function useAssetMetadata(assetIds = []) {
|
|
|
714
783
|
[assetIds]
|
|
715
784
|
);
|
|
716
785
|
useEffect3(() => {
|
|
717
|
-
if (!
|
|
786
|
+
if (!rpcClient || uniqueAssetIds.length === 0) return;
|
|
718
787
|
uniqueAssetIds.forEach((assetId) => {
|
|
719
788
|
const existing = assetMetadata.get(assetId);
|
|
720
789
|
const hasMetadata = existing?.symbol !== void 0 || existing?.decimals !== void 0;
|
|
721
790
|
if (hasMetadata || inflight.has(assetId)) return;
|
|
722
|
-
const promise =
|
|
723
|
-
const metadata = await fetchAssetMetadata(
|
|
724
|
-
client,
|
|
725
|
-
rpcClient,
|
|
726
|
-
assetId
|
|
727
|
-
);
|
|
791
|
+
const promise = fetchAssetMetadata(rpcClient, assetId).then((metadata) => {
|
|
728
792
|
setAssetMetadata(assetId, metadata ?? { assetId });
|
|
729
793
|
}).finally(() => {
|
|
730
794
|
inflight.delete(assetId);
|
|
731
795
|
});
|
|
732
796
|
inflight.set(assetId, promise);
|
|
733
797
|
});
|
|
734
|
-
}, [
|
|
735
|
-
client,
|
|
736
|
-
isReady,
|
|
737
|
-
uniqueAssetIds,
|
|
738
|
-
assetMetadata,
|
|
739
|
-
runExclusiveSafe,
|
|
740
|
-
setAssetMetadata,
|
|
741
|
-
rpcClient
|
|
742
|
-
]);
|
|
798
|
+
}, [uniqueAssetIds, assetMetadata, setAssetMetadata, rpcClient]);
|
|
743
799
|
return { assetMetadata };
|
|
744
800
|
}
|
|
745
801
|
|
|
@@ -841,7 +897,7 @@ function useAccount(accountId) {
|
|
|
841
897
|
|
|
842
898
|
// src/hooks/useNotes.ts
|
|
843
899
|
import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo4, useState as useState3 } from "react";
|
|
844
|
-
import { NoteFilter
|
|
900
|
+
import { NoteFilter } from "@miden-sdk/miden-sdk";
|
|
845
901
|
|
|
846
902
|
// src/utils/amounts.ts
|
|
847
903
|
var formatAssetAmount = (amount, decimals) => {
|
|
@@ -925,6 +981,86 @@ var formatNoteSummary = (summary, formatAsset = (asset) => `${formatAssetAmount(
|
|
|
925
981
|
return summary.sender ? `${assetsText} from ${summary.sender}` : assetsText;
|
|
926
982
|
};
|
|
927
983
|
|
|
984
|
+
// src/utils/accountId.ts
|
|
985
|
+
function normalizeAccountId(id) {
|
|
986
|
+
return toBech32AccountId(id);
|
|
987
|
+
}
|
|
988
|
+
function accountIdsEqual(a, b) {
|
|
989
|
+
let idA;
|
|
990
|
+
let idB;
|
|
991
|
+
try {
|
|
992
|
+
idA = parseAccountId(a);
|
|
993
|
+
idB = parseAccountId(b);
|
|
994
|
+
return idA.toString() === idB.toString();
|
|
995
|
+
} catch {
|
|
996
|
+
return a === b;
|
|
997
|
+
} finally {
|
|
998
|
+
idA?.free?.();
|
|
999
|
+
idB?.free?.();
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/utils/noteFilters.ts
|
|
1004
|
+
import {
|
|
1005
|
+
NoteFilterTypes,
|
|
1006
|
+
NoteType,
|
|
1007
|
+
TransactionFilter
|
|
1008
|
+
} from "@miden-sdk/miden-sdk";
|
|
1009
|
+
function getNoteFilterType(status) {
|
|
1010
|
+
switch (status) {
|
|
1011
|
+
case "consumed":
|
|
1012
|
+
return NoteFilterTypes.Consumed;
|
|
1013
|
+
case "committed":
|
|
1014
|
+
return NoteFilterTypes.Committed;
|
|
1015
|
+
case "expected":
|
|
1016
|
+
return NoteFilterTypes.Expected;
|
|
1017
|
+
case "processing":
|
|
1018
|
+
return NoteFilterTypes.Processing;
|
|
1019
|
+
case "all":
|
|
1020
|
+
default:
|
|
1021
|
+
return NoteFilterTypes.All;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
function getNoteType(type) {
|
|
1025
|
+
switch (type) {
|
|
1026
|
+
case "private":
|
|
1027
|
+
return NoteType.Private;
|
|
1028
|
+
case "public":
|
|
1029
|
+
return NoteType.Public;
|
|
1030
|
+
default:
|
|
1031
|
+
return NoteType.Private;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
async function waitForTransactionCommit(client, runExclusiveSafe, txIdHex, maxWaitMs = 1e4, delayMs = 1e3) {
|
|
1035
|
+
const deadline = Date.now() + maxWaitMs;
|
|
1036
|
+
const targetHex = normalizeHex(txIdHex);
|
|
1037
|
+
while (Date.now() < deadline) {
|
|
1038
|
+
await runExclusiveSafe(() => client.syncState());
|
|
1039
|
+
const records = await runExclusiveSafe(
|
|
1040
|
+
() => client.getTransactions(TransactionFilter.all())
|
|
1041
|
+
);
|
|
1042
|
+
const record = records.find(
|
|
1043
|
+
(r) => normalizeHex(r.id().toHex()) === targetHex
|
|
1044
|
+
);
|
|
1045
|
+
if (record) {
|
|
1046
|
+
const status = record.transactionStatus();
|
|
1047
|
+
if (status.isCommitted()) {
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
if (status.isDiscarded()) {
|
|
1051
|
+
throw new Error("Transaction was discarded before commit");
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1055
|
+
}
|
|
1056
|
+
throw new Error("Timeout waiting for transaction commit");
|
|
1057
|
+
}
|
|
1058
|
+
function normalizeHex(value) {
|
|
1059
|
+
const trimmed = value.trim();
|
|
1060
|
+
const normalized = trimmed.startsWith("0x") || trimmed.startsWith("0X") ? trimmed : `0x${trimmed}`;
|
|
1061
|
+
return normalized.toLowerCase();
|
|
1062
|
+
}
|
|
1063
|
+
|
|
928
1064
|
// src/hooks/useNotes.ts
|
|
929
1065
|
function useNotes(options) {
|
|
930
1066
|
const { client, isReady, runExclusive } = useMiden();
|
|
@@ -933,8 +1069,10 @@ function useNotes(options) {
|
|
|
933
1069
|
const consumableNotes = useConsumableNotesStore();
|
|
934
1070
|
const isLoadingNotes = useMidenStore((state) => state.isLoadingNotes);
|
|
935
1071
|
const setLoadingNotes = useMidenStore((state) => state.setLoadingNotes);
|
|
936
|
-
const
|
|
937
|
-
const
|
|
1072
|
+
const setNotesIfChanged = useMidenStore((state) => state.setNotesIfChanged);
|
|
1073
|
+
const setConsumableNotesIfChanged = useMidenStore(
|
|
1074
|
+
(state) => state.setConsumableNotesIfChanged
|
|
1075
|
+
);
|
|
938
1076
|
const { lastSyncTime } = useSyncStateStore();
|
|
939
1077
|
const [error, setError] = useState3(null);
|
|
940
1078
|
const refetch = useCallback4(async () => {
|
|
@@ -960,8 +1098,8 @@ function useNotes(options) {
|
|
|
960
1098
|
};
|
|
961
1099
|
}
|
|
962
1100
|
);
|
|
963
|
-
|
|
964
|
-
|
|
1101
|
+
setNotesIfChanged(fetchedNotes);
|
|
1102
|
+
setConsumableNotesIfChanged(fetchedConsumable);
|
|
965
1103
|
} catch (err) {
|
|
966
1104
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
967
1105
|
} finally {
|
|
@@ -974,8 +1112,8 @@ function useNotes(options) {
|
|
|
974
1112
|
options?.status,
|
|
975
1113
|
options?.accountId,
|
|
976
1114
|
setLoadingNotes,
|
|
977
|
-
|
|
978
|
-
|
|
1115
|
+
setNotesIfChanged,
|
|
1116
|
+
setConsumableNotesIfChanged
|
|
979
1117
|
]);
|
|
980
1118
|
useEffect5(() => {
|
|
981
1119
|
if (isReady && notes.length === 0) {
|
|
@@ -1002,14 +1140,65 @@ function useNotes(options) {
|
|
|
1002
1140
|
(assetId) => assetMetadata.get(assetId),
|
|
1003
1141
|
[assetMetadata]
|
|
1004
1142
|
);
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1143
|
+
const normalizedSender = useMemo4(() => {
|
|
1144
|
+
if (!options?.sender) return null;
|
|
1145
|
+
try {
|
|
1146
|
+
return normalizeAccountId(options.sender);
|
|
1147
|
+
} catch {
|
|
1148
|
+
return options.sender;
|
|
1149
|
+
}
|
|
1150
|
+
}, [options?.sender]);
|
|
1151
|
+
const excludeIdsKey = useMemo4(() => {
|
|
1152
|
+
if (!options?.excludeIds || options.excludeIds.length === 0) return "";
|
|
1153
|
+
return [...options.excludeIds].sort().join("\0");
|
|
1154
|
+
}, [options?.excludeIds]);
|
|
1155
|
+
const filterBySender = useCallback4(
|
|
1156
|
+
(summaries, target) => {
|
|
1157
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1158
|
+
return summaries.filter((s) => {
|
|
1159
|
+
if (!s.sender) return false;
|
|
1160
|
+
let normalized = cache.get(s.sender);
|
|
1161
|
+
if (normalized === void 0) {
|
|
1162
|
+
try {
|
|
1163
|
+
normalized = normalizeAccountId(s.sender);
|
|
1164
|
+
} catch {
|
|
1165
|
+
normalized = s.sender;
|
|
1166
|
+
}
|
|
1167
|
+
cache.set(s.sender, normalized);
|
|
1168
|
+
}
|
|
1169
|
+
return normalized === target;
|
|
1170
|
+
});
|
|
1171
|
+
},
|
|
1172
|
+
[]
|
|
1012
1173
|
);
|
|
1174
|
+
const noteSummaries = useMemo4(() => {
|
|
1175
|
+
let summaries = notes.map((note) => getNoteSummary(note, getMetadata)).filter(Boolean);
|
|
1176
|
+
if (normalizedSender) {
|
|
1177
|
+
summaries = filterBySender(summaries, normalizedSender);
|
|
1178
|
+
}
|
|
1179
|
+
if (excludeIdsKey) {
|
|
1180
|
+
const excludeSet = new Set(excludeIdsKey.split("\0"));
|
|
1181
|
+
summaries = summaries.filter((s) => !excludeSet.has(s.id));
|
|
1182
|
+
}
|
|
1183
|
+
return summaries;
|
|
1184
|
+
}, [notes, getMetadata, normalizedSender, excludeIdsKey, filterBySender]);
|
|
1185
|
+
const consumableNoteSummaries = useMemo4(() => {
|
|
1186
|
+
let summaries = consumableNotes.map((note) => getNoteSummary(note, getMetadata)).filter(Boolean);
|
|
1187
|
+
if (normalizedSender) {
|
|
1188
|
+
summaries = filterBySender(summaries, normalizedSender);
|
|
1189
|
+
}
|
|
1190
|
+
if (excludeIdsKey) {
|
|
1191
|
+
const excludeSet = new Set(excludeIdsKey.split("\0"));
|
|
1192
|
+
summaries = summaries.filter((s) => !excludeSet.has(s.id));
|
|
1193
|
+
}
|
|
1194
|
+
return summaries;
|
|
1195
|
+
}, [
|
|
1196
|
+
consumableNotes,
|
|
1197
|
+
getMetadata,
|
|
1198
|
+
normalizedSender,
|
|
1199
|
+
excludeIdsKey,
|
|
1200
|
+
filterBySender
|
|
1201
|
+
]);
|
|
1013
1202
|
return {
|
|
1014
1203
|
notes,
|
|
1015
1204
|
consumableNotes,
|
|
@@ -1020,46 +1209,251 @@ function useNotes(options) {
|
|
|
1020
1209
|
refetch
|
|
1021
1210
|
};
|
|
1022
1211
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1212
|
+
|
|
1213
|
+
// src/hooks/useNoteStream.ts
|
|
1214
|
+
import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef2, useState as useState4 } from "react";
|
|
1215
|
+
import { NoteFilter as NoteFilter2 } from "@miden-sdk/miden-sdk";
|
|
1216
|
+
|
|
1217
|
+
// src/utils/noteAttachment.ts
|
|
1218
|
+
import {
|
|
1219
|
+
NoteAttachment,
|
|
1220
|
+
NoteAttachmentKind,
|
|
1221
|
+
NoteAttachmentScheme,
|
|
1222
|
+
Word
|
|
1223
|
+
} from "@miden-sdk/miden-sdk";
|
|
1224
|
+
function readNoteAttachment(note) {
|
|
1225
|
+
try {
|
|
1226
|
+
const metadata = note.metadata?.();
|
|
1227
|
+
if (!metadata) return null;
|
|
1228
|
+
const attachment = metadata.attachment?.();
|
|
1229
|
+
if (!attachment) return null;
|
|
1230
|
+
const kind = attachment.kind?.();
|
|
1231
|
+
if (!kind) return null;
|
|
1232
|
+
if (kind === NoteAttachmentKind.None) return null;
|
|
1233
|
+
if (kind === NoteAttachmentKind.Word) {
|
|
1234
|
+
const word = attachment.asWord?.();
|
|
1235
|
+
if (!word) return null;
|
|
1236
|
+
const u64s = word.toU64s();
|
|
1237
|
+
const values = Array.from(u64s).map(
|
|
1238
|
+
(v) => BigInt(v)
|
|
1239
|
+
);
|
|
1240
|
+
return { values, kind: "word" };
|
|
1241
|
+
}
|
|
1242
|
+
if (kind === NoteAttachmentKind.Array) {
|
|
1243
|
+
const arr = attachment.asArray?.();
|
|
1244
|
+
if (!arr) return null;
|
|
1245
|
+
const u64s = arr.toU64s();
|
|
1246
|
+
const values = Array.from(u64s).map(
|
|
1247
|
+
(v) => BigInt(v)
|
|
1248
|
+
);
|
|
1249
|
+
return { values, kind: "array" };
|
|
1250
|
+
}
|
|
1251
|
+
return null;
|
|
1252
|
+
} catch {
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
function createNoteAttachment(values) {
|
|
1257
|
+
const bigints = [];
|
|
1258
|
+
for (let i = 0; i < values.length; i++) {
|
|
1259
|
+
bigints.push(BigInt(values[i]));
|
|
1260
|
+
}
|
|
1261
|
+
if (bigints.length === 0) {
|
|
1262
|
+
return new NoteAttachment();
|
|
1263
|
+
}
|
|
1264
|
+
const scheme = NoteAttachmentScheme.none();
|
|
1265
|
+
if (bigints.length <= 4) {
|
|
1266
|
+
while (bigints.length < 4) {
|
|
1267
|
+
bigints.push(0n);
|
|
1268
|
+
}
|
|
1269
|
+
const word = new Word(BigUint64Array.from(bigints));
|
|
1270
|
+
return NoteAttachment.newWord(scheme, word);
|
|
1271
|
+
}
|
|
1272
|
+
while (bigints.length % 4 !== 0) {
|
|
1273
|
+
bigints.push(0n);
|
|
1274
|
+
}
|
|
1275
|
+
const words = [];
|
|
1276
|
+
for (let i = 0; i < bigints.length; i += 4) {
|
|
1277
|
+
words.push(new Word(BigUint64Array.from(bigints.slice(i, i + 4))));
|
|
1036
1278
|
}
|
|
1279
|
+
const newArray = NoteAttachment["newArray"];
|
|
1280
|
+
if (typeof newArray !== "function") {
|
|
1281
|
+
throw new Error(
|
|
1282
|
+
"NoteAttachment.newArray is not available. Ensure @miden-sdk/miden-sdk >= 0.13.1."
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
return newArray.call(NoteAttachment, scheme, words);
|
|
1037
1286
|
}
|
|
1038
1287
|
|
|
1039
|
-
// src/hooks/
|
|
1040
|
-
|
|
1041
|
-
import { TransactionFilter } from "@miden-sdk/miden-sdk";
|
|
1042
|
-
function useTransactionHistory(options = {}) {
|
|
1288
|
+
// src/hooks/useNoteStream.ts
|
|
1289
|
+
function useNoteStream(options = {}) {
|
|
1043
1290
|
const { client, isReady, runExclusive } = useMiden();
|
|
1044
1291
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1292
|
+
const allNotes = useNotesStore();
|
|
1293
|
+
const noteFirstSeen = useNoteFirstSeenStore();
|
|
1045
1294
|
const { lastSyncTime } = useSyncStateStore();
|
|
1046
|
-
const
|
|
1295
|
+
const setNotesIfChanged = useMidenStore((state) => state.setNotesIfChanged);
|
|
1047
1296
|
const [isLoading, setIsLoading] = useState4(false);
|
|
1048
1297
|
const [error, setError] = useState4(null);
|
|
1049
|
-
const
|
|
1298
|
+
const handledIdsRef = useRef2(/* @__PURE__ */ new Set());
|
|
1299
|
+
const [handledVersion, setHandledVersion] = useState4(0);
|
|
1300
|
+
const status = options.status ?? "committed";
|
|
1301
|
+
const sender = options.sender ?? null;
|
|
1302
|
+
const since = options.since;
|
|
1303
|
+
const amountFilterRef = useRef2(options.amountFilter);
|
|
1304
|
+
amountFilterRef.current = options.amountFilter;
|
|
1305
|
+
const excludeIdsKey = useMemo5(() => {
|
|
1306
|
+
if (!options.excludeIds) return "";
|
|
1307
|
+
if (options.excludeIds instanceof Set)
|
|
1308
|
+
return Array.from(options.excludeIds).sort().join("\0");
|
|
1309
|
+
return [...options.excludeIds].sort().join("\0");
|
|
1310
|
+
}, [options.excludeIds]);
|
|
1311
|
+
const excludeIdSet = useMemo5(() => {
|
|
1312
|
+
if (!excludeIdsKey) return null;
|
|
1313
|
+
return new Set(excludeIdsKey.split("\0"));
|
|
1314
|
+
}, [excludeIdsKey]);
|
|
1315
|
+
const refetch = useCallback5(async () => {
|
|
1316
|
+
if (!client || !isReady) return;
|
|
1317
|
+
setIsLoading(true);
|
|
1318
|
+
setError(null);
|
|
1319
|
+
try {
|
|
1320
|
+
const filterType = getNoteFilterType(status);
|
|
1321
|
+
const filter = new NoteFilter2(filterType);
|
|
1322
|
+
const fetched = await runExclusiveSafe(
|
|
1323
|
+
() => client.getInputNotes(filter)
|
|
1324
|
+
);
|
|
1325
|
+
setNotesIfChanged(fetched);
|
|
1326
|
+
} catch (err) {
|
|
1327
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1328
|
+
} finally {
|
|
1329
|
+
setIsLoading(false);
|
|
1330
|
+
}
|
|
1331
|
+
}, [client, isReady, runExclusiveSafe, status, setNotesIfChanged]);
|
|
1332
|
+
useEffect6(() => {
|
|
1333
|
+
if (isReady) {
|
|
1334
|
+
refetch();
|
|
1335
|
+
}
|
|
1336
|
+
}, [isReady, lastSyncTime, refetch]);
|
|
1337
|
+
const streamedNotes = useMemo5(() => {
|
|
1338
|
+
void handledVersion;
|
|
1339
|
+
const result = [];
|
|
1340
|
+
let normalizedSender = null;
|
|
1341
|
+
if (sender) {
|
|
1342
|
+
try {
|
|
1343
|
+
normalizedSender = normalizeAccountId(sender);
|
|
1344
|
+
} catch {
|
|
1345
|
+
normalizedSender = sender;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
for (const record of allNotes) {
|
|
1349
|
+
const note = buildStreamedNote(record, noteFirstSeen);
|
|
1350
|
+
if (!note) continue;
|
|
1351
|
+
if (handledIdsRef.current.has(note.id)) continue;
|
|
1352
|
+
if (excludeIdSet && excludeIdSet.has(note.id)) continue;
|
|
1353
|
+
if (normalizedSender && note.sender !== normalizedSender) continue;
|
|
1354
|
+
if (since !== void 0 && note.firstSeenAt < since) continue;
|
|
1355
|
+
if (amountFilterRef.current && !amountFilterRef.current(note.amount))
|
|
1356
|
+
continue;
|
|
1357
|
+
result.push(note);
|
|
1358
|
+
}
|
|
1359
|
+
result.sort((a, b) => a.firstSeenAt - b.firstSeenAt);
|
|
1360
|
+
return result;
|
|
1361
|
+
}, [allNotes, noteFirstSeen, excludeIdSet, sender, since, handledVersion]);
|
|
1362
|
+
const latest = useMemo5(
|
|
1363
|
+
() => streamedNotes.length > 0 ? streamedNotes[streamedNotes.length - 1] : null,
|
|
1364
|
+
[streamedNotes]
|
|
1365
|
+
);
|
|
1366
|
+
const markHandled = useCallback5((noteId) => {
|
|
1367
|
+
handledIdsRef.current = new Set(handledIdsRef.current).add(noteId);
|
|
1368
|
+
setHandledVersion((v) => v + 1);
|
|
1369
|
+
}, []);
|
|
1370
|
+
const markAllHandled = useCallback5(() => {
|
|
1371
|
+
const newSet = new Set(handledIdsRef.current);
|
|
1372
|
+
for (const note of streamedNotes) {
|
|
1373
|
+
newSet.add(note.id);
|
|
1374
|
+
}
|
|
1375
|
+
handledIdsRef.current = newSet;
|
|
1376
|
+
setHandledVersion((v) => v + 1);
|
|
1377
|
+
}, [streamedNotes]);
|
|
1378
|
+
const snapshot = useCallback5(() => {
|
|
1379
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1380
|
+
for (const note of streamedNotes) {
|
|
1381
|
+
ids.add(note.id);
|
|
1382
|
+
}
|
|
1383
|
+
return { ids, timestamp: Date.now() };
|
|
1384
|
+
}, [streamedNotes]);
|
|
1385
|
+
return {
|
|
1386
|
+
notes: streamedNotes,
|
|
1387
|
+
latest,
|
|
1388
|
+
markHandled,
|
|
1389
|
+
markAllHandled,
|
|
1390
|
+
snapshot,
|
|
1391
|
+
isLoading,
|
|
1392
|
+
error
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
function buildStreamedNote(record, noteFirstSeen) {
|
|
1396
|
+
try {
|
|
1397
|
+
const id = record.id().toString();
|
|
1398
|
+
const metadata = record.metadata?.();
|
|
1399
|
+
const senderHex = metadata?.sender?.()?.toString?.();
|
|
1400
|
+
const sender = senderHex ? toBech32AccountId(senderHex) : "";
|
|
1401
|
+
const assets = [];
|
|
1402
|
+
let primaryAmount = 0n;
|
|
1403
|
+
try {
|
|
1404
|
+
const details = record.details();
|
|
1405
|
+
const assetsList = details?.assets?.().fungibleAssets?.() ?? [];
|
|
1406
|
+
for (const asset of assetsList) {
|
|
1407
|
+
const assetId = asset.faucetId().toString();
|
|
1408
|
+
const amount = BigInt(asset.amount());
|
|
1409
|
+
assets.push({ assetId, amount });
|
|
1410
|
+
if (primaryAmount === 0n) {
|
|
1411
|
+
primaryAmount = amount;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
} catch {
|
|
1415
|
+
}
|
|
1416
|
+
const attachmentData = readNoteAttachment(record);
|
|
1417
|
+
const attachment = attachmentData ? attachmentData.values : null;
|
|
1418
|
+
const firstSeenAt = noteFirstSeen.get(id) ?? Date.now();
|
|
1419
|
+
return {
|
|
1420
|
+
id,
|
|
1421
|
+
sender,
|
|
1422
|
+
amount: primaryAmount,
|
|
1423
|
+
assets,
|
|
1424
|
+
record,
|
|
1425
|
+
firstSeenAt,
|
|
1426
|
+
attachment
|
|
1427
|
+
};
|
|
1428
|
+
} catch {
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// src/hooks/useTransactionHistory.ts
|
|
1434
|
+
import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo6, useState as useState5 } from "react";
|
|
1435
|
+
import { TransactionFilter as TransactionFilter2 } from "@miden-sdk/miden-sdk";
|
|
1436
|
+
function useTransactionHistory(options = {}) {
|
|
1437
|
+
const { client, isReady, runExclusive } = useMiden();
|
|
1438
|
+
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1439
|
+
const { lastSyncTime } = useSyncStateStore();
|
|
1440
|
+
const [records, setRecords] = useState5([]);
|
|
1441
|
+
const [isLoading, setIsLoading] = useState5(false);
|
|
1442
|
+
const [error, setError] = useState5(null);
|
|
1443
|
+
const rawIds = useMemo6(() => {
|
|
1050
1444
|
if (options.id) return [options.id];
|
|
1051
1445
|
if (options.ids && options.ids.length > 0) return options.ids;
|
|
1052
1446
|
return null;
|
|
1053
1447
|
}, [options.id, options.ids]);
|
|
1054
|
-
const idsHex =
|
|
1448
|
+
const idsHex = useMemo6(() => {
|
|
1055
1449
|
if (!rawIds) return null;
|
|
1056
1450
|
return rawIds.map(
|
|
1057
|
-
(id) =>
|
|
1451
|
+
(id) => normalizeHex2(typeof id === "string" ? id : id.toHex())
|
|
1058
1452
|
);
|
|
1059
1453
|
}, [rawIds]);
|
|
1060
1454
|
const filter = options.filter;
|
|
1061
1455
|
const refreshOnSync = options.refreshOnSync !== false;
|
|
1062
|
-
const refetch =
|
|
1456
|
+
const refetch = useCallback6(async () => {
|
|
1063
1457
|
if (!client || !isReady) return;
|
|
1064
1458
|
setIsLoading(true);
|
|
1065
1459
|
setError(null);
|
|
@@ -1073,7 +1467,7 @@ function useTransactionHistory(options = {}) {
|
|
|
1073
1467
|
() => client.getTransactions(resolvedFilter)
|
|
1074
1468
|
);
|
|
1075
1469
|
const filtered = localFilterHexes ? fetched.filter(
|
|
1076
|
-
(record2) => localFilterHexes.includes(
|
|
1470
|
+
(record2) => localFilterHexes.includes(normalizeHex2(record2.id().toHex()))
|
|
1077
1471
|
) : fetched;
|
|
1078
1472
|
setRecords(filtered);
|
|
1079
1473
|
} catch (err) {
|
|
@@ -1082,19 +1476,19 @@ function useTransactionHistory(options = {}) {
|
|
|
1082
1476
|
setIsLoading(false);
|
|
1083
1477
|
}
|
|
1084
1478
|
}, [client, isReady, runExclusiveSafe, filter, rawIds, idsHex]);
|
|
1085
|
-
|
|
1479
|
+
useEffect7(() => {
|
|
1086
1480
|
if (!isReady) return;
|
|
1087
1481
|
refetch();
|
|
1088
1482
|
}, [isReady, refetch]);
|
|
1089
|
-
|
|
1483
|
+
useEffect7(() => {
|
|
1090
1484
|
if (!isReady || !refreshOnSync || !lastSyncTime) return;
|
|
1091
1485
|
refetch();
|
|
1092
1486
|
}, [isReady, lastSyncTime, refreshOnSync, refetch]);
|
|
1093
|
-
const record =
|
|
1487
|
+
const record = useMemo6(() => {
|
|
1094
1488
|
if (!idsHex || idsHex.length !== 1) return null;
|
|
1095
|
-
return records.find((item) =>
|
|
1489
|
+
return records.find((item) => normalizeHex2(item.id().toHex()) === idsHex[0]) ?? null;
|
|
1096
1490
|
}, [records, idsHex]);
|
|
1097
|
-
const status =
|
|
1491
|
+
const status = useMemo6(() => {
|
|
1098
1492
|
if (!record) return null;
|
|
1099
1493
|
const current = record.transactionStatus();
|
|
1100
1494
|
if (current.isCommitted()) return "committed";
|
|
@@ -1116,29 +1510,29 @@ function buildFilter(filter, ids, idsHex) {
|
|
|
1116
1510
|
return { filter };
|
|
1117
1511
|
}
|
|
1118
1512
|
if (!ids || ids.length === 0) {
|
|
1119
|
-
return { filter:
|
|
1513
|
+
return { filter: TransactionFilter2.all() };
|
|
1120
1514
|
}
|
|
1121
1515
|
const allTransactionIds = ids.every((id) => typeof id !== "string");
|
|
1122
1516
|
if (allTransactionIds) {
|
|
1123
|
-
return { filter:
|
|
1517
|
+
return { filter: TransactionFilter2.ids(ids) };
|
|
1124
1518
|
}
|
|
1125
1519
|
return {
|
|
1126
|
-
filter:
|
|
1520
|
+
filter: TransactionFilter2.all(),
|
|
1127
1521
|
localFilterHexes: idsHex ?? []
|
|
1128
1522
|
};
|
|
1129
1523
|
}
|
|
1130
|
-
function
|
|
1524
|
+
function normalizeHex2(value) {
|
|
1131
1525
|
const trimmed = value.trim();
|
|
1132
1526
|
const normalized = trimmed.startsWith("0x") || trimmed.startsWith("0X") ? trimmed : `0x${trimmed}`;
|
|
1133
1527
|
return normalized.toLowerCase();
|
|
1134
1528
|
}
|
|
1135
1529
|
|
|
1136
1530
|
// src/hooks/useSyncState.ts
|
|
1137
|
-
import { useCallback as
|
|
1531
|
+
import { useCallback as useCallback7 } from "react";
|
|
1138
1532
|
function useSyncState() {
|
|
1139
1533
|
const { sync: triggerSync } = useMiden();
|
|
1140
1534
|
const syncState = useSyncStateStore();
|
|
1141
|
-
const sync =
|
|
1535
|
+
const sync = useCallback7(async () => {
|
|
1142
1536
|
await triggerSync();
|
|
1143
1537
|
}, [triggerSync]);
|
|
1144
1538
|
return {
|
|
@@ -1148,21 +1542,20 @@ function useSyncState() {
|
|
|
1148
1542
|
}
|
|
1149
1543
|
|
|
1150
1544
|
// src/hooks/useCreateWallet.ts
|
|
1151
|
-
import { useCallback as
|
|
1545
|
+
import { useCallback as useCallback8, useState as useState6 } from "react";
|
|
1152
1546
|
import { AccountStorageMode } from "@miden-sdk/miden-sdk";
|
|
1153
1547
|
function useCreateWallet() {
|
|
1154
|
-
const { client, isReady,
|
|
1548
|
+
const { client, isReady, runExclusive } = useMiden();
|
|
1155
1549
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1156
1550
|
const setAccounts = useMidenStore((state) => state.setAccounts);
|
|
1157
|
-
const [wallet, setWallet] =
|
|
1158
|
-
const [isCreating, setIsCreating] =
|
|
1159
|
-
const [error, setError] =
|
|
1160
|
-
const createWallet =
|
|
1551
|
+
const [wallet, setWallet] = useState6(null);
|
|
1552
|
+
const [isCreating, setIsCreating] = useState6(false);
|
|
1553
|
+
const [error, setError] = useState6(null);
|
|
1554
|
+
const createWallet = useCallback8(
|
|
1161
1555
|
async (options = {}) => {
|
|
1162
1556
|
if (!client || !isReady) {
|
|
1163
1557
|
throw new Error("Miden client is not ready");
|
|
1164
1558
|
}
|
|
1165
|
-
await sync();
|
|
1166
1559
|
setIsCreating(true);
|
|
1167
1560
|
setError(null);
|
|
1168
1561
|
try {
|
|
@@ -1195,7 +1588,7 @@ function useCreateWallet() {
|
|
|
1195
1588
|
},
|
|
1196
1589
|
[client, isReady, runExclusive, setAccounts]
|
|
1197
1590
|
);
|
|
1198
|
-
const reset =
|
|
1591
|
+
const reset = useCallback8(() => {
|
|
1199
1592
|
setWallet(null);
|
|
1200
1593
|
setIsCreating(false);
|
|
1201
1594
|
setError(null);
|
|
@@ -1222,16 +1615,16 @@ function getStorageMode(mode) {
|
|
|
1222
1615
|
}
|
|
1223
1616
|
|
|
1224
1617
|
// src/hooks/useCreateFaucet.ts
|
|
1225
|
-
import { useCallback as
|
|
1618
|
+
import { useCallback as useCallback9, useState as useState7 } from "react";
|
|
1226
1619
|
import { AccountStorageMode as AccountStorageMode2 } from "@miden-sdk/miden-sdk";
|
|
1227
1620
|
function useCreateFaucet() {
|
|
1228
1621
|
const { client, isReady, runExclusive } = useMiden();
|
|
1229
1622
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1230
1623
|
const setAccounts = useMidenStore((state) => state.setAccounts);
|
|
1231
|
-
const [faucet, setFaucet] =
|
|
1232
|
-
const [isCreating, setIsCreating] =
|
|
1233
|
-
const [error, setError] =
|
|
1234
|
-
const createFaucet =
|
|
1624
|
+
const [faucet, setFaucet] = useState7(null);
|
|
1625
|
+
const [isCreating, setIsCreating] = useState7(false);
|
|
1626
|
+
const [error, setError] = useState7(null);
|
|
1627
|
+
const createFaucet = useCallback9(
|
|
1235
1628
|
async (options) => {
|
|
1236
1629
|
if (!client || !isReady) {
|
|
1237
1630
|
throw new Error("Miden client is not ready");
|
|
@@ -1270,7 +1663,7 @@ function useCreateFaucet() {
|
|
|
1270
1663
|
},
|
|
1271
1664
|
[client, isReady, runExclusive, setAccounts]
|
|
1272
1665
|
);
|
|
1273
|
-
const reset =
|
|
1666
|
+
const reset = useCallback9(() => {
|
|
1274
1667
|
setFaucet(null);
|
|
1275
1668
|
setIsCreating(false);
|
|
1276
1669
|
setError(null);
|
|
@@ -1297,16 +1690,16 @@ function getStorageMode2(mode) {
|
|
|
1297
1690
|
}
|
|
1298
1691
|
|
|
1299
1692
|
// src/hooks/useImportAccount.ts
|
|
1300
|
-
import { useCallback as
|
|
1693
|
+
import { useCallback as useCallback10, useState as useState8 } from "react";
|
|
1301
1694
|
import { AccountFile } from "@miden-sdk/miden-sdk";
|
|
1302
1695
|
function useImportAccount() {
|
|
1303
1696
|
const { client, isReady, runExclusive } = useMiden();
|
|
1304
1697
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1305
1698
|
const setAccounts = useMidenStore((state) => state.setAccounts);
|
|
1306
|
-
const [account, setAccount] =
|
|
1307
|
-
const [isImporting, setIsImporting] =
|
|
1308
|
-
const [error, setError] =
|
|
1309
|
-
const importAccount =
|
|
1699
|
+
const [account, setAccount] = useState8(null);
|
|
1700
|
+
const [isImporting, setIsImporting] = useState8(false);
|
|
1701
|
+
const [error, setError] = useState8(null);
|
|
1702
|
+
const importAccount = useCallback10(
|
|
1310
1703
|
async (options) => {
|
|
1311
1704
|
if (!client || !isReady) {
|
|
1312
1705
|
throw new Error("Miden client is not ready");
|
|
@@ -1401,7 +1794,7 @@ function useImportAccount() {
|
|
|
1401
1794
|
},
|
|
1402
1795
|
[client, isReady, runExclusive, setAccounts]
|
|
1403
1796
|
);
|
|
1404
|
-
const reset =
|
|
1797
|
+
const reset = useCallback10(() => {
|
|
1405
1798
|
setAccount(null);
|
|
1406
1799
|
setIsImporting(false);
|
|
1407
1800
|
setError(null);
|
|
@@ -1451,43 +1844,154 @@ function bytesEqual(left, right) {
|
|
|
1451
1844
|
}
|
|
1452
1845
|
|
|
1453
1846
|
// src/hooks/useSend.ts
|
|
1454
|
-
import { useCallback as
|
|
1455
|
-
import {
|
|
1847
|
+
import { useCallback as useCallback11, useRef as useRef3, useState as useState9 } from "react";
|
|
1848
|
+
import {
|
|
1849
|
+
FungibleAsset,
|
|
1850
|
+
Note,
|
|
1851
|
+
NoteAssets,
|
|
1852
|
+
NoteType as NoteType2,
|
|
1853
|
+
OutputNote,
|
|
1854
|
+
OutputNoteArray,
|
|
1855
|
+
TransactionRequestBuilder
|
|
1856
|
+
} from "@miden-sdk/miden-sdk";
|
|
1857
|
+
|
|
1858
|
+
// src/utils/errors.ts
|
|
1859
|
+
var MidenError = class extends Error {
|
|
1860
|
+
constructor(message, options) {
|
|
1861
|
+
super(message);
|
|
1862
|
+
this.name = "MidenError";
|
|
1863
|
+
this.code = options?.code ?? "UNKNOWN";
|
|
1864
|
+
if (options?.cause !== void 0) {
|
|
1865
|
+
this.cause = options.cause;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
};
|
|
1869
|
+
var ERROR_PATTERNS = [
|
|
1870
|
+
{
|
|
1871
|
+
test: (msg) => msg.includes("_assertClass") || msg.includes("expected instance of"),
|
|
1872
|
+
code: "WASM_CLASS_MISMATCH",
|
|
1873
|
+
message: "WASM class identity mismatch. This usually means multiple copies of @miden-sdk/miden-sdk are bundled. Ensure your bundler deduplicates the package. For Vite: add resolve.dedupe and optimizeDeps.exclude for @miden-sdk/miden-sdk."
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
test: (msg) => msg.includes("null pointer") || msg.includes("already been freed") || msg.includes("dereferencing a null"),
|
|
1877
|
+
code: "WASM_POINTER_CONSUMED",
|
|
1878
|
+
message: "WASM object was already consumed. Some WASM-bound objects can only be passed once \u2014 if you need to reuse a value, create a fresh instance before each call."
|
|
1879
|
+
},
|
|
1880
|
+
{
|
|
1881
|
+
test: (msg) => msg.includes("not initialized") || msg.includes("Cannot read properties of null"),
|
|
1882
|
+
code: "WASM_NOT_INITIALIZED",
|
|
1883
|
+
message: "Miden client is not initialized. Ensure you are inside a <MidenProvider> and the client is ready before calling SDK methods."
|
|
1884
|
+
},
|
|
1885
|
+
{
|
|
1886
|
+
test: (msg) => msg.includes("state commitment mismatch") || msg.includes("stale state"),
|
|
1887
|
+
code: "WASM_SYNC_REQUIRED",
|
|
1888
|
+
message: "Account state is stale. Call sync() before executing transactions, or ensure no concurrent transactions are running against the same account."
|
|
1889
|
+
}
|
|
1890
|
+
];
|
|
1891
|
+
function wrapWasmError(e) {
|
|
1892
|
+
if (e instanceof MidenError) return e;
|
|
1893
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1894
|
+
for (const pattern of ERROR_PATTERNS) {
|
|
1895
|
+
if (pattern.test(msg)) {
|
|
1896
|
+
return new MidenError(pattern.message, { cause: e, code: pattern.code });
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
if (e instanceof Error) return e;
|
|
1900
|
+
return new Error(msg);
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// src/hooks/useSend.ts
|
|
1456
1904
|
function useSend() {
|
|
1457
1905
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
1458
1906
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1459
|
-
const
|
|
1460
|
-
const [
|
|
1461
|
-
const [
|
|
1462
|
-
const [
|
|
1463
|
-
const
|
|
1907
|
+
const isBusyRef = useRef3(false);
|
|
1908
|
+
const [result, setResult] = useState9(null);
|
|
1909
|
+
const [isLoading, setIsLoading] = useState9(false);
|
|
1910
|
+
const [stage, setStage] = useState9("idle");
|
|
1911
|
+
const [error, setError] = useState9(null);
|
|
1912
|
+
const send = useCallback11(
|
|
1464
1913
|
async (options) => {
|
|
1465
1914
|
if (!client || !isReady) {
|
|
1466
1915
|
throw new Error("Miden client is not ready");
|
|
1467
1916
|
}
|
|
1917
|
+
if (isBusyRef.current) {
|
|
1918
|
+
throw new MidenError(
|
|
1919
|
+
"A send is already in progress. Await the previous send before starting another.",
|
|
1920
|
+
{ code: "SEND_BUSY" }
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
isBusyRef.current = true;
|
|
1468
1924
|
setIsLoading(true);
|
|
1469
1925
|
setStage("executing");
|
|
1470
1926
|
setError(null);
|
|
1471
1927
|
try {
|
|
1928
|
+
if (!options.skipSync) {
|
|
1929
|
+
await sync();
|
|
1930
|
+
}
|
|
1472
1931
|
const noteType = getNoteType(options.noteType ?? DEFAULTS.NOTE_TYPE);
|
|
1473
|
-
|
|
1474
|
-
|
|
1932
|
+
let amount = options.amount;
|
|
1933
|
+
if (options.sendAll) {
|
|
1934
|
+
const resolvedAmount = await runExclusiveSafe(async () => {
|
|
1935
|
+
const fromId = parseAccountId(options.from);
|
|
1936
|
+
const account = await client.getAccount(fromId);
|
|
1937
|
+
if (!account) throw new Error("Account not found");
|
|
1938
|
+
const assetIdObj = parseAccountId(options.assetId);
|
|
1939
|
+
const balance = account.vault?.()?.getBalance?.(assetIdObj);
|
|
1940
|
+
if (balance === void 0 || balance === null) {
|
|
1941
|
+
throw new Error("Could not query account balance");
|
|
1942
|
+
}
|
|
1943
|
+
const bal = BigInt(balance);
|
|
1944
|
+
if (bal === 0n) {
|
|
1945
|
+
throw new Error("Account has zero balance for this asset");
|
|
1946
|
+
}
|
|
1947
|
+
return bal;
|
|
1948
|
+
});
|
|
1949
|
+
amount = resolvedAmount;
|
|
1950
|
+
}
|
|
1951
|
+
if (amount === void 0 || amount === null) {
|
|
1952
|
+
throw new Error("Amount is required (provide amount or sendAll)");
|
|
1953
|
+
}
|
|
1475
1954
|
const assetId = options.assetId ?? options.faucetId ?? null;
|
|
1476
1955
|
if (!assetId) {
|
|
1477
1956
|
throw new Error("Asset ID is required");
|
|
1478
1957
|
}
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
toAccountId,
|
|
1484
|
-
assetIdObj,
|
|
1485
|
-
noteType,
|
|
1486
|
-
options.amount,
|
|
1487
|
-
options.recallHeight ?? null,
|
|
1488
|
-
options.timelockHeight ?? null
|
|
1958
|
+
const hasAttachment = options.attachment !== void 0 && options.attachment !== null;
|
|
1959
|
+
if (hasAttachment && (options.recallHeight != null || options.timelockHeight != null)) {
|
|
1960
|
+
throw new Error(
|
|
1961
|
+
"recallHeight and timelockHeight are not supported when attachment is provided"
|
|
1489
1962
|
);
|
|
1490
|
-
|
|
1963
|
+
}
|
|
1964
|
+
const txResult = await runExclusiveSafe(async () => {
|
|
1965
|
+
const fromAccountId = parseAccountId(options.from);
|
|
1966
|
+
const toAccountId = parseAccountId(options.to);
|
|
1967
|
+
const assetIdObj = parseAccountId(assetId);
|
|
1968
|
+
let txRequest;
|
|
1969
|
+
if (hasAttachment) {
|
|
1970
|
+
const attachment = createNoteAttachment(options.attachment);
|
|
1971
|
+
const assets = new NoteAssets([
|
|
1972
|
+
new FungibleAsset(assetIdObj, amount)
|
|
1973
|
+
]);
|
|
1974
|
+
const note = Note.createP2IDNote(
|
|
1975
|
+
fromAccountId,
|
|
1976
|
+
toAccountId,
|
|
1977
|
+
assets,
|
|
1978
|
+
noteType,
|
|
1979
|
+
attachment
|
|
1980
|
+
);
|
|
1981
|
+
txRequest = new TransactionRequestBuilder().withOwnOutputNotes(new OutputNoteArray([OutputNote.full(note)])).build();
|
|
1982
|
+
} else {
|
|
1983
|
+
txRequest = client.newSendTransactionRequest(
|
|
1984
|
+
fromAccountId,
|
|
1985
|
+
toAccountId,
|
|
1986
|
+
assetIdObj,
|
|
1987
|
+
noteType,
|
|
1988
|
+
amount,
|
|
1989
|
+
options.recallHeight ?? null,
|
|
1990
|
+
options.timelockHeight ?? null
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1993
|
+
const execAccountId = parseAccountId(options.from);
|
|
1994
|
+
return await client.executeTransaction(execAccountId, txRequest);
|
|
1491
1995
|
});
|
|
1492
1996
|
setStage("proving");
|
|
1493
1997
|
const provenTransaction = await runExclusiveSafe(
|
|
@@ -1497,22 +2001,31 @@ function useSend() {
|
|
|
1497
2001
|
const submissionHeight = await runExclusiveSafe(
|
|
1498
2002
|
() => client.submitProvenTransaction(provenTransaction, txResult)
|
|
1499
2003
|
);
|
|
2004
|
+
const txIdHex = txResult.id().toHex();
|
|
2005
|
+
const txIdString = txResult.id().toString();
|
|
2006
|
+
let fullNote = null;
|
|
2007
|
+
if (noteType === NoteType2.Private) {
|
|
2008
|
+
fullNote = extractFullNote(txResult);
|
|
2009
|
+
}
|
|
1500
2010
|
await runExclusiveSafe(
|
|
1501
2011
|
() => client.applyTransaction(txResult, submissionHeight)
|
|
1502
2012
|
);
|
|
1503
|
-
|
|
1504
|
-
await waitForTransactionCommit(client, runExclusiveSafe, txId);
|
|
1505
|
-
if (noteType === NoteType.Private) {
|
|
1506
|
-
const fullNote = extractFullNote(txResult);
|
|
2013
|
+
if (noteType === NoteType2.Private) {
|
|
1507
2014
|
if (!fullNote) {
|
|
1508
2015
|
throw new Error("Missing full note for private send");
|
|
1509
2016
|
}
|
|
1510
|
-
|
|
2017
|
+
await waitForTransactionCommit(
|
|
2018
|
+
client,
|
|
2019
|
+
runExclusiveSafe,
|
|
2020
|
+
txIdHex
|
|
2021
|
+
);
|
|
2022
|
+
const recipientAccountId = parseAccountId(options.to);
|
|
2023
|
+
const recipientAddress = parseAddress(options.to, recipientAccountId);
|
|
1511
2024
|
await runExclusiveSafe(
|
|
1512
2025
|
() => client.sendPrivateNote(fullNote, recipientAddress)
|
|
1513
2026
|
);
|
|
1514
2027
|
}
|
|
1515
|
-
const txSummary = { transactionId:
|
|
2028
|
+
const txSummary = { transactionId: txIdString };
|
|
1516
2029
|
setStage("complete");
|
|
1517
2030
|
setResult(txSummary);
|
|
1518
2031
|
await sync();
|
|
@@ -1524,11 +2037,12 @@ function useSend() {
|
|
|
1524
2037
|
throw error2;
|
|
1525
2038
|
} finally {
|
|
1526
2039
|
setIsLoading(false);
|
|
2040
|
+
isBusyRef.current = false;
|
|
1527
2041
|
}
|
|
1528
2042
|
},
|
|
1529
2043
|
[client, isReady, prover, runExclusive, sync]
|
|
1530
2044
|
);
|
|
1531
|
-
const reset =
|
|
2045
|
+
const reset = useCallback11(() => {
|
|
1532
2046
|
setResult(null);
|
|
1533
2047
|
setIsLoading(false);
|
|
1534
2048
|
setStage("idle");
|
|
@@ -1543,39 +2057,6 @@ function useSend() {
|
|
|
1543
2057
|
reset
|
|
1544
2058
|
};
|
|
1545
2059
|
}
|
|
1546
|
-
function getNoteType(type) {
|
|
1547
|
-
switch (type) {
|
|
1548
|
-
case "private":
|
|
1549
|
-
return NoteType.Private;
|
|
1550
|
-
case "public":
|
|
1551
|
-
return NoteType.Public;
|
|
1552
|
-
case "encrypted":
|
|
1553
|
-
return NoteType.Encrypted;
|
|
1554
|
-
default:
|
|
1555
|
-
return NoteType.Private;
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
async function waitForTransactionCommit(client, runExclusiveSafe, txId, maxWaitMs = 1e4, delayMs = 1e3) {
|
|
1559
|
-
let waited = 0;
|
|
1560
|
-
while (waited < maxWaitMs) {
|
|
1561
|
-
await runExclusiveSafe(() => client.syncState());
|
|
1562
|
-
const [record] = await runExclusiveSafe(
|
|
1563
|
-
() => client.getTransactions(TransactionFilter2.ids([txId]))
|
|
1564
|
-
);
|
|
1565
|
-
if (record) {
|
|
1566
|
-
const status = record.transactionStatus();
|
|
1567
|
-
if (status.isCommitted()) {
|
|
1568
|
-
return;
|
|
1569
|
-
}
|
|
1570
|
-
if (status.isDiscarded()) {
|
|
1571
|
-
throw new Error("Transaction was discarded before commit");
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1575
|
-
waited += delayMs;
|
|
1576
|
-
}
|
|
1577
|
-
throw new Error("Timeout waiting for transaction commit");
|
|
1578
|
-
}
|
|
1579
2060
|
function extractFullNote(txResult) {
|
|
1580
2061
|
try {
|
|
1581
2062
|
const executedTx = txResult.executedTransaction?.();
|
|
@@ -1588,26 +2069,26 @@ function extractFullNote(txResult) {
|
|
|
1588
2069
|
}
|
|
1589
2070
|
|
|
1590
2071
|
// src/hooks/useMultiSend.ts
|
|
1591
|
-
import { useCallback as
|
|
2072
|
+
import { useCallback as useCallback12, useRef as useRef4, useState as useState10 } from "react";
|
|
1592
2073
|
import {
|
|
1593
|
-
FungibleAsset,
|
|
1594
|
-
Note,
|
|
1595
|
-
NoteAssets,
|
|
1596
|
-
NoteAttachment,
|
|
1597
|
-
NoteType as
|
|
1598
|
-
OutputNote,
|
|
1599
|
-
OutputNoteArray,
|
|
1600
|
-
|
|
1601
|
-
TransactionRequestBuilder
|
|
2074
|
+
FungibleAsset as FungibleAsset2,
|
|
2075
|
+
Note as Note2,
|
|
2076
|
+
NoteAssets as NoteAssets2,
|
|
2077
|
+
NoteAttachment as NoteAttachment2,
|
|
2078
|
+
NoteType as NoteType3,
|
|
2079
|
+
OutputNote as OutputNote2,
|
|
2080
|
+
OutputNoteArray as OutputNoteArray2,
|
|
2081
|
+
TransactionRequestBuilder as TransactionRequestBuilder2
|
|
1602
2082
|
} from "@miden-sdk/miden-sdk";
|
|
1603
2083
|
function useMultiSend() {
|
|
1604
2084
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
1605
2085
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1606
|
-
const
|
|
1607
|
-
const [
|
|
1608
|
-
const [
|
|
1609
|
-
const [
|
|
1610
|
-
const
|
|
2086
|
+
const isBusyRef = useRef4(false);
|
|
2087
|
+
const [result, setResult] = useState10(null);
|
|
2088
|
+
const [isLoading, setIsLoading] = useState10(false);
|
|
2089
|
+
const [stage, setStage] = useState10("idle");
|
|
2090
|
+
const [error, setError] = useState10(null);
|
|
2091
|
+
const sendMany = useCallback12(
|
|
1611
2092
|
async (options) => {
|
|
1612
2093
|
if (!client || !isReady) {
|
|
1613
2094
|
throw new Error("Miden client is not ready");
|
|
@@ -1615,35 +2096,53 @@ function useMultiSend() {
|
|
|
1615
2096
|
if (options.recipients.length === 0) {
|
|
1616
2097
|
throw new Error("No recipients provided");
|
|
1617
2098
|
}
|
|
2099
|
+
if (isBusyRef.current) {
|
|
2100
|
+
throw new MidenError(
|
|
2101
|
+
"A send is already in progress. Await the previous send before starting another.",
|
|
2102
|
+
{ code: "SEND_BUSY" }
|
|
2103
|
+
);
|
|
2104
|
+
}
|
|
2105
|
+
isBusyRef.current = true;
|
|
1618
2106
|
setIsLoading(true);
|
|
1619
2107
|
setStage("executing");
|
|
1620
2108
|
setError(null);
|
|
1621
2109
|
try {
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
receiverId
|
|
1631
|
-
assets
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
2110
|
+
if (!options.skipSync) {
|
|
2111
|
+
await sync();
|
|
2112
|
+
}
|
|
2113
|
+
const noteType = getNoteType(options.noteType ?? DEFAULTS.NOTE_TYPE);
|
|
2114
|
+
const outputs = options.recipients.map(
|
|
2115
|
+
({ to, amount, attachment, noteType: recipientNoteType }) => {
|
|
2116
|
+
const iterSenderId = parseAccountId(options.from);
|
|
2117
|
+
const iterAssetId = parseAccountId(options.assetId);
|
|
2118
|
+
const receiverId = parseAccountId(to);
|
|
2119
|
+
const assets = new NoteAssets2([
|
|
2120
|
+
new FungibleAsset2(iterAssetId, amount)
|
|
2121
|
+
]);
|
|
2122
|
+
const resolvedNoteType = recipientNoteType ? getNoteType(recipientNoteType) : noteType;
|
|
2123
|
+
const noteAttachment = attachment !== void 0 && attachment !== null ? createNoteAttachment(attachment) : new NoteAttachment2();
|
|
2124
|
+
const note = Note2.createP2IDNote(
|
|
2125
|
+
iterSenderId,
|
|
2126
|
+
receiverId,
|
|
2127
|
+
assets,
|
|
2128
|
+
resolvedNoteType,
|
|
2129
|
+
noteAttachment
|
|
2130
|
+
);
|
|
2131
|
+
const recipientAddress = parseAddress(to, receiverId);
|
|
2132
|
+
return {
|
|
2133
|
+
outputNote: OutputNote2.full(note),
|
|
2134
|
+
note,
|
|
2135
|
+
recipientAddress,
|
|
2136
|
+
noteType: resolvedNoteType
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
);
|
|
2140
|
+
const txRequest = new TransactionRequestBuilder2().withOwnOutputNotes(
|
|
2141
|
+
new OutputNoteArray2(outputs.map((o) => o.outputNote))
|
|
1644
2142
|
).build();
|
|
2143
|
+
const txSenderId = parseAccountId(options.from);
|
|
1645
2144
|
const txResult = await runExclusiveSafe(
|
|
1646
|
-
() => client.executeTransaction(
|
|
2145
|
+
() => client.executeTransaction(txSenderId, txRequest)
|
|
1647
2146
|
);
|
|
1648
2147
|
setStage("proving");
|
|
1649
2148
|
const provenTransaction = await runExclusiveSafe(
|
|
@@ -1653,23 +2152,27 @@ function useMultiSend() {
|
|
|
1653
2152
|
const submissionHeight = await runExclusiveSafe(
|
|
1654
2153
|
() => client.submitProvenTransaction(provenTransaction, txResult)
|
|
1655
2154
|
);
|
|
2155
|
+
const txIdHex = txResult.id().toHex();
|
|
2156
|
+
const txIdString = txResult.id().toString();
|
|
1656
2157
|
await runExclusiveSafe(
|
|
1657
2158
|
() => client.applyTransaction(txResult, submissionHeight)
|
|
1658
2159
|
);
|
|
1659
|
-
const
|
|
1660
|
-
if (
|
|
1661
|
-
await
|
|
2160
|
+
const hasPrivate = outputs.some((o) => o.noteType === NoteType3.Private);
|
|
2161
|
+
if (hasPrivate) {
|
|
2162
|
+
await waitForTransactionCommit(
|
|
1662
2163
|
client,
|
|
1663
2164
|
runExclusiveSafe,
|
|
1664
|
-
|
|
2165
|
+
txIdHex
|
|
1665
2166
|
);
|
|
1666
2167
|
for (const output of outputs) {
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
2168
|
+
if (output.noteType === NoteType3.Private) {
|
|
2169
|
+
await runExclusiveSafe(
|
|
2170
|
+
() => client.sendPrivateNote(output.note, output.recipientAddress)
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
1670
2173
|
}
|
|
1671
2174
|
}
|
|
1672
|
-
const txSummary = { transactionId:
|
|
2175
|
+
const txSummary = { transactionId: txIdString };
|
|
1673
2176
|
setStage("complete");
|
|
1674
2177
|
setResult(txSummary);
|
|
1675
2178
|
await sync();
|
|
@@ -1681,11 +2184,12 @@ function useMultiSend() {
|
|
|
1681
2184
|
throw error2;
|
|
1682
2185
|
} finally {
|
|
1683
2186
|
setIsLoading(false);
|
|
2187
|
+
isBusyRef.current = false;
|
|
1684
2188
|
}
|
|
1685
2189
|
},
|
|
1686
2190
|
[client, isReady, prover, runExclusive, sync]
|
|
1687
2191
|
);
|
|
1688
|
-
const reset =
|
|
2192
|
+
const reset = useCallback12(() => {
|
|
1689
2193
|
setResult(null);
|
|
1690
2194
|
setIsLoading(false);
|
|
1691
2195
|
setStage("idle");
|
|
@@ -1700,82 +2204,48 @@ function useMultiSend() {
|
|
|
1700
2204
|
reset
|
|
1701
2205
|
};
|
|
1702
2206
|
}
|
|
1703
|
-
function getNoteType2(type) {
|
|
1704
|
-
switch (type) {
|
|
1705
|
-
case "private":
|
|
1706
|
-
return NoteType2.Private;
|
|
1707
|
-
case "public":
|
|
1708
|
-
return NoteType2.Public;
|
|
1709
|
-
case "encrypted":
|
|
1710
|
-
return NoteType2.Encrypted;
|
|
1711
|
-
default:
|
|
1712
|
-
return NoteType2.Private;
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
async function waitForTransactionCommit2(client, runExclusiveSafe, txId, maxWaitMs = 1e4, delayMs = 1e3) {
|
|
1716
|
-
let waited = 0;
|
|
1717
|
-
while (waited < maxWaitMs) {
|
|
1718
|
-
await runExclusiveSafe(() => client.syncState());
|
|
1719
|
-
const [record] = await runExclusiveSafe(
|
|
1720
|
-
() => client.getTransactions(TransactionFilter3.ids([txId]))
|
|
1721
|
-
);
|
|
1722
|
-
if (record) {
|
|
1723
|
-
const status = record.transactionStatus();
|
|
1724
|
-
if (status.isCommitted()) {
|
|
1725
|
-
return;
|
|
1726
|
-
}
|
|
1727
|
-
if (status.isDiscarded()) {
|
|
1728
|
-
throw new Error("Transaction was discarded before commit");
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1732
|
-
waited += delayMs;
|
|
1733
|
-
}
|
|
1734
|
-
throw new Error("Timeout waiting for transaction commit");
|
|
1735
|
-
}
|
|
1736
2207
|
|
|
1737
2208
|
// src/hooks/useInternalTransfer.ts
|
|
1738
|
-
import { useCallback as
|
|
2209
|
+
import { useCallback as useCallback13, useState as useState11 } from "react";
|
|
1739
2210
|
import {
|
|
1740
|
-
FungibleAsset as
|
|
1741
|
-
Note as
|
|
2211
|
+
FungibleAsset as FungibleAsset3,
|
|
2212
|
+
Note as Note3,
|
|
1742
2213
|
NoteAndArgs,
|
|
1743
2214
|
NoteAndArgsArray,
|
|
1744
|
-
NoteAssets as
|
|
1745
|
-
NoteAttachment as
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
TransactionRequestBuilder as TransactionRequestBuilder2
|
|
2215
|
+
NoteAssets as NoteAssets3,
|
|
2216
|
+
NoteAttachment as NoteAttachment3,
|
|
2217
|
+
OutputNote as OutputNote3,
|
|
2218
|
+
OutputNoteArray as OutputNoteArray3,
|
|
2219
|
+
TransactionRequestBuilder as TransactionRequestBuilder3
|
|
1750
2220
|
} from "@miden-sdk/miden-sdk";
|
|
1751
2221
|
function useInternalTransfer() {
|
|
1752
2222
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
1753
2223
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1754
|
-
const [result, setResult] =
|
|
1755
|
-
const [isLoading, setIsLoading] =
|
|
1756
|
-
const [stage, setStage] =
|
|
1757
|
-
const [error, setError] =
|
|
1758
|
-
const transferOnce =
|
|
2224
|
+
const [result, setResult] = useState11(null);
|
|
2225
|
+
const [isLoading, setIsLoading] = useState11(false);
|
|
2226
|
+
const [stage, setStage] = useState11("idle");
|
|
2227
|
+
const [error, setError] = useState11(null);
|
|
2228
|
+
const transferOnce = useCallback13(
|
|
1759
2229
|
async (options) => {
|
|
1760
2230
|
if (!client || !isReady) {
|
|
1761
2231
|
throw new Error("Miden client is not ready");
|
|
1762
2232
|
}
|
|
1763
|
-
const noteType =
|
|
2233
|
+
const noteType = getNoteType(options.noteType ?? DEFAULTS.NOTE_TYPE);
|
|
1764
2234
|
const senderId = parseAccountId(options.from);
|
|
1765
2235
|
const receiverId = parseAccountId(options.to);
|
|
1766
2236
|
const assetId = parseAccountId(options.assetId);
|
|
1767
|
-
const assets = new
|
|
1768
|
-
new
|
|
2237
|
+
const assets = new NoteAssets3([
|
|
2238
|
+
new FungibleAsset3(assetId, options.amount)
|
|
1769
2239
|
]);
|
|
1770
|
-
const note =
|
|
2240
|
+
const note = Note3.createP2IDNote(
|
|
1771
2241
|
senderId,
|
|
1772
2242
|
receiverId,
|
|
1773
2243
|
assets,
|
|
1774
2244
|
noteType,
|
|
1775
|
-
new
|
|
2245
|
+
new NoteAttachment3()
|
|
1776
2246
|
);
|
|
1777
2247
|
const noteId = note.id().toString();
|
|
1778
|
-
const createRequest = new
|
|
2248
|
+
const createRequest = new TransactionRequestBuilder3().withOwnOutputNotes(new OutputNoteArray3([OutputNote3.full(note)])).build();
|
|
1779
2249
|
const createTxId = await runExclusiveSafe(
|
|
1780
2250
|
() => prover ? client.submitNewTransactionWithProver(
|
|
1781
2251
|
senderId,
|
|
@@ -1783,7 +2253,7 @@ function useInternalTransfer() {
|
|
|
1783
2253
|
prover
|
|
1784
2254
|
) : client.submitNewTransaction(senderId, createRequest)
|
|
1785
2255
|
);
|
|
1786
|
-
const consumeRequest = new
|
|
2256
|
+
const consumeRequest = new TransactionRequestBuilder3().withInputNotes(new NoteAndArgsArray([new NoteAndArgs(note, null)])).build();
|
|
1787
2257
|
const consumeTxId = await runExclusiveSafe(
|
|
1788
2258
|
() => prover ? client.submitNewTransactionWithProver(
|
|
1789
2259
|
receiverId,
|
|
@@ -1799,7 +2269,7 @@ function useInternalTransfer() {
|
|
|
1799
2269
|
},
|
|
1800
2270
|
[client, isReady, prover, runExclusiveSafe]
|
|
1801
2271
|
);
|
|
1802
|
-
const transfer =
|
|
2272
|
+
const transfer = useCallback13(
|
|
1803
2273
|
async (options) => {
|
|
1804
2274
|
if (!client || !isReady) {
|
|
1805
2275
|
throw new Error("Miden client is not ready");
|
|
@@ -1825,7 +2295,7 @@ function useInternalTransfer() {
|
|
|
1825
2295
|
},
|
|
1826
2296
|
[client, isReady, sync, transferOnce]
|
|
1827
2297
|
);
|
|
1828
|
-
const transferChain =
|
|
2298
|
+
const transferChain = useCallback13(
|
|
1829
2299
|
async (options) => {
|
|
1830
2300
|
if (!client || !isReady) {
|
|
1831
2301
|
throw new Error("Miden client is not ready");
|
|
@@ -1866,7 +2336,7 @@ function useInternalTransfer() {
|
|
|
1866
2336
|
},
|
|
1867
2337
|
[client, isReady, sync, transferOnce]
|
|
1868
2338
|
);
|
|
1869
|
-
const reset =
|
|
2339
|
+
const reset = useCallback13(() => {
|
|
1870
2340
|
setResult(null);
|
|
1871
2341
|
setIsLoading(false);
|
|
1872
2342
|
setStage("idle");
|
|
@@ -1882,47 +2352,35 @@ function useInternalTransfer() {
|
|
|
1882
2352
|
reset
|
|
1883
2353
|
};
|
|
1884
2354
|
}
|
|
1885
|
-
function getNoteType3(type) {
|
|
1886
|
-
switch (type) {
|
|
1887
|
-
case "private":
|
|
1888
|
-
return NoteType3.Private;
|
|
1889
|
-
case "public":
|
|
1890
|
-
return NoteType3.Public;
|
|
1891
|
-
case "encrypted":
|
|
1892
|
-
return NoteType3.Encrypted;
|
|
1893
|
-
default:
|
|
1894
|
-
return NoteType3.Private;
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
2355
|
|
|
1898
2356
|
// src/hooks/useWaitForCommit.ts
|
|
1899
|
-
import { useCallback as
|
|
1900
|
-
import { TransactionFilter as
|
|
2357
|
+
import { useCallback as useCallback14 } from "react";
|
|
2358
|
+
import { TransactionFilter as TransactionFilter3 } from "@miden-sdk/miden-sdk";
|
|
1901
2359
|
function useWaitForCommit() {
|
|
1902
2360
|
const { client, isReady, runExclusive } = useMiden();
|
|
1903
2361
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1904
|
-
const waitForCommit =
|
|
2362
|
+
const waitForCommit = useCallback14(
|
|
1905
2363
|
async (txId, options) => {
|
|
1906
2364
|
if (!client || !isReady) {
|
|
1907
2365
|
throw new Error("Miden client is not ready");
|
|
1908
2366
|
}
|
|
1909
2367
|
const timeoutMs = Math.max(0, options?.timeoutMs ?? 1e4);
|
|
1910
2368
|
const intervalMs = Math.max(1, options?.intervalMs ?? 1e3);
|
|
1911
|
-
const targetHex =
|
|
2369
|
+
const targetHex = normalizeHex3(
|
|
1912
2370
|
typeof txId === "string" ? txId : txId.toHex()
|
|
1913
2371
|
);
|
|
1914
|
-
|
|
1915
|
-
while (
|
|
2372
|
+
const deadline = Date.now() + timeoutMs;
|
|
2373
|
+
while (Date.now() < deadline) {
|
|
1916
2374
|
await runExclusiveSafe(
|
|
1917
2375
|
() => client.syncState()
|
|
1918
2376
|
);
|
|
1919
2377
|
const records = await runExclusiveSafe(
|
|
1920
2378
|
() => client.getTransactions(
|
|
1921
|
-
typeof txId === "string" ?
|
|
2379
|
+
typeof txId === "string" ? TransactionFilter3.all() : TransactionFilter3.ids([txId])
|
|
1922
2380
|
)
|
|
1923
2381
|
);
|
|
1924
2382
|
const record = records.find(
|
|
1925
|
-
(item) =>
|
|
2383
|
+
(item) => normalizeHex3(item.id().toHex()) === targetHex
|
|
1926
2384
|
);
|
|
1927
2385
|
if (record) {
|
|
1928
2386
|
const status = record.transactionStatus();
|
|
@@ -1934,7 +2392,6 @@ function useWaitForCommit() {
|
|
|
1934
2392
|
}
|
|
1935
2393
|
}
|
|
1936
2394
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1937
|
-
waited += intervalMs;
|
|
1938
2395
|
}
|
|
1939
2396
|
throw new Error("Timeout waiting for transaction commit");
|
|
1940
2397
|
},
|
|
@@ -1942,18 +2399,18 @@ function useWaitForCommit() {
|
|
|
1942
2399
|
);
|
|
1943
2400
|
return { waitForCommit };
|
|
1944
2401
|
}
|
|
1945
|
-
function
|
|
2402
|
+
function normalizeHex3(value) {
|
|
1946
2403
|
const trimmed = value.trim();
|
|
1947
2404
|
const normalized = trimmed.startsWith("0x") || trimmed.startsWith("0X") ? trimmed : `0x${trimmed}`;
|
|
1948
2405
|
return normalized.toLowerCase();
|
|
1949
2406
|
}
|
|
1950
2407
|
|
|
1951
2408
|
// src/hooks/useWaitForNotes.ts
|
|
1952
|
-
import { useCallback as
|
|
2409
|
+
import { useCallback as useCallback15 } from "react";
|
|
1953
2410
|
function useWaitForNotes() {
|
|
1954
2411
|
const { client, isReady, runExclusive } = useMiden();
|
|
1955
2412
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1956
|
-
const waitForConsumableNotes =
|
|
2413
|
+
const waitForConsumableNotes = useCallback15(
|
|
1957
2414
|
async (options) => {
|
|
1958
2415
|
if (!client || !isReady) {
|
|
1959
2416
|
throw new Error("Miden client is not ready");
|
|
@@ -1964,7 +2421,9 @@ function useWaitForNotes() {
|
|
|
1964
2421
|
const accountId = parseAccountId(options.accountId);
|
|
1965
2422
|
let waited = 0;
|
|
1966
2423
|
while (waited < timeoutMs) {
|
|
1967
|
-
await runExclusiveSafe(
|
|
2424
|
+
await runExclusiveSafe(
|
|
2425
|
+
() => client.syncState()
|
|
2426
|
+
);
|
|
1968
2427
|
const notes = await runExclusiveSafe(
|
|
1969
2428
|
() => client.getConsumableNotes(accountId)
|
|
1970
2429
|
);
|
|
@@ -1982,16 +2441,15 @@ function useWaitForNotes() {
|
|
|
1982
2441
|
}
|
|
1983
2442
|
|
|
1984
2443
|
// src/hooks/useMint.ts
|
|
1985
|
-
import { useCallback as
|
|
1986
|
-
import { NoteType as NoteType4 } from "@miden-sdk/miden-sdk";
|
|
2444
|
+
import { useCallback as useCallback16, useState as useState12 } from "react";
|
|
1987
2445
|
function useMint() {
|
|
1988
2446
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
1989
2447
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1990
|
-
const [result, setResult] =
|
|
1991
|
-
const [isLoading, setIsLoading] =
|
|
1992
|
-
const [stage, setStage] =
|
|
1993
|
-
const [error, setError] =
|
|
1994
|
-
const mint =
|
|
2448
|
+
const [result, setResult] = useState12(null);
|
|
2449
|
+
const [isLoading, setIsLoading] = useState12(false);
|
|
2450
|
+
const [stage, setStage] = useState12("idle");
|
|
2451
|
+
const [error, setError] = useState12(null);
|
|
2452
|
+
const mint = useCallback16(
|
|
1995
2453
|
async (options) => {
|
|
1996
2454
|
if (!client || !isReady) {
|
|
1997
2455
|
throw new Error("Miden client is not ready");
|
|
@@ -2000,7 +2458,7 @@ function useMint() {
|
|
|
2000
2458
|
setStage("executing");
|
|
2001
2459
|
setError(null);
|
|
2002
2460
|
try {
|
|
2003
|
-
const noteType =
|
|
2461
|
+
const noteType = getNoteType(options.noteType ?? DEFAULTS.NOTE_TYPE);
|
|
2004
2462
|
const targetAccountIdObj = parseAccountId(options.targetAccountId);
|
|
2005
2463
|
const faucetIdObj = parseAccountId(options.faucetId);
|
|
2006
2464
|
setStage("proving");
|
|
@@ -2033,7 +2491,7 @@ function useMint() {
|
|
|
2033
2491
|
},
|
|
2034
2492
|
[client, isReady, prover, runExclusive, sync]
|
|
2035
2493
|
);
|
|
2036
|
-
const reset =
|
|
2494
|
+
const reset = useCallback16(() => {
|
|
2037
2495
|
setResult(null);
|
|
2038
2496
|
setIsLoading(false);
|
|
2039
2497
|
setStage("idle");
|
|
@@ -2048,30 +2506,18 @@ function useMint() {
|
|
|
2048
2506
|
reset
|
|
2049
2507
|
};
|
|
2050
2508
|
}
|
|
2051
|
-
function getNoteType4(type) {
|
|
2052
|
-
switch (type) {
|
|
2053
|
-
case "private":
|
|
2054
|
-
return NoteType4.Private;
|
|
2055
|
-
case "public":
|
|
2056
|
-
return NoteType4.Public;
|
|
2057
|
-
case "encrypted":
|
|
2058
|
-
return NoteType4.Encrypted;
|
|
2059
|
-
default:
|
|
2060
|
-
return NoteType4.Private;
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
2509
|
|
|
2064
2510
|
// src/hooks/useConsume.ts
|
|
2065
|
-
import { useCallback as
|
|
2066
|
-
import { NoteFilter as
|
|
2511
|
+
import { useCallback as useCallback17, useState as useState13 } from "react";
|
|
2512
|
+
import { NoteFilter as NoteFilter3, NoteFilterTypes as NoteFilterTypes2, NoteId } from "@miden-sdk/miden-sdk";
|
|
2067
2513
|
function useConsume() {
|
|
2068
2514
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
2069
2515
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
2070
|
-
const [result, setResult] =
|
|
2071
|
-
const [isLoading, setIsLoading] =
|
|
2072
|
-
const [stage, setStage] =
|
|
2073
|
-
const [error, setError] =
|
|
2074
|
-
const consume =
|
|
2516
|
+
const [result, setResult] = useState13(null);
|
|
2517
|
+
const [isLoading, setIsLoading] = useState13(false);
|
|
2518
|
+
const [stage, setStage] = useState13("idle");
|
|
2519
|
+
const [error, setError] = useState13(null);
|
|
2520
|
+
const consume = useCallback17(
|
|
2075
2521
|
async (options) => {
|
|
2076
2522
|
if (!client || !isReady) {
|
|
2077
2523
|
throw new Error("Miden client is not ready");
|
|
@@ -2089,7 +2535,7 @@ function useConsume() {
|
|
|
2089
2535
|
const noteIds = options.noteIds.map(
|
|
2090
2536
|
(noteId) => NoteId.fromHex(noteId)
|
|
2091
2537
|
);
|
|
2092
|
-
const filter = new
|
|
2538
|
+
const filter = new NoteFilter3(NoteFilterTypes2.List, noteIds);
|
|
2093
2539
|
const noteRecords = await client.getInputNotes(filter);
|
|
2094
2540
|
const notes = noteRecords.map((record) => record.toNote());
|
|
2095
2541
|
if (notes.length === 0) {
|
|
@@ -2121,7 +2567,7 @@ function useConsume() {
|
|
|
2121
2567
|
},
|
|
2122
2568
|
[client, isReady, prover, runExclusive, sync]
|
|
2123
2569
|
);
|
|
2124
|
-
const reset =
|
|
2570
|
+
const reset = useCallback17(() => {
|
|
2125
2571
|
setResult(null);
|
|
2126
2572
|
setIsLoading(false);
|
|
2127
2573
|
setStage("idle");
|
|
@@ -2138,16 +2584,15 @@ function useConsume() {
|
|
|
2138
2584
|
}
|
|
2139
2585
|
|
|
2140
2586
|
// src/hooks/useSwap.ts
|
|
2141
|
-
import { useCallback as
|
|
2142
|
-
import { NoteType as NoteType5 } from "@miden-sdk/miden-sdk";
|
|
2587
|
+
import { useCallback as useCallback18, useState as useState14 } from "react";
|
|
2143
2588
|
function useSwap() {
|
|
2144
2589
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
2145
2590
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
2146
|
-
const [result, setResult] =
|
|
2147
|
-
const [isLoading, setIsLoading] =
|
|
2148
|
-
const [stage, setStage] =
|
|
2149
|
-
const [error, setError] =
|
|
2150
|
-
const swap =
|
|
2591
|
+
const [result, setResult] = useState14(null);
|
|
2592
|
+
const [isLoading, setIsLoading] = useState14(false);
|
|
2593
|
+
const [stage, setStage] = useState14("idle");
|
|
2594
|
+
const [error, setError] = useState14(null);
|
|
2595
|
+
const swap = useCallback18(
|
|
2151
2596
|
async (options) => {
|
|
2152
2597
|
if (!client || !isReady) {
|
|
2153
2598
|
throw new Error("Miden client is not ready");
|
|
@@ -2156,8 +2601,8 @@ function useSwap() {
|
|
|
2156
2601
|
setStage("executing");
|
|
2157
2602
|
setError(null);
|
|
2158
2603
|
try {
|
|
2159
|
-
const noteType =
|
|
2160
|
-
const paybackNoteType =
|
|
2604
|
+
const noteType = getNoteType(options.noteType ?? DEFAULTS.NOTE_TYPE);
|
|
2605
|
+
const paybackNoteType = getNoteType(
|
|
2161
2606
|
options.paybackNoteType ?? DEFAULTS.NOTE_TYPE
|
|
2162
2607
|
);
|
|
2163
2608
|
const accountIdObj = parseAccountId(options.accountId);
|
|
@@ -2196,7 +2641,7 @@ function useSwap() {
|
|
|
2196
2641
|
},
|
|
2197
2642
|
[client, isReady, prover, runExclusive, sync]
|
|
2198
2643
|
);
|
|
2199
|
-
const reset =
|
|
2644
|
+
const reset = useCallback18(() => {
|
|
2200
2645
|
setResult(null);
|
|
2201
2646
|
setIsLoading(false);
|
|
2202
2647
|
setStage("idle");
|
|
@@ -2211,41 +2656,40 @@ function useSwap() {
|
|
|
2211
2656
|
reset
|
|
2212
2657
|
};
|
|
2213
2658
|
}
|
|
2214
|
-
function getNoteType5(type) {
|
|
2215
|
-
switch (type) {
|
|
2216
|
-
case "private":
|
|
2217
|
-
return NoteType5.Private;
|
|
2218
|
-
case "public":
|
|
2219
|
-
return NoteType5.Public;
|
|
2220
|
-
case "encrypted":
|
|
2221
|
-
return NoteType5.Encrypted;
|
|
2222
|
-
default:
|
|
2223
|
-
return NoteType5.Private;
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
2659
|
|
|
2227
2660
|
// src/hooks/useTransaction.ts
|
|
2228
|
-
import { useCallback as
|
|
2661
|
+
import { useCallback as useCallback19, useRef as useRef5, useState as useState15 } from "react";
|
|
2229
2662
|
function useTransaction() {
|
|
2230
2663
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
2231
2664
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
2232
|
-
const
|
|
2233
|
-
const [
|
|
2234
|
-
const [
|
|
2235
|
-
const [
|
|
2236
|
-
const
|
|
2665
|
+
const isBusyRef = useRef5(false);
|
|
2666
|
+
const [result, setResult] = useState15(null);
|
|
2667
|
+
const [isLoading, setIsLoading] = useState15(false);
|
|
2668
|
+
const [stage, setStage] = useState15("idle");
|
|
2669
|
+
const [error, setError] = useState15(null);
|
|
2670
|
+
const execute = useCallback19(
|
|
2237
2671
|
async (options) => {
|
|
2238
2672
|
if (!client || !isReady) {
|
|
2239
2673
|
throw new Error("Miden client is not ready");
|
|
2240
2674
|
}
|
|
2675
|
+
if (isBusyRef.current) {
|
|
2676
|
+
throw new MidenError(
|
|
2677
|
+
"A transaction is already in progress. Await the previous transaction before starting another.",
|
|
2678
|
+
{ code: "SEND_BUSY" }
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
isBusyRef.current = true;
|
|
2241
2682
|
setIsLoading(true);
|
|
2242
2683
|
setStage("executing");
|
|
2243
2684
|
setError(null);
|
|
2244
2685
|
try {
|
|
2686
|
+
if (!options.skipSync) {
|
|
2687
|
+
await sync();
|
|
2688
|
+
}
|
|
2689
|
+
const txRequest = await resolveRequest(options.request, client);
|
|
2245
2690
|
setStage("proving");
|
|
2246
2691
|
const txResult = await runExclusiveSafe(async () => {
|
|
2247
2692
|
const accountIdObj = resolveAccountId2(options.accountId);
|
|
2248
|
-
const txRequest = await resolveRequest(options.request, client);
|
|
2249
2693
|
const txId = prover ? await client.submitNewTransactionWithProver(
|
|
2250
2694
|
accountIdObj,
|
|
2251
2695
|
txRequest,
|
|
@@ -2264,11 +2708,12 @@ function useTransaction() {
|
|
|
2264
2708
|
throw error2;
|
|
2265
2709
|
} finally {
|
|
2266
2710
|
setIsLoading(false);
|
|
2711
|
+
isBusyRef.current = false;
|
|
2267
2712
|
}
|
|
2268
2713
|
},
|
|
2269
2714
|
[client, isReady, prover, runExclusive, sync]
|
|
2270
2715
|
);
|
|
2271
|
-
const reset =
|
|
2716
|
+
const reset = useCallback19(() => {
|
|
2272
2717
|
setResult(null);
|
|
2273
2718
|
setIsLoading(false);
|
|
2274
2719
|
setStage("idle");
|
|
@@ -2293,16 +2738,338 @@ async function resolveRequest(request, client) {
|
|
|
2293
2738
|
return request;
|
|
2294
2739
|
}
|
|
2295
2740
|
|
|
2741
|
+
// src/hooks/useSessionAccount.ts
|
|
2742
|
+
import { useCallback as useCallback20, useEffect as useEffect8, useRef as useRef6, useState as useState16 } from "react";
|
|
2743
|
+
import { AccountStorageMode as AccountStorageMode3 } from "@miden-sdk/miden-sdk";
|
|
2744
|
+
function useSessionAccount(options) {
|
|
2745
|
+
const { client, isReady, sync, runExclusive } = useMiden();
|
|
2746
|
+
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
2747
|
+
const setAccounts = useMidenStore((state) => state.setAccounts);
|
|
2748
|
+
const [sessionAccountId, setSessionAccountId] = useState16(null);
|
|
2749
|
+
const [step, setStep] = useState16("idle");
|
|
2750
|
+
const [error, setError] = useState16(null);
|
|
2751
|
+
const cancelledRef = useRef6(false);
|
|
2752
|
+
const isBusyRef = useRef6(false);
|
|
2753
|
+
const storagePrefix = options.storagePrefix ?? "miden-session";
|
|
2754
|
+
const pollIntervalMs = options.pollIntervalMs ?? 3e3;
|
|
2755
|
+
const maxWaitMs = options.maxWaitMs ?? 6e4;
|
|
2756
|
+
const storageMode = options.walletOptions?.storageMode ?? "public";
|
|
2757
|
+
const mutable = options.walletOptions?.mutable ?? DEFAULTS.WALLET_MUTABLE;
|
|
2758
|
+
const authScheme = options.walletOptions?.authScheme ?? DEFAULTS.AUTH_SCHEME;
|
|
2759
|
+
const fundRef = useRef6(options.fund);
|
|
2760
|
+
fundRef.current = options.fund;
|
|
2761
|
+
useEffect8(() => {
|
|
2762
|
+
try {
|
|
2763
|
+
const stored = localStorage.getItem(`${storagePrefix}:accountId`);
|
|
2764
|
+
const storedReady = localStorage.getItem(`${storagePrefix}:ready`);
|
|
2765
|
+
if (stored && stored.length > 0) {
|
|
2766
|
+
const validationId = parseAccountId(stored);
|
|
2767
|
+
validationId?.free?.();
|
|
2768
|
+
setSessionAccountId(stored);
|
|
2769
|
+
if (storedReady === "true") {
|
|
2770
|
+
setStep("ready");
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
} catch {
|
|
2774
|
+
localStorage.removeItem(`${storagePrefix}:accountId`);
|
|
2775
|
+
localStorage.removeItem(`${storagePrefix}:ready`);
|
|
2776
|
+
}
|
|
2777
|
+
}, [storagePrefix]);
|
|
2778
|
+
const initialize = useCallback20(async () => {
|
|
2779
|
+
if (!client || !isReady) {
|
|
2780
|
+
throw new Error("Miden client is not ready");
|
|
2781
|
+
}
|
|
2782
|
+
if (isBusyRef.current) {
|
|
2783
|
+
throw new MidenError(
|
|
2784
|
+
"Session account initialization is already in progress.",
|
|
2785
|
+
{ code: "OPERATION_BUSY" }
|
|
2786
|
+
);
|
|
2787
|
+
}
|
|
2788
|
+
isBusyRef.current = true;
|
|
2789
|
+
cancelledRef.current = false;
|
|
2790
|
+
setError(null);
|
|
2791
|
+
try {
|
|
2792
|
+
let walletId = sessionAccountId;
|
|
2793
|
+
if (!walletId) {
|
|
2794
|
+
setStep("creating");
|
|
2795
|
+
const resolvedStorageMode = getStorageMode3(storageMode);
|
|
2796
|
+
const wallet = await runExclusiveSafe(async () => {
|
|
2797
|
+
const w = await client.newWallet(
|
|
2798
|
+
resolvedStorageMode,
|
|
2799
|
+
mutable,
|
|
2800
|
+
authScheme
|
|
2801
|
+
);
|
|
2802
|
+
ensureAccountBech32(w);
|
|
2803
|
+
const accounts = await client.getAccounts();
|
|
2804
|
+
setAccounts(accounts);
|
|
2805
|
+
return w;
|
|
2806
|
+
});
|
|
2807
|
+
if (cancelledRef.current) return;
|
|
2808
|
+
walletId = wallet.id().toString();
|
|
2809
|
+
setSessionAccountId(walletId);
|
|
2810
|
+
localStorage.setItem(`${storagePrefix}:accountId`, walletId);
|
|
2811
|
+
}
|
|
2812
|
+
setStep("funding");
|
|
2813
|
+
await fundRef.current(walletId);
|
|
2814
|
+
if (cancelledRef.current) return;
|
|
2815
|
+
setStep("consuming");
|
|
2816
|
+
await waitAndConsume(
|
|
2817
|
+
client,
|
|
2818
|
+
runExclusiveSafe,
|
|
2819
|
+
walletId,
|
|
2820
|
+
pollIntervalMs,
|
|
2821
|
+
maxWaitMs,
|
|
2822
|
+
cancelledRef
|
|
2823
|
+
);
|
|
2824
|
+
if (cancelledRef.current) return;
|
|
2825
|
+
setStep("ready");
|
|
2826
|
+
localStorage.setItem(`${storagePrefix}:ready`, "true");
|
|
2827
|
+
await sync();
|
|
2828
|
+
} catch (err) {
|
|
2829
|
+
if (!cancelledRef.current) {
|
|
2830
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
2831
|
+
setError(error2);
|
|
2832
|
+
setStep("idle");
|
|
2833
|
+
throw error2;
|
|
2834
|
+
}
|
|
2835
|
+
} finally {
|
|
2836
|
+
isBusyRef.current = false;
|
|
2837
|
+
}
|
|
2838
|
+
}, [
|
|
2839
|
+
client,
|
|
2840
|
+
isReady,
|
|
2841
|
+
sync,
|
|
2842
|
+
runExclusiveSafe,
|
|
2843
|
+
sessionAccountId,
|
|
2844
|
+
storageMode,
|
|
2845
|
+
mutable,
|
|
2846
|
+
authScheme,
|
|
2847
|
+
storagePrefix,
|
|
2848
|
+
pollIntervalMs,
|
|
2849
|
+
maxWaitMs,
|
|
2850
|
+
setAccounts
|
|
2851
|
+
]);
|
|
2852
|
+
const reset = useCallback20(() => {
|
|
2853
|
+
cancelledRef.current = true;
|
|
2854
|
+
isBusyRef.current = false;
|
|
2855
|
+
setSessionAccountId(null);
|
|
2856
|
+
setStep("idle");
|
|
2857
|
+
setError(null);
|
|
2858
|
+
localStorage.removeItem(`${storagePrefix}:accountId`);
|
|
2859
|
+
localStorage.removeItem(`${storagePrefix}:ready`);
|
|
2860
|
+
}, [storagePrefix]);
|
|
2861
|
+
return {
|
|
2862
|
+
initialize,
|
|
2863
|
+
sessionAccountId,
|
|
2864
|
+
isReady: step === "ready",
|
|
2865
|
+
step,
|
|
2866
|
+
error,
|
|
2867
|
+
reset
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2870
|
+
function getStorageMode3(mode) {
|
|
2871
|
+
switch (mode) {
|
|
2872
|
+
case "private":
|
|
2873
|
+
return AccountStorageMode3.private();
|
|
2874
|
+
case "public":
|
|
2875
|
+
return AccountStorageMode3.public();
|
|
2876
|
+
default:
|
|
2877
|
+
return AccountStorageMode3.public();
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
async function waitAndConsume(client, runExclusiveSafe, walletId, pollIntervalMs, maxWaitMs, cancelledRef) {
|
|
2881
|
+
const deadline = Date.now() + maxWaitMs;
|
|
2882
|
+
while (Date.now() < deadline) {
|
|
2883
|
+
if (cancelledRef.current) return;
|
|
2884
|
+
await runExclusiveSafe(
|
|
2885
|
+
() => client.syncState()
|
|
2886
|
+
);
|
|
2887
|
+
if (cancelledRef.current) return;
|
|
2888
|
+
const consumed = await runExclusiveSafe(async () => {
|
|
2889
|
+
const accountIdObj = parseAccountId(walletId);
|
|
2890
|
+
const consumable = await client.getConsumableNotes(accountIdObj);
|
|
2891
|
+
if (consumable.length === 0) return false;
|
|
2892
|
+
const notes = consumable.map((c) => c.inputNoteRecord().toNote());
|
|
2893
|
+
const txRequest = client.newConsumeTransactionRequest(notes);
|
|
2894
|
+
const freshAccountId = parseAccountId(walletId);
|
|
2895
|
+
await client.submitNewTransaction(freshAccountId, txRequest);
|
|
2896
|
+
return true;
|
|
2897
|
+
});
|
|
2898
|
+
if (consumed) return;
|
|
2899
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
2900
|
+
}
|
|
2901
|
+
throw new Error("Timeout waiting for session wallet funding");
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
// src/utils/bytes.ts
|
|
2905
|
+
function bytesToBigInt(bytes) {
|
|
2906
|
+
let result = 0n;
|
|
2907
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2908
|
+
result = result << 8n | BigInt(bytes[i]);
|
|
2909
|
+
}
|
|
2910
|
+
return result;
|
|
2911
|
+
}
|
|
2912
|
+
function bigIntToBytes(value, length) {
|
|
2913
|
+
if (value < 0n) {
|
|
2914
|
+
throw new RangeError("bigIntToBytes: value must be non-negative");
|
|
2915
|
+
}
|
|
2916
|
+
const maxValue = (1n << BigInt(length * 8)) - 1n;
|
|
2917
|
+
if (value > maxValue) {
|
|
2918
|
+
throw new RangeError(
|
|
2919
|
+
`bigIntToBytes: value ${value} does not fit in ${length} byte(s) (max ${maxValue})`
|
|
2920
|
+
);
|
|
2921
|
+
}
|
|
2922
|
+
const bytes = new Uint8Array(length);
|
|
2923
|
+
let remaining = value;
|
|
2924
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
2925
|
+
bytes[i] = Number(remaining & 0xffn);
|
|
2926
|
+
remaining >>= 8n;
|
|
2927
|
+
}
|
|
2928
|
+
return bytes;
|
|
2929
|
+
}
|
|
2930
|
+
function concatBytes(...arrays) {
|
|
2931
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
2932
|
+
const result = new Uint8Array(totalLength);
|
|
2933
|
+
let offset = 0;
|
|
2934
|
+
for (const arr of arrays) {
|
|
2935
|
+
result.set(arr, offset);
|
|
2936
|
+
offset += arr.length;
|
|
2937
|
+
}
|
|
2938
|
+
return result;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// src/utils/walletDetection.ts
|
|
2942
|
+
async function waitForWalletDetection(adapter, timeoutMs = 5e3) {
|
|
2943
|
+
if (adapter.readyState === "Installed") return;
|
|
2944
|
+
return new Promise((resolve, reject) => {
|
|
2945
|
+
let settled = false;
|
|
2946
|
+
const settle = () => {
|
|
2947
|
+
if (settled) return;
|
|
2948
|
+
settled = true;
|
|
2949
|
+
clearTimeout(timer);
|
|
2950
|
+
adapter.off("readyStateChange", onReady);
|
|
2951
|
+
resolve();
|
|
2952
|
+
};
|
|
2953
|
+
const timer = setTimeout(() => {
|
|
2954
|
+
if (settled) return;
|
|
2955
|
+
settled = true;
|
|
2956
|
+
adapter.off("readyStateChange", onReady);
|
|
2957
|
+
reject(
|
|
2958
|
+
new Error(
|
|
2959
|
+
`Wallet extension not detected within ${timeoutMs}ms. Is the browser extension installed and enabled?`
|
|
2960
|
+
)
|
|
2961
|
+
);
|
|
2962
|
+
}, timeoutMs);
|
|
2963
|
+
const onReady = (state) => {
|
|
2964
|
+
if (state === "Installed") settle();
|
|
2965
|
+
};
|
|
2966
|
+
adapter.on("readyStateChange", onReady);
|
|
2967
|
+
if (adapter.readyState === "Installed") settle();
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
// src/utils/storage.ts
|
|
2972
|
+
async function migrateStorage(options) {
|
|
2973
|
+
if (typeof window === "undefined") return false;
|
|
2974
|
+
const versionKey = options.versionKey ?? "miden:storageVersion";
|
|
2975
|
+
const stored = localStorage.getItem(versionKey);
|
|
2976
|
+
if (stored === options.version) return false;
|
|
2977
|
+
if (options.onBeforeClear) {
|
|
2978
|
+
await options.onBeforeClear();
|
|
2979
|
+
}
|
|
2980
|
+
await clearMidenStorage();
|
|
2981
|
+
localStorage.setItem(versionKey, options.version);
|
|
2982
|
+
if (options.reloadOnClear !== false) {
|
|
2983
|
+
window.location.reload();
|
|
2984
|
+
}
|
|
2985
|
+
return true;
|
|
2986
|
+
}
|
|
2987
|
+
async function clearMidenStorage() {
|
|
2988
|
+
if (typeof indexedDB === "undefined") return;
|
|
2989
|
+
try {
|
|
2990
|
+
const databases = await indexedDB.databases();
|
|
2991
|
+
const midenDbs = databases.filter(
|
|
2992
|
+
(db) => db.name?.toLowerCase().includes("miden")
|
|
2993
|
+
);
|
|
2994
|
+
await Promise.all(
|
|
2995
|
+
midenDbs.map(
|
|
2996
|
+
(db) => new Promise((resolve, reject) => {
|
|
2997
|
+
if (!db.name) {
|
|
2998
|
+
resolve();
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
const req = indexedDB.deleteDatabase(db.name);
|
|
3002
|
+
req.onsuccess = () => resolve();
|
|
3003
|
+
req.onerror = () => reject(req.error);
|
|
3004
|
+
req.onblocked = () => {
|
|
3005
|
+
console.warn(
|
|
3006
|
+
`IndexedDB "${db.name}" delete was blocked \u2014 close other tabs using this database.`
|
|
3007
|
+
);
|
|
3008
|
+
resolve();
|
|
3009
|
+
};
|
|
3010
|
+
})
|
|
3011
|
+
)
|
|
3012
|
+
);
|
|
3013
|
+
} catch {
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
function createMidenStorage(prefix) {
|
|
3017
|
+
const fullKey = (key) => `${prefix}:${key}`;
|
|
3018
|
+
return {
|
|
3019
|
+
get(key) {
|
|
3020
|
+
try {
|
|
3021
|
+
const raw = localStorage.getItem(fullKey(key));
|
|
3022
|
+
if (raw === null) return null;
|
|
3023
|
+
return JSON.parse(raw);
|
|
3024
|
+
} catch {
|
|
3025
|
+
return null;
|
|
3026
|
+
}
|
|
3027
|
+
},
|
|
3028
|
+
set(key, value) {
|
|
3029
|
+
try {
|
|
3030
|
+
localStorage.setItem(fullKey(key), JSON.stringify(value));
|
|
3031
|
+
} catch (e) {
|
|
3032
|
+
console.warn(`Failed to write localStorage key "${fullKey(key)}":`, e);
|
|
3033
|
+
}
|
|
3034
|
+
},
|
|
3035
|
+
remove(key) {
|
|
3036
|
+
localStorage.removeItem(fullKey(key));
|
|
3037
|
+
},
|
|
3038
|
+
clear() {
|
|
3039
|
+
const keysToRemove = [];
|
|
3040
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
3041
|
+
const k = localStorage.key(i);
|
|
3042
|
+
if (k?.startsWith(`${prefix}:`)) {
|
|
3043
|
+
keysToRemove.push(k);
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
keysToRemove.forEach((k) => localStorage.removeItem(k));
|
|
3047
|
+
}
|
|
3048
|
+
};
|
|
3049
|
+
}
|
|
3050
|
+
|
|
2296
3051
|
// src/index.ts
|
|
2297
3052
|
installAccountBech32();
|
|
2298
3053
|
export {
|
|
3054
|
+
AuthScheme,
|
|
2299
3055
|
DEFAULTS,
|
|
3056
|
+
MidenError,
|
|
2300
3057
|
MidenProvider,
|
|
2301
3058
|
SignerContext,
|
|
3059
|
+
accountIdsEqual,
|
|
3060
|
+
bigIntToBytes,
|
|
3061
|
+
bytesToBigInt,
|
|
3062
|
+
clearMidenStorage,
|
|
3063
|
+
concatBytes,
|
|
3064
|
+
createMidenStorage,
|
|
3065
|
+
createNoteAttachment,
|
|
2302
3066
|
formatAssetAmount,
|
|
2303
3067
|
formatNoteSummary,
|
|
2304
3068
|
getNoteSummary,
|
|
3069
|
+
migrateStorage,
|
|
3070
|
+
normalizeAccountId,
|
|
2305
3071
|
parseAssetAmount,
|
|
3072
|
+
readNoteAttachment,
|
|
2306
3073
|
toBech32AccountId,
|
|
2307
3074
|
useAccount,
|
|
2308
3075
|
useAccounts,
|
|
@@ -2316,13 +3083,17 @@ export {
|
|
|
2316
3083
|
useMidenClient,
|
|
2317
3084
|
useMint,
|
|
2318
3085
|
useMultiSend,
|
|
3086
|
+
useNoteStream,
|
|
2319
3087
|
useNotes,
|
|
2320
3088
|
useSend,
|
|
3089
|
+
useSessionAccount,
|
|
2321
3090
|
useSigner,
|
|
2322
3091
|
useSwap,
|
|
2323
3092
|
useSyncState,
|
|
2324
3093
|
useTransaction,
|
|
2325
3094
|
useTransactionHistory,
|
|
2326
3095
|
useWaitForCommit,
|
|
2327
|
-
useWaitForNotes
|
|
3096
|
+
useWaitForNotes,
|
|
3097
|
+
waitForWalletDetection,
|
|
3098
|
+
wrapWasmError
|
|
2328
3099
|
};
|