@miden-sdk/react 0.13.2 → 0.13.3
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 +264 -12
- package/dist/index.d.ts +264 -12
- package/dist/index.js +1140 -363
- package/dist/index.mjs +1144 -367
- package/package.json +1 -1
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)) {
|
|
@@ -339,19 +420,29 @@ function isPrivateStorageMode(storageMode) {
|
|
|
339
420
|
return storageMode.toString() === "private";
|
|
340
421
|
}
|
|
341
422
|
async function initializeSignerAccount(client, config) {
|
|
342
|
-
const { AccountBuilder, AccountComponent, Word } = await import("@miden-sdk/miden-sdk");
|
|
423
|
+
const { AccountBuilder, AccountComponent, Word: Word2 } = await import("@miden-sdk/miden-sdk");
|
|
343
424
|
await client.syncState();
|
|
344
|
-
const commitmentWord =
|
|
425
|
+
const commitmentWord = Word2.deserialize(config.publicKeyCommitment);
|
|
345
426
|
const seed = config.accountSeed ?? crypto.getRandomValues(new Uint8Array(32));
|
|
346
427
|
const accountType = await getAccountType(config.accountType);
|
|
347
|
-
|
|
348
|
-
const buildResult = builder.withAuthComponent(
|
|
428
|
+
let builder = new AccountBuilder(seed).withAuthComponent(
|
|
349
429
|
AccountComponent.createAuthComponentFromCommitment(
|
|
350
430
|
commitmentWord,
|
|
351
431
|
1
|
|
352
432
|
// ECDSA auth scheme (K256/Keccak)
|
|
353
433
|
)
|
|
354
|
-
).accountType(accountType).storageMode(config.storageMode).withBasicWalletComponent()
|
|
434
|
+
).accountType(accountType).storageMode(config.storageMode).withBasicWalletComponent();
|
|
435
|
+
if (config.customComponents?.length) {
|
|
436
|
+
for (const component of config.customComponents) {
|
|
437
|
+
if (component == null || typeof component.getProcedures !== "function") {
|
|
438
|
+
throw new Error(
|
|
439
|
+
"Each entry in customComponents must be an AccountComponent instance created via AccountComponent.compile(), AccountComponent.fromPackage(), or AccountComponent.fromLibrary()."
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
builder = builder.withComponent(component);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const buildResult = builder.build();
|
|
355
446
|
const account = buildResult.account;
|
|
356
447
|
const accountId = account.id();
|
|
357
448
|
if (!isPrivateStorageMode(config.storageMode)) {
|
|
@@ -455,9 +546,6 @@ function MidenProvider({
|
|
|
455
546
|
}
|
|
456
547
|
return;
|
|
457
548
|
}
|
|
458
|
-
if (!signerContext) {
|
|
459
|
-
isInitializedRef.current = true;
|
|
460
|
-
}
|
|
461
549
|
let cancelled = false;
|
|
462
550
|
const initClient = async () => {
|
|
463
551
|
await runExclusive(async () => {
|
|
@@ -517,6 +605,9 @@ function MidenProvider({
|
|
|
517
605
|
}
|
|
518
606
|
}
|
|
519
607
|
if (!cancelled) {
|
|
608
|
+
if (!signerContext) {
|
|
609
|
+
isInitializedRef.current = true;
|
|
610
|
+
}
|
|
520
611
|
setClient(webClient);
|
|
521
612
|
}
|
|
522
613
|
} catch (error) {
|
|
@@ -531,6 +622,7 @@ function MidenProvider({
|
|
|
531
622
|
initClient();
|
|
532
623
|
return () => {
|
|
533
624
|
cancelled = true;
|
|
625
|
+
isInitializedRef.current = false;
|
|
534
626
|
};
|
|
535
627
|
}, [
|
|
536
628
|
runExclusive,
|
|
@@ -646,16 +738,6 @@ function useAccounts() {
|
|
|
646
738
|
refetch
|
|
647
739
|
};
|
|
648
740
|
}
|
|
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
741
|
|
|
660
742
|
// src/hooks/useAccount.ts
|
|
661
743
|
import { useCallback as useCallback3, useEffect as useEffect4, useState as useState2, useMemo as useMemo3 } from "react";
|
|
@@ -682,17 +764,12 @@ var getRpcClient = (rpcUrl) => {
|
|
|
682
764
|
return null;
|
|
683
765
|
}
|
|
684
766
|
};
|
|
685
|
-
var fetchAssetMetadata = async (
|
|
767
|
+
var fetchAssetMetadata = async (rpcClient, assetId) => {
|
|
686
768
|
try {
|
|
687
769
|
const accountId = parseAccountId(assetId);
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const fetched = await rpcClient.getAccountDetails(accountId);
|
|
692
|
-
account = fetched.account?.();
|
|
693
|
-
} catch {
|
|
694
|
-
}
|
|
695
|
-
}
|
|
770
|
+
if (!isFaucetId(accountId)) return null;
|
|
771
|
+
const fetched = await rpcClient.getAccountDetails(accountId);
|
|
772
|
+
const account = fetched.account?.();
|
|
696
773
|
if (!account) return null;
|
|
697
774
|
const faucet = BasicFungibleFaucetComponent.fromAccount(account);
|
|
698
775
|
const symbol = faucet.symbol().toString();
|
|
@@ -703,8 +780,6 @@ var fetchAssetMetadata = async (client, rpcClient, assetId) => {
|
|
|
703
780
|
}
|
|
704
781
|
};
|
|
705
782
|
function useAssetMetadata(assetIds = []) {
|
|
706
|
-
const { client, isReady, runExclusive } = useMiden();
|
|
707
|
-
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
708
783
|
const assetMetadata = useAssetMetadataStore();
|
|
709
784
|
const setAssetMetadata = useMidenStore((state) => state.setAssetMetadata);
|
|
710
785
|
const rpcUrl = useMidenStore((state) => state.config.rpcUrl);
|
|
@@ -714,32 +789,19 @@ function useAssetMetadata(assetIds = []) {
|
|
|
714
789
|
[assetIds]
|
|
715
790
|
);
|
|
716
791
|
useEffect3(() => {
|
|
717
|
-
if (!
|
|
792
|
+
if (!rpcClient || uniqueAssetIds.length === 0) return;
|
|
718
793
|
uniqueAssetIds.forEach((assetId) => {
|
|
719
794
|
const existing = assetMetadata.get(assetId);
|
|
720
795
|
const hasMetadata = existing?.symbol !== void 0 || existing?.decimals !== void 0;
|
|
721
796
|
if (hasMetadata || inflight.has(assetId)) return;
|
|
722
|
-
const promise =
|
|
723
|
-
const metadata = await fetchAssetMetadata(
|
|
724
|
-
client,
|
|
725
|
-
rpcClient,
|
|
726
|
-
assetId
|
|
727
|
-
);
|
|
797
|
+
const promise = fetchAssetMetadata(rpcClient, assetId).then((metadata) => {
|
|
728
798
|
setAssetMetadata(assetId, metadata ?? { assetId });
|
|
729
799
|
}).finally(() => {
|
|
730
800
|
inflight.delete(assetId);
|
|
731
801
|
});
|
|
732
802
|
inflight.set(assetId, promise);
|
|
733
803
|
});
|
|
734
|
-
}, [
|
|
735
|
-
client,
|
|
736
|
-
isReady,
|
|
737
|
-
uniqueAssetIds,
|
|
738
|
-
assetMetadata,
|
|
739
|
-
runExclusiveSafe,
|
|
740
|
-
setAssetMetadata,
|
|
741
|
-
rpcClient
|
|
742
|
-
]);
|
|
804
|
+
}, [uniqueAssetIds, assetMetadata, setAssetMetadata, rpcClient]);
|
|
743
805
|
return { assetMetadata };
|
|
744
806
|
}
|
|
745
807
|
|
|
@@ -841,7 +903,7 @@ function useAccount(accountId) {
|
|
|
841
903
|
|
|
842
904
|
// src/hooks/useNotes.ts
|
|
843
905
|
import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo4, useState as useState3 } from "react";
|
|
844
|
-
import { NoteFilter
|
|
906
|
+
import { NoteFilter } from "@miden-sdk/miden-sdk";
|
|
845
907
|
|
|
846
908
|
// src/utils/amounts.ts
|
|
847
909
|
var formatAssetAmount = (amount, decimals) => {
|
|
@@ -925,6 +987,86 @@ var formatNoteSummary = (summary, formatAsset = (asset) => `${formatAssetAmount(
|
|
|
925
987
|
return summary.sender ? `${assetsText} from ${summary.sender}` : assetsText;
|
|
926
988
|
};
|
|
927
989
|
|
|
990
|
+
// src/utils/accountId.ts
|
|
991
|
+
function normalizeAccountId(id) {
|
|
992
|
+
return toBech32AccountId(id);
|
|
993
|
+
}
|
|
994
|
+
function accountIdsEqual(a, b) {
|
|
995
|
+
let idA;
|
|
996
|
+
let idB;
|
|
997
|
+
try {
|
|
998
|
+
idA = parseAccountId(a);
|
|
999
|
+
idB = parseAccountId(b);
|
|
1000
|
+
return idA.toString() === idB.toString();
|
|
1001
|
+
} catch {
|
|
1002
|
+
return a === b;
|
|
1003
|
+
} finally {
|
|
1004
|
+
idA?.free?.();
|
|
1005
|
+
idB?.free?.();
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/utils/noteFilters.ts
|
|
1010
|
+
import {
|
|
1011
|
+
NoteFilterTypes,
|
|
1012
|
+
NoteType,
|
|
1013
|
+
TransactionFilter
|
|
1014
|
+
} from "@miden-sdk/miden-sdk";
|
|
1015
|
+
function getNoteFilterType(status) {
|
|
1016
|
+
switch (status) {
|
|
1017
|
+
case "consumed":
|
|
1018
|
+
return NoteFilterTypes.Consumed;
|
|
1019
|
+
case "committed":
|
|
1020
|
+
return NoteFilterTypes.Committed;
|
|
1021
|
+
case "expected":
|
|
1022
|
+
return NoteFilterTypes.Expected;
|
|
1023
|
+
case "processing":
|
|
1024
|
+
return NoteFilterTypes.Processing;
|
|
1025
|
+
case "all":
|
|
1026
|
+
default:
|
|
1027
|
+
return NoteFilterTypes.All;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
function getNoteType(type) {
|
|
1031
|
+
switch (type) {
|
|
1032
|
+
case "private":
|
|
1033
|
+
return NoteType.Private;
|
|
1034
|
+
case "public":
|
|
1035
|
+
return NoteType.Public;
|
|
1036
|
+
default:
|
|
1037
|
+
return NoteType.Private;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
async function waitForTransactionCommit(client, runExclusiveSafe, txIdHex, maxWaitMs = 1e4, delayMs = 1e3) {
|
|
1041
|
+
const deadline = Date.now() + maxWaitMs;
|
|
1042
|
+
const targetHex = normalizeHex(txIdHex);
|
|
1043
|
+
while (Date.now() < deadline) {
|
|
1044
|
+
await runExclusiveSafe(() => client.syncState());
|
|
1045
|
+
const records = await runExclusiveSafe(
|
|
1046
|
+
() => client.getTransactions(TransactionFilter.all())
|
|
1047
|
+
);
|
|
1048
|
+
const record = records.find(
|
|
1049
|
+
(r) => normalizeHex(r.id().toHex()) === targetHex
|
|
1050
|
+
);
|
|
1051
|
+
if (record) {
|
|
1052
|
+
const status = record.transactionStatus();
|
|
1053
|
+
if (status.isCommitted()) {
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
if (status.isDiscarded()) {
|
|
1057
|
+
throw new Error("Transaction was discarded before commit");
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1061
|
+
}
|
|
1062
|
+
throw new Error("Timeout waiting for transaction commit");
|
|
1063
|
+
}
|
|
1064
|
+
function normalizeHex(value) {
|
|
1065
|
+
const trimmed = value.trim();
|
|
1066
|
+
const normalized = trimmed.startsWith("0x") || trimmed.startsWith("0X") ? trimmed : `0x${trimmed}`;
|
|
1067
|
+
return normalized.toLowerCase();
|
|
1068
|
+
}
|
|
1069
|
+
|
|
928
1070
|
// src/hooks/useNotes.ts
|
|
929
1071
|
function useNotes(options) {
|
|
930
1072
|
const { client, isReady, runExclusive } = useMiden();
|
|
@@ -933,8 +1075,10 @@ function useNotes(options) {
|
|
|
933
1075
|
const consumableNotes = useConsumableNotesStore();
|
|
934
1076
|
const isLoadingNotes = useMidenStore((state) => state.isLoadingNotes);
|
|
935
1077
|
const setLoadingNotes = useMidenStore((state) => state.setLoadingNotes);
|
|
936
|
-
const
|
|
937
|
-
const
|
|
1078
|
+
const setNotesIfChanged = useMidenStore((state) => state.setNotesIfChanged);
|
|
1079
|
+
const setConsumableNotesIfChanged = useMidenStore(
|
|
1080
|
+
(state) => state.setConsumableNotesIfChanged
|
|
1081
|
+
);
|
|
938
1082
|
const { lastSyncTime } = useSyncStateStore();
|
|
939
1083
|
const [error, setError] = useState3(null);
|
|
940
1084
|
const refetch = useCallback4(async () => {
|
|
@@ -960,8 +1104,8 @@ function useNotes(options) {
|
|
|
960
1104
|
};
|
|
961
1105
|
}
|
|
962
1106
|
);
|
|
963
|
-
|
|
964
|
-
|
|
1107
|
+
setNotesIfChanged(fetchedNotes);
|
|
1108
|
+
setConsumableNotesIfChanged(fetchedConsumable);
|
|
965
1109
|
} catch (err) {
|
|
966
1110
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
967
1111
|
} finally {
|
|
@@ -974,8 +1118,8 @@ function useNotes(options) {
|
|
|
974
1118
|
options?.status,
|
|
975
1119
|
options?.accountId,
|
|
976
1120
|
setLoadingNotes,
|
|
977
|
-
|
|
978
|
-
|
|
1121
|
+
setNotesIfChanged,
|
|
1122
|
+
setConsumableNotesIfChanged
|
|
979
1123
|
]);
|
|
980
1124
|
useEffect5(() => {
|
|
981
1125
|
if (isReady && notes.length === 0) {
|
|
@@ -1002,14 +1146,65 @@ function useNotes(options) {
|
|
|
1002
1146
|
(assetId) => assetMetadata.get(assetId),
|
|
1003
1147
|
[assetMetadata]
|
|
1004
1148
|
);
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1149
|
+
const normalizedSender = useMemo4(() => {
|
|
1150
|
+
if (!options?.sender) return null;
|
|
1151
|
+
try {
|
|
1152
|
+
return normalizeAccountId(options.sender);
|
|
1153
|
+
} catch {
|
|
1154
|
+
return options.sender;
|
|
1155
|
+
}
|
|
1156
|
+
}, [options?.sender]);
|
|
1157
|
+
const excludeIdsKey = useMemo4(() => {
|
|
1158
|
+
if (!options?.excludeIds || options.excludeIds.length === 0) return "";
|
|
1159
|
+
return [...options.excludeIds].sort().join("\0");
|
|
1160
|
+
}, [options?.excludeIds]);
|
|
1161
|
+
const filterBySender = useCallback4(
|
|
1162
|
+
(summaries, target) => {
|
|
1163
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1164
|
+
return summaries.filter((s) => {
|
|
1165
|
+
if (!s.sender) return false;
|
|
1166
|
+
let normalized = cache.get(s.sender);
|
|
1167
|
+
if (normalized === void 0) {
|
|
1168
|
+
try {
|
|
1169
|
+
normalized = normalizeAccountId(s.sender);
|
|
1170
|
+
} catch {
|
|
1171
|
+
normalized = s.sender;
|
|
1172
|
+
}
|
|
1173
|
+
cache.set(s.sender, normalized);
|
|
1174
|
+
}
|
|
1175
|
+
return normalized === target;
|
|
1176
|
+
});
|
|
1177
|
+
},
|
|
1178
|
+
[]
|
|
1012
1179
|
);
|
|
1180
|
+
const noteSummaries = useMemo4(() => {
|
|
1181
|
+
let summaries = notes.map((note) => getNoteSummary(note, getMetadata)).filter(Boolean);
|
|
1182
|
+
if (normalizedSender) {
|
|
1183
|
+
summaries = filterBySender(summaries, normalizedSender);
|
|
1184
|
+
}
|
|
1185
|
+
if (excludeIdsKey) {
|
|
1186
|
+
const excludeSet = new Set(excludeIdsKey.split("\0"));
|
|
1187
|
+
summaries = summaries.filter((s) => !excludeSet.has(s.id));
|
|
1188
|
+
}
|
|
1189
|
+
return summaries;
|
|
1190
|
+
}, [notes, getMetadata, normalizedSender, excludeIdsKey, filterBySender]);
|
|
1191
|
+
const consumableNoteSummaries = useMemo4(() => {
|
|
1192
|
+
let summaries = consumableNotes.map((note) => getNoteSummary(note, getMetadata)).filter(Boolean);
|
|
1193
|
+
if (normalizedSender) {
|
|
1194
|
+
summaries = filterBySender(summaries, normalizedSender);
|
|
1195
|
+
}
|
|
1196
|
+
if (excludeIdsKey) {
|
|
1197
|
+
const excludeSet = new Set(excludeIdsKey.split("\0"));
|
|
1198
|
+
summaries = summaries.filter((s) => !excludeSet.has(s.id));
|
|
1199
|
+
}
|
|
1200
|
+
return summaries;
|
|
1201
|
+
}, [
|
|
1202
|
+
consumableNotes,
|
|
1203
|
+
getMetadata,
|
|
1204
|
+
normalizedSender,
|
|
1205
|
+
excludeIdsKey,
|
|
1206
|
+
filterBySender
|
|
1207
|
+
]);
|
|
1013
1208
|
return {
|
|
1014
1209
|
notes,
|
|
1015
1210
|
consumableNotes,
|
|
@@ -1020,46 +1215,251 @@ function useNotes(options) {
|
|
|
1020
1215
|
refetch
|
|
1021
1216
|
};
|
|
1022
1217
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1218
|
+
|
|
1219
|
+
// src/hooks/useNoteStream.ts
|
|
1220
|
+
import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef2, useState as useState4 } from "react";
|
|
1221
|
+
import { NoteFilter as NoteFilter2 } from "@miden-sdk/miden-sdk";
|
|
1222
|
+
|
|
1223
|
+
// src/utils/noteAttachment.ts
|
|
1224
|
+
import {
|
|
1225
|
+
NoteAttachment,
|
|
1226
|
+
NoteAttachmentKind,
|
|
1227
|
+
NoteAttachmentScheme,
|
|
1228
|
+
Word
|
|
1229
|
+
} from "@miden-sdk/miden-sdk";
|
|
1230
|
+
function readNoteAttachment(note) {
|
|
1231
|
+
try {
|
|
1232
|
+
const metadata = note.metadata?.();
|
|
1233
|
+
if (!metadata) return null;
|
|
1234
|
+
const attachment = metadata.attachment?.();
|
|
1235
|
+
if (!attachment) return null;
|
|
1236
|
+
const kind = attachment.kind?.();
|
|
1237
|
+
if (!kind) return null;
|
|
1238
|
+
if (kind === NoteAttachmentKind.None) return null;
|
|
1239
|
+
if (kind === NoteAttachmentKind.Word) {
|
|
1240
|
+
const word = attachment.asWord?.();
|
|
1241
|
+
if (!word) return null;
|
|
1242
|
+
const u64s = word.toU64s();
|
|
1243
|
+
const values = Array.from(u64s).map(
|
|
1244
|
+
(v) => BigInt(v)
|
|
1245
|
+
);
|
|
1246
|
+
return { values, kind: "word" };
|
|
1247
|
+
}
|
|
1248
|
+
if (kind === NoteAttachmentKind.Array) {
|
|
1249
|
+
const arr = attachment.asArray?.();
|
|
1250
|
+
if (!arr) return null;
|
|
1251
|
+
const u64s = arr.toU64s();
|
|
1252
|
+
const values = Array.from(u64s).map(
|
|
1253
|
+
(v) => BigInt(v)
|
|
1254
|
+
);
|
|
1255
|
+
return { values, kind: "array" };
|
|
1256
|
+
}
|
|
1257
|
+
return null;
|
|
1258
|
+
} catch {
|
|
1259
|
+
return null;
|
|
1036
1260
|
}
|
|
1037
1261
|
}
|
|
1262
|
+
function createNoteAttachment(values) {
|
|
1263
|
+
const bigints = [];
|
|
1264
|
+
for (let i = 0; i < values.length; i++) {
|
|
1265
|
+
bigints.push(BigInt(values[i]));
|
|
1266
|
+
}
|
|
1267
|
+
if (bigints.length === 0) {
|
|
1268
|
+
return new NoteAttachment();
|
|
1269
|
+
}
|
|
1270
|
+
const scheme = NoteAttachmentScheme.none();
|
|
1271
|
+
if (bigints.length <= 4) {
|
|
1272
|
+
while (bigints.length < 4) {
|
|
1273
|
+
bigints.push(0n);
|
|
1274
|
+
}
|
|
1275
|
+
const word = new Word(BigUint64Array.from(bigints));
|
|
1276
|
+
return NoteAttachment.newWord(scheme, word);
|
|
1277
|
+
}
|
|
1278
|
+
while (bigints.length % 4 !== 0) {
|
|
1279
|
+
bigints.push(0n);
|
|
1280
|
+
}
|
|
1281
|
+
const words = [];
|
|
1282
|
+
for (let i = 0; i < bigints.length; i += 4) {
|
|
1283
|
+
words.push(new Word(BigUint64Array.from(bigints.slice(i, i + 4))));
|
|
1284
|
+
}
|
|
1285
|
+
const newArray = NoteAttachment["newArray"];
|
|
1286
|
+
if (typeof newArray !== "function") {
|
|
1287
|
+
throw new Error(
|
|
1288
|
+
"NoteAttachment.newArray is not available. Ensure @miden-sdk/miden-sdk >= 0.13.1."
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
return newArray.call(NoteAttachment, scheme, words);
|
|
1292
|
+
}
|
|
1038
1293
|
|
|
1039
|
-
// src/hooks/
|
|
1040
|
-
|
|
1041
|
-
import { TransactionFilter } from "@miden-sdk/miden-sdk";
|
|
1042
|
-
function useTransactionHistory(options = {}) {
|
|
1294
|
+
// src/hooks/useNoteStream.ts
|
|
1295
|
+
function useNoteStream(options = {}) {
|
|
1043
1296
|
const { client, isReady, runExclusive } = useMiden();
|
|
1044
1297
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1298
|
+
const allNotes = useNotesStore();
|
|
1299
|
+
const noteFirstSeen = useNoteFirstSeenStore();
|
|
1045
1300
|
const { lastSyncTime } = useSyncStateStore();
|
|
1046
|
-
const
|
|
1301
|
+
const setNotesIfChanged = useMidenStore((state) => state.setNotesIfChanged);
|
|
1047
1302
|
const [isLoading, setIsLoading] = useState4(false);
|
|
1048
1303
|
const [error, setError] = useState4(null);
|
|
1049
|
-
const
|
|
1304
|
+
const handledIdsRef = useRef2(/* @__PURE__ */ new Set());
|
|
1305
|
+
const [handledVersion, setHandledVersion] = useState4(0);
|
|
1306
|
+
const status = options.status ?? "committed";
|
|
1307
|
+
const sender = options.sender ?? null;
|
|
1308
|
+
const since = options.since;
|
|
1309
|
+
const amountFilterRef = useRef2(options.amountFilter);
|
|
1310
|
+
amountFilterRef.current = options.amountFilter;
|
|
1311
|
+
const excludeIdsKey = useMemo5(() => {
|
|
1312
|
+
if (!options.excludeIds) return "";
|
|
1313
|
+
if (options.excludeIds instanceof Set)
|
|
1314
|
+
return Array.from(options.excludeIds).sort().join("\0");
|
|
1315
|
+
return [...options.excludeIds].sort().join("\0");
|
|
1316
|
+
}, [options.excludeIds]);
|
|
1317
|
+
const excludeIdSet = useMemo5(() => {
|
|
1318
|
+
if (!excludeIdsKey) return null;
|
|
1319
|
+
return new Set(excludeIdsKey.split("\0"));
|
|
1320
|
+
}, [excludeIdsKey]);
|
|
1321
|
+
const refetch = useCallback5(async () => {
|
|
1322
|
+
if (!client || !isReady) return;
|
|
1323
|
+
setIsLoading(true);
|
|
1324
|
+
setError(null);
|
|
1325
|
+
try {
|
|
1326
|
+
const filterType = getNoteFilterType(status);
|
|
1327
|
+
const filter = new NoteFilter2(filterType);
|
|
1328
|
+
const fetched = await runExclusiveSafe(
|
|
1329
|
+
() => client.getInputNotes(filter)
|
|
1330
|
+
);
|
|
1331
|
+
setNotesIfChanged(fetched);
|
|
1332
|
+
} catch (err) {
|
|
1333
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1334
|
+
} finally {
|
|
1335
|
+
setIsLoading(false);
|
|
1336
|
+
}
|
|
1337
|
+
}, [client, isReady, runExclusiveSafe, status, setNotesIfChanged]);
|
|
1338
|
+
useEffect6(() => {
|
|
1339
|
+
if (isReady) {
|
|
1340
|
+
refetch();
|
|
1341
|
+
}
|
|
1342
|
+
}, [isReady, lastSyncTime, refetch]);
|
|
1343
|
+
const streamedNotes = useMemo5(() => {
|
|
1344
|
+
void handledVersion;
|
|
1345
|
+
const result = [];
|
|
1346
|
+
let normalizedSender = null;
|
|
1347
|
+
if (sender) {
|
|
1348
|
+
try {
|
|
1349
|
+
normalizedSender = normalizeAccountId(sender);
|
|
1350
|
+
} catch {
|
|
1351
|
+
normalizedSender = sender;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
for (const record of allNotes) {
|
|
1355
|
+
const note = buildStreamedNote(record, noteFirstSeen);
|
|
1356
|
+
if (!note) continue;
|
|
1357
|
+
if (handledIdsRef.current.has(note.id)) continue;
|
|
1358
|
+
if (excludeIdSet && excludeIdSet.has(note.id)) continue;
|
|
1359
|
+
if (normalizedSender && note.sender !== normalizedSender) continue;
|
|
1360
|
+
if (since !== void 0 && note.firstSeenAt < since) continue;
|
|
1361
|
+
if (amountFilterRef.current && !amountFilterRef.current(note.amount))
|
|
1362
|
+
continue;
|
|
1363
|
+
result.push(note);
|
|
1364
|
+
}
|
|
1365
|
+
result.sort((a, b) => a.firstSeenAt - b.firstSeenAt);
|
|
1366
|
+
return result;
|
|
1367
|
+
}, [allNotes, noteFirstSeen, excludeIdSet, sender, since, handledVersion]);
|
|
1368
|
+
const latest = useMemo5(
|
|
1369
|
+
() => streamedNotes.length > 0 ? streamedNotes[streamedNotes.length - 1] : null,
|
|
1370
|
+
[streamedNotes]
|
|
1371
|
+
);
|
|
1372
|
+
const markHandled = useCallback5((noteId) => {
|
|
1373
|
+
handledIdsRef.current = new Set(handledIdsRef.current).add(noteId);
|
|
1374
|
+
setHandledVersion((v) => v + 1);
|
|
1375
|
+
}, []);
|
|
1376
|
+
const markAllHandled = useCallback5(() => {
|
|
1377
|
+
const newSet = new Set(handledIdsRef.current);
|
|
1378
|
+
for (const note of streamedNotes) {
|
|
1379
|
+
newSet.add(note.id);
|
|
1380
|
+
}
|
|
1381
|
+
handledIdsRef.current = newSet;
|
|
1382
|
+
setHandledVersion((v) => v + 1);
|
|
1383
|
+
}, [streamedNotes]);
|
|
1384
|
+
const snapshot = useCallback5(() => {
|
|
1385
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1386
|
+
for (const note of streamedNotes) {
|
|
1387
|
+
ids.add(note.id);
|
|
1388
|
+
}
|
|
1389
|
+
return { ids, timestamp: Date.now() };
|
|
1390
|
+
}, [streamedNotes]);
|
|
1391
|
+
return {
|
|
1392
|
+
notes: streamedNotes,
|
|
1393
|
+
latest,
|
|
1394
|
+
markHandled,
|
|
1395
|
+
markAllHandled,
|
|
1396
|
+
snapshot,
|
|
1397
|
+
isLoading,
|
|
1398
|
+
error
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
function buildStreamedNote(record, noteFirstSeen) {
|
|
1402
|
+
try {
|
|
1403
|
+
const id = record.id().toString();
|
|
1404
|
+
const metadata = record.metadata?.();
|
|
1405
|
+
const senderHex = metadata?.sender?.()?.toString?.();
|
|
1406
|
+
const sender = senderHex ? toBech32AccountId(senderHex) : "";
|
|
1407
|
+
const assets = [];
|
|
1408
|
+
let primaryAmount = 0n;
|
|
1409
|
+
try {
|
|
1410
|
+
const details = record.details();
|
|
1411
|
+
const assetsList = details?.assets?.().fungibleAssets?.() ?? [];
|
|
1412
|
+
for (const asset of assetsList) {
|
|
1413
|
+
const assetId = asset.faucetId().toString();
|
|
1414
|
+
const amount = BigInt(asset.amount());
|
|
1415
|
+
assets.push({ assetId, amount });
|
|
1416
|
+
if (primaryAmount === 0n) {
|
|
1417
|
+
primaryAmount = amount;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
} catch {
|
|
1421
|
+
}
|
|
1422
|
+
const attachmentData = readNoteAttachment(record);
|
|
1423
|
+
const attachment = attachmentData ? attachmentData.values : null;
|
|
1424
|
+
const firstSeenAt = noteFirstSeen.get(id) ?? Date.now();
|
|
1425
|
+
return {
|
|
1426
|
+
id,
|
|
1427
|
+
sender,
|
|
1428
|
+
amount: primaryAmount,
|
|
1429
|
+
assets,
|
|
1430
|
+
record,
|
|
1431
|
+
firstSeenAt,
|
|
1432
|
+
attachment
|
|
1433
|
+
};
|
|
1434
|
+
} catch {
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// src/hooks/useTransactionHistory.ts
|
|
1440
|
+
import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo6, useState as useState5 } from "react";
|
|
1441
|
+
import { TransactionFilter as TransactionFilter2 } from "@miden-sdk/miden-sdk";
|
|
1442
|
+
function useTransactionHistory(options = {}) {
|
|
1443
|
+
const { client, isReady, runExclusive } = useMiden();
|
|
1444
|
+
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1445
|
+
const { lastSyncTime } = useSyncStateStore();
|
|
1446
|
+
const [records, setRecords] = useState5([]);
|
|
1447
|
+
const [isLoading, setIsLoading] = useState5(false);
|
|
1448
|
+
const [error, setError] = useState5(null);
|
|
1449
|
+
const rawIds = useMemo6(() => {
|
|
1050
1450
|
if (options.id) return [options.id];
|
|
1051
1451
|
if (options.ids && options.ids.length > 0) return options.ids;
|
|
1052
1452
|
return null;
|
|
1053
1453
|
}, [options.id, options.ids]);
|
|
1054
|
-
const idsHex =
|
|
1454
|
+
const idsHex = useMemo6(() => {
|
|
1055
1455
|
if (!rawIds) return null;
|
|
1056
1456
|
return rawIds.map(
|
|
1057
|
-
(id) =>
|
|
1457
|
+
(id) => normalizeHex2(typeof id === "string" ? id : id.toHex())
|
|
1058
1458
|
);
|
|
1059
1459
|
}, [rawIds]);
|
|
1060
1460
|
const filter = options.filter;
|
|
1061
1461
|
const refreshOnSync = options.refreshOnSync !== false;
|
|
1062
|
-
const refetch =
|
|
1462
|
+
const refetch = useCallback6(async () => {
|
|
1063
1463
|
if (!client || !isReady) return;
|
|
1064
1464
|
setIsLoading(true);
|
|
1065
1465
|
setError(null);
|
|
@@ -1073,7 +1473,7 @@ function useTransactionHistory(options = {}) {
|
|
|
1073
1473
|
() => client.getTransactions(resolvedFilter)
|
|
1074
1474
|
);
|
|
1075
1475
|
const filtered = localFilterHexes ? fetched.filter(
|
|
1076
|
-
(record2) => localFilterHexes.includes(
|
|
1476
|
+
(record2) => localFilterHexes.includes(normalizeHex2(record2.id().toHex()))
|
|
1077
1477
|
) : fetched;
|
|
1078
1478
|
setRecords(filtered);
|
|
1079
1479
|
} catch (err) {
|
|
@@ -1082,19 +1482,19 @@ function useTransactionHistory(options = {}) {
|
|
|
1082
1482
|
setIsLoading(false);
|
|
1083
1483
|
}
|
|
1084
1484
|
}, [client, isReady, runExclusiveSafe, filter, rawIds, idsHex]);
|
|
1085
|
-
|
|
1485
|
+
useEffect7(() => {
|
|
1086
1486
|
if (!isReady) return;
|
|
1087
1487
|
refetch();
|
|
1088
1488
|
}, [isReady, refetch]);
|
|
1089
|
-
|
|
1489
|
+
useEffect7(() => {
|
|
1090
1490
|
if (!isReady || !refreshOnSync || !lastSyncTime) return;
|
|
1091
1491
|
refetch();
|
|
1092
1492
|
}, [isReady, lastSyncTime, refreshOnSync, refetch]);
|
|
1093
|
-
const record =
|
|
1493
|
+
const record = useMemo6(() => {
|
|
1094
1494
|
if (!idsHex || idsHex.length !== 1) return null;
|
|
1095
|
-
return records.find((item) =>
|
|
1495
|
+
return records.find((item) => normalizeHex2(item.id().toHex()) === idsHex[0]) ?? null;
|
|
1096
1496
|
}, [records, idsHex]);
|
|
1097
|
-
const status =
|
|
1497
|
+
const status = useMemo6(() => {
|
|
1098
1498
|
if (!record) return null;
|
|
1099
1499
|
const current = record.transactionStatus();
|
|
1100
1500
|
if (current.isCommitted()) return "committed";
|
|
@@ -1116,29 +1516,29 @@ function buildFilter(filter, ids, idsHex) {
|
|
|
1116
1516
|
return { filter };
|
|
1117
1517
|
}
|
|
1118
1518
|
if (!ids || ids.length === 0) {
|
|
1119
|
-
return { filter:
|
|
1519
|
+
return { filter: TransactionFilter2.all() };
|
|
1120
1520
|
}
|
|
1121
1521
|
const allTransactionIds = ids.every((id) => typeof id !== "string");
|
|
1122
1522
|
if (allTransactionIds) {
|
|
1123
|
-
return { filter:
|
|
1523
|
+
return { filter: TransactionFilter2.ids(ids) };
|
|
1124
1524
|
}
|
|
1125
1525
|
return {
|
|
1126
|
-
filter:
|
|
1526
|
+
filter: TransactionFilter2.all(),
|
|
1127
1527
|
localFilterHexes: idsHex ?? []
|
|
1128
1528
|
};
|
|
1129
1529
|
}
|
|
1130
|
-
function
|
|
1530
|
+
function normalizeHex2(value) {
|
|
1131
1531
|
const trimmed = value.trim();
|
|
1132
1532
|
const normalized = trimmed.startsWith("0x") || trimmed.startsWith("0X") ? trimmed : `0x${trimmed}`;
|
|
1133
1533
|
return normalized.toLowerCase();
|
|
1134
1534
|
}
|
|
1135
1535
|
|
|
1136
1536
|
// src/hooks/useSyncState.ts
|
|
1137
|
-
import { useCallback as
|
|
1537
|
+
import { useCallback as useCallback7 } from "react";
|
|
1138
1538
|
function useSyncState() {
|
|
1139
1539
|
const { sync: triggerSync } = useMiden();
|
|
1140
1540
|
const syncState = useSyncStateStore();
|
|
1141
|
-
const sync =
|
|
1541
|
+
const sync = useCallback7(async () => {
|
|
1142
1542
|
await triggerSync();
|
|
1143
1543
|
}, [triggerSync]);
|
|
1144
1544
|
return {
|
|
@@ -1148,16 +1548,16 @@ function useSyncState() {
|
|
|
1148
1548
|
}
|
|
1149
1549
|
|
|
1150
1550
|
// src/hooks/useCreateWallet.ts
|
|
1151
|
-
import { useCallback as
|
|
1551
|
+
import { useCallback as useCallback8, useState as useState6 } from "react";
|
|
1152
1552
|
import { AccountStorageMode } from "@miden-sdk/miden-sdk";
|
|
1153
1553
|
function useCreateWallet() {
|
|
1154
1554
|
const { client, isReady, sync, runExclusive } = useMiden();
|
|
1155
1555
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1156
1556
|
const setAccounts = useMidenStore((state) => state.setAccounts);
|
|
1157
|
-
const [wallet, setWallet] =
|
|
1158
|
-
const [isCreating, setIsCreating] =
|
|
1159
|
-
const [error, setError] =
|
|
1160
|
-
const createWallet =
|
|
1557
|
+
const [wallet, setWallet] = useState6(null);
|
|
1558
|
+
const [isCreating, setIsCreating] = useState6(false);
|
|
1559
|
+
const [error, setError] = useState6(null);
|
|
1560
|
+
const createWallet = useCallback8(
|
|
1161
1561
|
async (options = {}) => {
|
|
1162
1562
|
if (!client || !isReady) {
|
|
1163
1563
|
throw new Error("Miden client is not ready");
|
|
@@ -1195,7 +1595,7 @@ function useCreateWallet() {
|
|
|
1195
1595
|
},
|
|
1196
1596
|
[client, isReady, runExclusive, setAccounts]
|
|
1197
1597
|
);
|
|
1198
|
-
const reset =
|
|
1598
|
+
const reset = useCallback8(() => {
|
|
1199
1599
|
setWallet(null);
|
|
1200
1600
|
setIsCreating(false);
|
|
1201
1601
|
setError(null);
|
|
@@ -1222,16 +1622,16 @@ function getStorageMode(mode) {
|
|
|
1222
1622
|
}
|
|
1223
1623
|
|
|
1224
1624
|
// src/hooks/useCreateFaucet.ts
|
|
1225
|
-
import { useCallback as
|
|
1625
|
+
import { useCallback as useCallback9, useState as useState7 } from "react";
|
|
1226
1626
|
import { AccountStorageMode as AccountStorageMode2 } from "@miden-sdk/miden-sdk";
|
|
1227
1627
|
function useCreateFaucet() {
|
|
1228
1628
|
const { client, isReady, runExclusive } = useMiden();
|
|
1229
1629
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1230
1630
|
const setAccounts = useMidenStore((state) => state.setAccounts);
|
|
1231
|
-
const [faucet, setFaucet] =
|
|
1232
|
-
const [isCreating, setIsCreating] =
|
|
1233
|
-
const [error, setError] =
|
|
1234
|
-
const createFaucet =
|
|
1631
|
+
const [faucet, setFaucet] = useState7(null);
|
|
1632
|
+
const [isCreating, setIsCreating] = useState7(false);
|
|
1633
|
+
const [error, setError] = useState7(null);
|
|
1634
|
+
const createFaucet = useCallback9(
|
|
1235
1635
|
async (options) => {
|
|
1236
1636
|
if (!client || !isReady) {
|
|
1237
1637
|
throw new Error("Miden client is not ready");
|
|
@@ -1270,7 +1670,7 @@ function useCreateFaucet() {
|
|
|
1270
1670
|
},
|
|
1271
1671
|
[client, isReady, runExclusive, setAccounts]
|
|
1272
1672
|
);
|
|
1273
|
-
const reset =
|
|
1673
|
+
const reset = useCallback9(() => {
|
|
1274
1674
|
setFaucet(null);
|
|
1275
1675
|
setIsCreating(false);
|
|
1276
1676
|
setError(null);
|
|
@@ -1297,16 +1697,16 @@ function getStorageMode2(mode) {
|
|
|
1297
1697
|
}
|
|
1298
1698
|
|
|
1299
1699
|
// src/hooks/useImportAccount.ts
|
|
1300
|
-
import { useCallback as
|
|
1700
|
+
import { useCallback as useCallback10, useState as useState8 } from "react";
|
|
1301
1701
|
import { AccountFile } from "@miden-sdk/miden-sdk";
|
|
1302
1702
|
function useImportAccount() {
|
|
1303
1703
|
const { client, isReady, runExclusive } = useMiden();
|
|
1304
1704
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1305
1705
|
const setAccounts = useMidenStore((state) => state.setAccounts);
|
|
1306
|
-
const [account, setAccount] =
|
|
1307
|
-
const [isImporting, setIsImporting] =
|
|
1308
|
-
const [error, setError] =
|
|
1309
|
-
const importAccount =
|
|
1706
|
+
const [account, setAccount] = useState8(null);
|
|
1707
|
+
const [isImporting, setIsImporting] = useState8(false);
|
|
1708
|
+
const [error, setError] = useState8(null);
|
|
1709
|
+
const importAccount = useCallback10(
|
|
1310
1710
|
async (options) => {
|
|
1311
1711
|
if (!client || !isReady) {
|
|
1312
1712
|
throw new Error("Miden client is not ready");
|
|
@@ -1401,7 +1801,7 @@ function useImportAccount() {
|
|
|
1401
1801
|
},
|
|
1402
1802
|
[client, isReady, runExclusive, setAccounts]
|
|
1403
1803
|
);
|
|
1404
|
-
const reset =
|
|
1804
|
+
const reset = useCallback10(() => {
|
|
1405
1805
|
setAccount(null);
|
|
1406
1806
|
setIsImporting(false);
|
|
1407
1807
|
setError(null);
|
|
@@ -1451,43 +1851,154 @@ function bytesEqual(left, right) {
|
|
|
1451
1851
|
}
|
|
1452
1852
|
|
|
1453
1853
|
// src/hooks/useSend.ts
|
|
1454
|
-
import { useCallback as
|
|
1455
|
-
import {
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1854
|
+
import { useCallback as useCallback11, useRef as useRef3, useState as useState9 } from "react";
|
|
1855
|
+
import {
|
|
1856
|
+
FungibleAsset,
|
|
1857
|
+
Note,
|
|
1858
|
+
NoteAssets,
|
|
1859
|
+
NoteType as NoteType2,
|
|
1860
|
+
OutputNote,
|
|
1861
|
+
OutputNoteArray,
|
|
1862
|
+
TransactionRequestBuilder
|
|
1863
|
+
} from "@miden-sdk/miden-sdk";
|
|
1864
|
+
|
|
1865
|
+
// src/utils/errors.ts
|
|
1866
|
+
var MidenError = class extends Error {
|
|
1867
|
+
constructor(message, options) {
|
|
1868
|
+
super(message);
|
|
1869
|
+
this.name = "MidenError";
|
|
1870
|
+
this.code = options?.code ?? "UNKNOWN";
|
|
1871
|
+
if (options?.cause !== void 0) {
|
|
1872
|
+
this.cause = options.cause;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
};
|
|
1876
|
+
var ERROR_PATTERNS = [
|
|
1877
|
+
{
|
|
1878
|
+
test: (msg) => msg.includes("_assertClass") || msg.includes("expected instance of"),
|
|
1879
|
+
code: "WASM_CLASS_MISMATCH",
|
|
1880
|
+
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."
|
|
1881
|
+
},
|
|
1882
|
+
{
|
|
1883
|
+
test: (msg) => msg.includes("null pointer") || msg.includes("already been freed") || msg.includes("dereferencing a null"),
|
|
1884
|
+
code: "WASM_POINTER_CONSUMED",
|
|
1885
|
+
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."
|
|
1886
|
+
},
|
|
1887
|
+
{
|
|
1888
|
+
test: (msg) => msg.includes("not initialized") || msg.includes("Cannot read properties of null"),
|
|
1889
|
+
code: "WASM_NOT_INITIALIZED",
|
|
1890
|
+
message: "Miden client is not initialized. Ensure you are inside a <MidenProvider> and the client is ready before calling SDK methods."
|
|
1891
|
+
},
|
|
1892
|
+
{
|
|
1893
|
+
test: (msg) => msg.includes("state commitment mismatch") || msg.includes("stale state"),
|
|
1894
|
+
code: "WASM_SYNC_REQUIRED",
|
|
1895
|
+
message: "Account state is stale. Call sync() before executing transactions, or ensure no concurrent transactions are running against the same account."
|
|
1896
|
+
}
|
|
1897
|
+
];
|
|
1898
|
+
function wrapWasmError(e) {
|
|
1899
|
+
if (e instanceof MidenError) return e;
|
|
1900
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1901
|
+
for (const pattern of ERROR_PATTERNS) {
|
|
1902
|
+
if (pattern.test(msg)) {
|
|
1903
|
+
return new MidenError(pattern.message, { cause: e, code: pattern.code });
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
if (e instanceof Error) return e;
|
|
1907
|
+
return new Error(msg);
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// src/hooks/useSend.ts
|
|
1911
|
+
function useSend() {
|
|
1912
|
+
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
1913
|
+
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1914
|
+
const isBusyRef = useRef3(false);
|
|
1915
|
+
const [result, setResult] = useState9(null);
|
|
1916
|
+
const [isLoading, setIsLoading] = useState9(false);
|
|
1917
|
+
const [stage, setStage] = useState9("idle");
|
|
1918
|
+
const [error, setError] = useState9(null);
|
|
1919
|
+
const send = useCallback11(
|
|
1920
|
+
async (options) => {
|
|
1921
|
+
if (!client || !isReady) {
|
|
1466
1922
|
throw new Error("Miden client is not ready");
|
|
1467
1923
|
}
|
|
1924
|
+
if (isBusyRef.current) {
|
|
1925
|
+
throw new MidenError(
|
|
1926
|
+
"A send is already in progress. Await the previous send before starting another.",
|
|
1927
|
+
{ code: "SEND_BUSY" }
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
isBusyRef.current = true;
|
|
1468
1931
|
setIsLoading(true);
|
|
1469
1932
|
setStage("executing");
|
|
1470
1933
|
setError(null);
|
|
1471
1934
|
try {
|
|
1935
|
+
if (!options.skipSync) {
|
|
1936
|
+
await sync();
|
|
1937
|
+
}
|
|
1472
1938
|
const noteType = getNoteType(options.noteType ?? DEFAULTS.NOTE_TYPE);
|
|
1473
|
-
|
|
1474
|
-
|
|
1939
|
+
let amount = options.amount;
|
|
1940
|
+
if (options.sendAll) {
|
|
1941
|
+
const resolvedAmount = await runExclusiveSafe(async () => {
|
|
1942
|
+
const fromId = parseAccountId(options.from);
|
|
1943
|
+
const account = await client.getAccount(fromId);
|
|
1944
|
+
if (!account) throw new Error("Account not found");
|
|
1945
|
+
const assetIdObj = parseAccountId(options.assetId);
|
|
1946
|
+
const balance = account.vault?.()?.getBalance?.(assetIdObj);
|
|
1947
|
+
if (balance === void 0 || balance === null) {
|
|
1948
|
+
throw new Error("Could not query account balance");
|
|
1949
|
+
}
|
|
1950
|
+
const bal = BigInt(balance);
|
|
1951
|
+
if (bal === 0n) {
|
|
1952
|
+
throw new Error("Account has zero balance for this asset");
|
|
1953
|
+
}
|
|
1954
|
+
return bal;
|
|
1955
|
+
});
|
|
1956
|
+
amount = resolvedAmount;
|
|
1957
|
+
}
|
|
1958
|
+
if (amount === void 0 || amount === null) {
|
|
1959
|
+
throw new Error("Amount is required (provide amount or sendAll)");
|
|
1960
|
+
}
|
|
1475
1961
|
const assetId = options.assetId ?? options.faucetId ?? null;
|
|
1476
1962
|
if (!assetId) {
|
|
1477
1963
|
throw new Error("Asset ID is required");
|
|
1478
1964
|
}
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
toAccountId,
|
|
1484
|
-
assetIdObj,
|
|
1485
|
-
noteType,
|
|
1486
|
-
options.amount,
|
|
1487
|
-
options.recallHeight ?? null,
|
|
1488
|
-
options.timelockHeight ?? null
|
|
1965
|
+
const hasAttachment = options.attachment !== void 0 && options.attachment !== null;
|
|
1966
|
+
if (hasAttachment && (options.recallHeight != null || options.timelockHeight != null)) {
|
|
1967
|
+
throw new Error(
|
|
1968
|
+
"recallHeight and timelockHeight are not supported when attachment is provided"
|
|
1489
1969
|
);
|
|
1490
|
-
|
|
1970
|
+
}
|
|
1971
|
+
const txResult = await runExclusiveSafe(async () => {
|
|
1972
|
+
const fromAccountId = parseAccountId(options.from);
|
|
1973
|
+
const toAccountId = parseAccountId(options.to);
|
|
1974
|
+
const assetIdObj = parseAccountId(assetId);
|
|
1975
|
+
let txRequest;
|
|
1976
|
+
if (hasAttachment) {
|
|
1977
|
+
const attachment = createNoteAttachment(options.attachment);
|
|
1978
|
+
const assets = new NoteAssets([
|
|
1979
|
+
new FungibleAsset(assetIdObj, amount)
|
|
1980
|
+
]);
|
|
1981
|
+
const note = Note.createP2IDNote(
|
|
1982
|
+
fromAccountId,
|
|
1983
|
+
toAccountId,
|
|
1984
|
+
assets,
|
|
1985
|
+
noteType,
|
|
1986
|
+
attachment
|
|
1987
|
+
);
|
|
1988
|
+
txRequest = new TransactionRequestBuilder().withOwnOutputNotes(new OutputNoteArray([OutputNote.full(note)])).build();
|
|
1989
|
+
} else {
|
|
1990
|
+
txRequest = client.newSendTransactionRequest(
|
|
1991
|
+
fromAccountId,
|
|
1992
|
+
toAccountId,
|
|
1993
|
+
assetIdObj,
|
|
1994
|
+
noteType,
|
|
1995
|
+
amount,
|
|
1996
|
+
options.recallHeight ?? null,
|
|
1997
|
+
options.timelockHeight ?? null
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
const execAccountId = parseAccountId(options.from);
|
|
2001
|
+
return await client.executeTransaction(execAccountId, txRequest);
|
|
1491
2002
|
});
|
|
1492
2003
|
setStage("proving");
|
|
1493
2004
|
const provenTransaction = await runExclusiveSafe(
|
|
@@ -1497,22 +2008,31 @@ function useSend() {
|
|
|
1497
2008
|
const submissionHeight = await runExclusiveSafe(
|
|
1498
2009
|
() => client.submitProvenTransaction(provenTransaction, txResult)
|
|
1499
2010
|
);
|
|
2011
|
+
const txIdHex = txResult.id().toHex();
|
|
2012
|
+
const txIdString = txResult.id().toString();
|
|
2013
|
+
let fullNote = null;
|
|
2014
|
+
if (noteType === NoteType2.Private) {
|
|
2015
|
+
fullNote = extractFullNote(txResult);
|
|
2016
|
+
}
|
|
1500
2017
|
await runExclusiveSafe(
|
|
1501
2018
|
() => client.applyTransaction(txResult, submissionHeight)
|
|
1502
2019
|
);
|
|
1503
|
-
|
|
1504
|
-
await waitForTransactionCommit(client, runExclusiveSafe, txId);
|
|
1505
|
-
if (noteType === NoteType.Private) {
|
|
1506
|
-
const fullNote = extractFullNote(txResult);
|
|
2020
|
+
if (noteType === NoteType2.Private) {
|
|
1507
2021
|
if (!fullNote) {
|
|
1508
2022
|
throw new Error("Missing full note for private send");
|
|
1509
2023
|
}
|
|
1510
|
-
|
|
2024
|
+
await waitForTransactionCommit(
|
|
2025
|
+
client,
|
|
2026
|
+
runExclusiveSafe,
|
|
2027
|
+
txIdHex
|
|
2028
|
+
);
|
|
2029
|
+
const recipientAccountId = parseAccountId(options.to);
|
|
2030
|
+
const recipientAddress = parseAddress(options.to, recipientAccountId);
|
|
1511
2031
|
await runExclusiveSafe(
|
|
1512
2032
|
() => client.sendPrivateNote(fullNote, recipientAddress)
|
|
1513
2033
|
);
|
|
1514
2034
|
}
|
|
1515
|
-
const txSummary = { transactionId:
|
|
2035
|
+
const txSummary = { transactionId: txIdString };
|
|
1516
2036
|
setStage("complete");
|
|
1517
2037
|
setResult(txSummary);
|
|
1518
2038
|
await sync();
|
|
@@ -1524,11 +2044,12 @@ function useSend() {
|
|
|
1524
2044
|
throw error2;
|
|
1525
2045
|
} finally {
|
|
1526
2046
|
setIsLoading(false);
|
|
2047
|
+
isBusyRef.current = false;
|
|
1527
2048
|
}
|
|
1528
2049
|
},
|
|
1529
2050
|
[client, isReady, prover, runExclusive, sync]
|
|
1530
2051
|
);
|
|
1531
|
-
const reset =
|
|
2052
|
+
const reset = useCallback11(() => {
|
|
1532
2053
|
setResult(null);
|
|
1533
2054
|
setIsLoading(false);
|
|
1534
2055
|
setStage("idle");
|
|
@@ -1543,39 +2064,6 @@ function useSend() {
|
|
|
1543
2064
|
reset
|
|
1544
2065
|
};
|
|
1545
2066
|
}
|
|
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
2067
|
function extractFullNote(txResult) {
|
|
1580
2068
|
try {
|
|
1581
2069
|
const executedTx = txResult.executedTransaction?.();
|
|
@@ -1588,26 +2076,26 @@ function extractFullNote(txResult) {
|
|
|
1588
2076
|
}
|
|
1589
2077
|
|
|
1590
2078
|
// src/hooks/useMultiSend.ts
|
|
1591
|
-
import { useCallback as
|
|
2079
|
+
import { useCallback as useCallback12, useRef as useRef4, useState as useState10 } from "react";
|
|
1592
2080
|
import {
|
|
1593
|
-
FungibleAsset,
|
|
1594
|
-
Note,
|
|
1595
|
-
NoteAssets,
|
|
1596
|
-
NoteAttachment,
|
|
1597
|
-
NoteType as
|
|
1598
|
-
OutputNote,
|
|
1599
|
-
OutputNoteArray,
|
|
1600
|
-
|
|
1601
|
-
TransactionRequestBuilder
|
|
2081
|
+
FungibleAsset as FungibleAsset2,
|
|
2082
|
+
Note as Note2,
|
|
2083
|
+
NoteAssets as NoteAssets2,
|
|
2084
|
+
NoteAttachment as NoteAttachment2,
|
|
2085
|
+
NoteType as NoteType3,
|
|
2086
|
+
OutputNote as OutputNote2,
|
|
2087
|
+
OutputNoteArray as OutputNoteArray2,
|
|
2088
|
+
TransactionRequestBuilder as TransactionRequestBuilder2
|
|
1602
2089
|
} from "@miden-sdk/miden-sdk";
|
|
1603
2090
|
function useMultiSend() {
|
|
1604
2091
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
1605
2092
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1606
|
-
const
|
|
1607
|
-
const [
|
|
1608
|
-
const [
|
|
1609
|
-
const [
|
|
1610
|
-
const
|
|
2093
|
+
const isBusyRef = useRef4(false);
|
|
2094
|
+
const [result, setResult] = useState10(null);
|
|
2095
|
+
const [isLoading, setIsLoading] = useState10(false);
|
|
2096
|
+
const [stage, setStage] = useState10("idle");
|
|
2097
|
+
const [error, setError] = useState10(null);
|
|
2098
|
+
const sendMany = useCallback12(
|
|
1611
2099
|
async (options) => {
|
|
1612
2100
|
if (!client || !isReady) {
|
|
1613
2101
|
throw new Error("Miden client is not ready");
|
|
@@ -1615,35 +2103,53 @@ function useMultiSend() {
|
|
|
1615
2103
|
if (options.recipients.length === 0) {
|
|
1616
2104
|
throw new Error("No recipients provided");
|
|
1617
2105
|
}
|
|
2106
|
+
if (isBusyRef.current) {
|
|
2107
|
+
throw new MidenError(
|
|
2108
|
+
"A send is already in progress. Await the previous send before starting another.",
|
|
2109
|
+
{ code: "SEND_BUSY" }
|
|
2110
|
+
);
|
|
2111
|
+
}
|
|
2112
|
+
isBusyRef.current = true;
|
|
1618
2113
|
setIsLoading(true);
|
|
1619
2114
|
setStage("executing");
|
|
1620
2115
|
setError(null);
|
|
1621
2116
|
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
|
-
|
|
2117
|
+
if (!options.skipSync) {
|
|
2118
|
+
await sync();
|
|
2119
|
+
}
|
|
2120
|
+
const noteType = getNoteType(options.noteType ?? DEFAULTS.NOTE_TYPE);
|
|
2121
|
+
const outputs = options.recipients.map(
|
|
2122
|
+
({ to, amount, attachment, noteType: recipientNoteType }) => {
|
|
2123
|
+
const iterSenderId = parseAccountId(options.from);
|
|
2124
|
+
const iterAssetId = parseAccountId(options.assetId);
|
|
2125
|
+
const receiverId = parseAccountId(to);
|
|
2126
|
+
const assets = new NoteAssets2([
|
|
2127
|
+
new FungibleAsset2(iterAssetId, amount)
|
|
2128
|
+
]);
|
|
2129
|
+
const resolvedNoteType = recipientNoteType ? getNoteType(recipientNoteType) : noteType;
|
|
2130
|
+
const noteAttachment = attachment !== void 0 && attachment !== null ? createNoteAttachment(attachment) : new NoteAttachment2();
|
|
2131
|
+
const note = Note2.createP2IDNote(
|
|
2132
|
+
iterSenderId,
|
|
2133
|
+
receiverId,
|
|
2134
|
+
assets,
|
|
2135
|
+
resolvedNoteType,
|
|
2136
|
+
noteAttachment
|
|
2137
|
+
);
|
|
2138
|
+
const recipientAddress = parseAddress(to, receiverId);
|
|
2139
|
+
return {
|
|
2140
|
+
outputNote: OutputNote2.full(note),
|
|
2141
|
+
note,
|
|
2142
|
+
recipientAddress,
|
|
2143
|
+
noteType: resolvedNoteType
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
);
|
|
2147
|
+
const txRequest = new TransactionRequestBuilder2().withOwnOutputNotes(
|
|
2148
|
+
new OutputNoteArray2(outputs.map((o) => o.outputNote))
|
|
1644
2149
|
).build();
|
|
2150
|
+
const txSenderId = parseAccountId(options.from);
|
|
1645
2151
|
const txResult = await runExclusiveSafe(
|
|
1646
|
-
() => client.executeTransaction(
|
|
2152
|
+
() => client.executeTransaction(txSenderId, txRequest)
|
|
1647
2153
|
);
|
|
1648
2154
|
setStage("proving");
|
|
1649
2155
|
const provenTransaction = await runExclusiveSafe(
|
|
@@ -1653,23 +2159,27 @@ function useMultiSend() {
|
|
|
1653
2159
|
const submissionHeight = await runExclusiveSafe(
|
|
1654
2160
|
() => client.submitProvenTransaction(provenTransaction, txResult)
|
|
1655
2161
|
);
|
|
2162
|
+
const txIdHex = txResult.id().toHex();
|
|
2163
|
+
const txIdString = txResult.id().toString();
|
|
1656
2164
|
await runExclusiveSafe(
|
|
1657
2165
|
() => client.applyTransaction(txResult, submissionHeight)
|
|
1658
2166
|
);
|
|
1659
|
-
const
|
|
1660
|
-
if (
|
|
1661
|
-
await
|
|
2167
|
+
const hasPrivate = outputs.some((o) => o.noteType === NoteType3.Private);
|
|
2168
|
+
if (hasPrivate) {
|
|
2169
|
+
await waitForTransactionCommit(
|
|
1662
2170
|
client,
|
|
1663
2171
|
runExclusiveSafe,
|
|
1664
|
-
|
|
2172
|
+
txIdHex
|
|
1665
2173
|
);
|
|
1666
2174
|
for (const output of outputs) {
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
2175
|
+
if (output.noteType === NoteType3.Private) {
|
|
2176
|
+
await runExclusiveSafe(
|
|
2177
|
+
() => client.sendPrivateNote(output.note, output.recipientAddress)
|
|
2178
|
+
);
|
|
2179
|
+
}
|
|
1670
2180
|
}
|
|
1671
2181
|
}
|
|
1672
|
-
const txSummary = { transactionId:
|
|
2182
|
+
const txSummary = { transactionId: txIdString };
|
|
1673
2183
|
setStage("complete");
|
|
1674
2184
|
setResult(txSummary);
|
|
1675
2185
|
await sync();
|
|
@@ -1681,11 +2191,12 @@ function useMultiSend() {
|
|
|
1681
2191
|
throw error2;
|
|
1682
2192
|
} finally {
|
|
1683
2193
|
setIsLoading(false);
|
|
2194
|
+
isBusyRef.current = false;
|
|
1684
2195
|
}
|
|
1685
2196
|
},
|
|
1686
2197
|
[client, isReady, prover, runExclusive, sync]
|
|
1687
2198
|
);
|
|
1688
|
-
const reset =
|
|
2199
|
+
const reset = useCallback12(() => {
|
|
1689
2200
|
setResult(null);
|
|
1690
2201
|
setIsLoading(false);
|
|
1691
2202
|
setStage("idle");
|
|
@@ -1700,82 +2211,48 @@ function useMultiSend() {
|
|
|
1700
2211
|
reset
|
|
1701
2212
|
};
|
|
1702
2213
|
}
|
|
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
2214
|
|
|
1737
2215
|
// src/hooks/useInternalTransfer.ts
|
|
1738
|
-
import { useCallback as
|
|
2216
|
+
import { useCallback as useCallback13, useState as useState11 } from "react";
|
|
1739
2217
|
import {
|
|
1740
|
-
FungibleAsset as
|
|
1741
|
-
Note as
|
|
2218
|
+
FungibleAsset as FungibleAsset3,
|
|
2219
|
+
Note as Note3,
|
|
1742
2220
|
NoteAndArgs,
|
|
1743
2221
|
NoteAndArgsArray,
|
|
1744
|
-
NoteAssets as
|
|
1745
|
-
NoteAttachment as
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
TransactionRequestBuilder as TransactionRequestBuilder2
|
|
2222
|
+
NoteAssets as NoteAssets3,
|
|
2223
|
+
NoteAttachment as NoteAttachment3,
|
|
2224
|
+
OutputNote as OutputNote3,
|
|
2225
|
+
OutputNoteArray as OutputNoteArray3,
|
|
2226
|
+
TransactionRequestBuilder as TransactionRequestBuilder3
|
|
1750
2227
|
} from "@miden-sdk/miden-sdk";
|
|
1751
2228
|
function useInternalTransfer() {
|
|
1752
2229
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
1753
2230
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1754
|
-
const [result, setResult] =
|
|
1755
|
-
const [isLoading, setIsLoading] =
|
|
1756
|
-
const [stage, setStage] =
|
|
1757
|
-
const [error, setError] =
|
|
1758
|
-
const transferOnce =
|
|
2231
|
+
const [result, setResult] = useState11(null);
|
|
2232
|
+
const [isLoading, setIsLoading] = useState11(false);
|
|
2233
|
+
const [stage, setStage] = useState11("idle");
|
|
2234
|
+
const [error, setError] = useState11(null);
|
|
2235
|
+
const transferOnce = useCallback13(
|
|
1759
2236
|
async (options) => {
|
|
1760
2237
|
if (!client || !isReady) {
|
|
1761
2238
|
throw new Error("Miden client is not ready");
|
|
1762
2239
|
}
|
|
1763
|
-
const noteType =
|
|
2240
|
+
const noteType = getNoteType(options.noteType ?? DEFAULTS.NOTE_TYPE);
|
|
1764
2241
|
const senderId = parseAccountId(options.from);
|
|
1765
2242
|
const receiverId = parseAccountId(options.to);
|
|
1766
2243
|
const assetId = parseAccountId(options.assetId);
|
|
1767
|
-
const assets = new
|
|
1768
|
-
new
|
|
2244
|
+
const assets = new NoteAssets3([
|
|
2245
|
+
new FungibleAsset3(assetId, options.amount)
|
|
1769
2246
|
]);
|
|
1770
|
-
const note =
|
|
2247
|
+
const note = Note3.createP2IDNote(
|
|
1771
2248
|
senderId,
|
|
1772
2249
|
receiverId,
|
|
1773
2250
|
assets,
|
|
1774
2251
|
noteType,
|
|
1775
|
-
new
|
|
2252
|
+
new NoteAttachment3()
|
|
1776
2253
|
);
|
|
1777
2254
|
const noteId = note.id().toString();
|
|
1778
|
-
const createRequest = new
|
|
2255
|
+
const createRequest = new TransactionRequestBuilder3().withOwnOutputNotes(new OutputNoteArray3([OutputNote3.full(note)])).build();
|
|
1779
2256
|
const createTxId = await runExclusiveSafe(
|
|
1780
2257
|
() => prover ? client.submitNewTransactionWithProver(
|
|
1781
2258
|
senderId,
|
|
@@ -1783,7 +2260,7 @@ function useInternalTransfer() {
|
|
|
1783
2260
|
prover
|
|
1784
2261
|
) : client.submitNewTransaction(senderId, createRequest)
|
|
1785
2262
|
);
|
|
1786
|
-
const consumeRequest = new
|
|
2263
|
+
const consumeRequest = new TransactionRequestBuilder3().withInputNotes(new NoteAndArgsArray([new NoteAndArgs(note, null)])).build();
|
|
1787
2264
|
const consumeTxId = await runExclusiveSafe(
|
|
1788
2265
|
() => prover ? client.submitNewTransactionWithProver(
|
|
1789
2266
|
receiverId,
|
|
@@ -1799,7 +2276,7 @@ function useInternalTransfer() {
|
|
|
1799
2276
|
},
|
|
1800
2277
|
[client, isReady, prover, runExclusiveSafe]
|
|
1801
2278
|
);
|
|
1802
|
-
const transfer =
|
|
2279
|
+
const transfer = useCallback13(
|
|
1803
2280
|
async (options) => {
|
|
1804
2281
|
if (!client || !isReady) {
|
|
1805
2282
|
throw new Error("Miden client is not ready");
|
|
@@ -1825,7 +2302,7 @@ function useInternalTransfer() {
|
|
|
1825
2302
|
},
|
|
1826
2303
|
[client, isReady, sync, transferOnce]
|
|
1827
2304
|
);
|
|
1828
|
-
const transferChain =
|
|
2305
|
+
const transferChain = useCallback13(
|
|
1829
2306
|
async (options) => {
|
|
1830
2307
|
if (!client || !isReady) {
|
|
1831
2308
|
throw new Error("Miden client is not ready");
|
|
@@ -1866,7 +2343,7 @@ function useInternalTransfer() {
|
|
|
1866
2343
|
},
|
|
1867
2344
|
[client, isReady, sync, transferOnce]
|
|
1868
2345
|
);
|
|
1869
|
-
const reset =
|
|
2346
|
+
const reset = useCallback13(() => {
|
|
1870
2347
|
setResult(null);
|
|
1871
2348
|
setIsLoading(false);
|
|
1872
2349
|
setStage("idle");
|
|
@@ -1882,47 +2359,35 @@ function useInternalTransfer() {
|
|
|
1882
2359
|
reset
|
|
1883
2360
|
};
|
|
1884
2361
|
}
|
|
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
2362
|
|
|
1898
2363
|
// src/hooks/useWaitForCommit.ts
|
|
1899
|
-
import { useCallback as
|
|
1900
|
-
import { TransactionFilter as
|
|
2364
|
+
import { useCallback as useCallback14 } from "react";
|
|
2365
|
+
import { TransactionFilter as TransactionFilter3 } from "@miden-sdk/miden-sdk";
|
|
1901
2366
|
function useWaitForCommit() {
|
|
1902
2367
|
const { client, isReady, runExclusive } = useMiden();
|
|
1903
2368
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1904
|
-
const waitForCommit =
|
|
2369
|
+
const waitForCommit = useCallback14(
|
|
1905
2370
|
async (txId, options) => {
|
|
1906
2371
|
if (!client || !isReady) {
|
|
1907
2372
|
throw new Error("Miden client is not ready");
|
|
1908
2373
|
}
|
|
1909
2374
|
const timeoutMs = Math.max(0, options?.timeoutMs ?? 1e4);
|
|
1910
2375
|
const intervalMs = Math.max(1, options?.intervalMs ?? 1e3);
|
|
1911
|
-
const targetHex =
|
|
2376
|
+
const targetHex = normalizeHex3(
|
|
1912
2377
|
typeof txId === "string" ? txId : txId.toHex()
|
|
1913
2378
|
);
|
|
1914
|
-
|
|
1915
|
-
while (
|
|
2379
|
+
const deadline = Date.now() + timeoutMs;
|
|
2380
|
+
while (Date.now() < deadline) {
|
|
1916
2381
|
await runExclusiveSafe(
|
|
1917
2382
|
() => client.syncState()
|
|
1918
2383
|
);
|
|
1919
2384
|
const records = await runExclusiveSafe(
|
|
1920
2385
|
() => client.getTransactions(
|
|
1921
|
-
typeof txId === "string" ?
|
|
2386
|
+
typeof txId === "string" ? TransactionFilter3.all() : TransactionFilter3.ids([txId])
|
|
1922
2387
|
)
|
|
1923
2388
|
);
|
|
1924
2389
|
const record = records.find(
|
|
1925
|
-
(item) =>
|
|
2390
|
+
(item) => normalizeHex3(item.id().toHex()) === targetHex
|
|
1926
2391
|
);
|
|
1927
2392
|
if (record) {
|
|
1928
2393
|
const status = record.transactionStatus();
|
|
@@ -1934,7 +2399,6 @@ function useWaitForCommit() {
|
|
|
1934
2399
|
}
|
|
1935
2400
|
}
|
|
1936
2401
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1937
|
-
waited += intervalMs;
|
|
1938
2402
|
}
|
|
1939
2403
|
throw new Error("Timeout waiting for transaction commit");
|
|
1940
2404
|
},
|
|
@@ -1942,18 +2406,18 @@ function useWaitForCommit() {
|
|
|
1942
2406
|
);
|
|
1943
2407
|
return { waitForCommit };
|
|
1944
2408
|
}
|
|
1945
|
-
function
|
|
2409
|
+
function normalizeHex3(value) {
|
|
1946
2410
|
const trimmed = value.trim();
|
|
1947
2411
|
const normalized = trimmed.startsWith("0x") || trimmed.startsWith("0X") ? trimmed : `0x${trimmed}`;
|
|
1948
2412
|
return normalized.toLowerCase();
|
|
1949
2413
|
}
|
|
1950
2414
|
|
|
1951
2415
|
// src/hooks/useWaitForNotes.ts
|
|
1952
|
-
import { useCallback as
|
|
2416
|
+
import { useCallback as useCallback15 } from "react";
|
|
1953
2417
|
function useWaitForNotes() {
|
|
1954
2418
|
const { client, isReady, runExclusive } = useMiden();
|
|
1955
2419
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1956
|
-
const waitForConsumableNotes =
|
|
2420
|
+
const waitForConsumableNotes = useCallback15(
|
|
1957
2421
|
async (options) => {
|
|
1958
2422
|
if (!client || !isReady) {
|
|
1959
2423
|
throw new Error("Miden client is not ready");
|
|
@@ -1964,7 +2428,9 @@ function useWaitForNotes() {
|
|
|
1964
2428
|
const accountId = parseAccountId(options.accountId);
|
|
1965
2429
|
let waited = 0;
|
|
1966
2430
|
while (waited < timeoutMs) {
|
|
1967
|
-
await runExclusiveSafe(
|
|
2431
|
+
await runExclusiveSafe(
|
|
2432
|
+
() => client.syncState()
|
|
2433
|
+
);
|
|
1968
2434
|
const notes = await runExclusiveSafe(
|
|
1969
2435
|
() => client.getConsumableNotes(accountId)
|
|
1970
2436
|
);
|
|
@@ -1982,16 +2448,15 @@ function useWaitForNotes() {
|
|
|
1982
2448
|
}
|
|
1983
2449
|
|
|
1984
2450
|
// src/hooks/useMint.ts
|
|
1985
|
-
import { useCallback as
|
|
1986
|
-
import { NoteType as NoteType4 } from "@miden-sdk/miden-sdk";
|
|
2451
|
+
import { useCallback as useCallback16, useState as useState12 } from "react";
|
|
1987
2452
|
function useMint() {
|
|
1988
2453
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
1989
2454
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
1990
|
-
const [result, setResult] =
|
|
1991
|
-
const [isLoading, setIsLoading] =
|
|
1992
|
-
const [stage, setStage] =
|
|
1993
|
-
const [error, setError] =
|
|
1994
|
-
const mint =
|
|
2455
|
+
const [result, setResult] = useState12(null);
|
|
2456
|
+
const [isLoading, setIsLoading] = useState12(false);
|
|
2457
|
+
const [stage, setStage] = useState12("idle");
|
|
2458
|
+
const [error, setError] = useState12(null);
|
|
2459
|
+
const mint = useCallback16(
|
|
1995
2460
|
async (options) => {
|
|
1996
2461
|
if (!client || !isReady) {
|
|
1997
2462
|
throw new Error("Miden client is not ready");
|
|
@@ -2000,7 +2465,7 @@ function useMint() {
|
|
|
2000
2465
|
setStage("executing");
|
|
2001
2466
|
setError(null);
|
|
2002
2467
|
try {
|
|
2003
|
-
const noteType =
|
|
2468
|
+
const noteType = getNoteType(options.noteType ?? DEFAULTS.NOTE_TYPE);
|
|
2004
2469
|
const targetAccountIdObj = parseAccountId(options.targetAccountId);
|
|
2005
2470
|
const faucetIdObj = parseAccountId(options.faucetId);
|
|
2006
2471
|
setStage("proving");
|
|
@@ -2033,7 +2498,7 @@ function useMint() {
|
|
|
2033
2498
|
},
|
|
2034
2499
|
[client, isReady, prover, runExclusive, sync]
|
|
2035
2500
|
);
|
|
2036
|
-
const reset =
|
|
2501
|
+
const reset = useCallback16(() => {
|
|
2037
2502
|
setResult(null);
|
|
2038
2503
|
setIsLoading(false);
|
|
2039
2504
|
setStage("idle");
|
|
@@ -2048,30 +2513,18 @@ function useMint() {
|
|
|
2048
2513
|
reset
|
|
2049
2514
|
};
|
|
2050
2515
|
}
|
|
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
2516
|
|
|
2064
2517
|
// src/hooks/useConsume.ts
|
|
2065
|
-
import { useCallback as
|
|
2066
|
-
import { NoteFilter as
|
|
2518
|
+
import { useCallback as useCallback17, useState as useState13 } from "react";
|
|
2519
|
+
import { NoteFilter as NoteFilter3, NoteFilterTypes as NoteFilterTypes2, NoteId } from "@miden-sdk/miden-sdk";
|
|
2067
2520
|
function useConsume() {
|
|
2068
2521
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
2069
2522
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
2070
|
-
const [result, setResult] =
|
|
2071
|
-
const [isLoading, setIsLoading] =
|
|
2072
|
-
const [stage, setStage] =
|
|
2073
|
-
const [error, setError] =
|
|
2074
|
-
const consume =
|
|
2523
|
+
const [result, setResult] = useState13(null);
|
|
2524
|
+
const [isLoading, setIsLoading] = useState13(false);
|
|
2525
|
+
const [stage, setStage] = useState13("idle");
|
|
2526
|
+
const [error, setError] = useState13(null);
|
|
2527
|
+
const consume = useCallback17(
|
|
2075
2528
|
async (options) => {
|
|
2076
2529
|
if (!client || !isReady) {
|
|
2077
2530
|
throw new Error("Miden client is not ready");
|
|
@@ -2089,7 +2542,7 @@ function useConsume() {
|
|
|
2089
2542
|
const noteIds = options.noteIds.map(
|
|
2090
2543
|
(noteId) => NoteId.fromHex(noteId)
|
|
2091
2544
|
);
|
|
2092
|
-
const filter = new
|
|
2545
|
+
const filter = new NoteFilter3(NoteFilterTypes2.List, noteIds);
|
|
2093
2546
|
const noteRecords = await client.getInputNotes(filter);
|
|
2094
2547
|
const notes = noteRecords.map((record) => record.toNote());
|
|
2095
2548
|
if (notes.length === 0) {
|
|
@@ -2121,7 +2574,7 @@ function useConsume() {
|
|
|
2121
2574
|
},
|
|
2122
2575
|
[client, isReady, prover, runExclusive, sync]
|
|
2123
2576
|
);
|
|
2124
|
-
const reset =
|
|
2577
|
+
const reset = useCallback17(() => {
|
|
2125
2578
|
setResult(null);
|
|
2126
2579
|
setIsLoading(false);
|
|
2127
2580
|
setStage("idle");
|
|
@@ -2138,16 +2591,15 @@ function useConsume() {
|
|
|
2138
2591
|
}
|
|
2139
2592
|
|
|
2140
2593
|
// src/hooks/useSwap.ts
|
|
2141
|
-
import { useCallback as
|
|
2142
|
-
import { NoteType as NoteType5 } from "@miden-sdk/miden-sdk";
|
|
2594
|
+
import { useCallback as useCallback18, useState as useState14 } from "react";
|
|
2143
2595
|
function useSwap() {
|
|
2144
2596
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
2145
2597
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
2146
|
-
const [result, setResult] =
|
|
2147
|
-
const [isLoading, setIsLoading] =
|
|
2148
|
-
const [stage, setStage] =
|
|
2149
|
-
const [error, setError] =
|
|
2150
|
-
const swap =
|
|
2598
|
+
const [result, setResult] = useState14(null);
|
|
2599
|
+
const [isLoading, setIsLoading] = useState14(false);
|
|
2600
|
+
const [stage, setStage] = useState14("idle");
|
|
2601
|
+
const [error, setError] = useState14(null);
|
|
2602
|
+
const swap = useCallback18(
|
|
2151
2603
|
async (options) => {
|
|
2152
2604
|
if (!client || !isReady) {
|
|
2153
2605
|
throw new Error("Miden client is not ready");
|
|
@@ -2156,8 +2608,8 @@ function useSwap() {
|
|
|
2156
2608
|
setStage("executing");
|
|
2157
2609
|
setError(null);
|
|
2158
2610
|
try {
|
|
2159
|
-
const noteType =
|
|
2160
|
-
const paybackNoteType =
|
|
2611
|
+
const noteType = getNoteType(options.noteType ?? DEFAULTS.NOTE_TYPE);
|
|
2612
|
+
const paybackNoteType = getNoteType(
|
|
2161
2613
|
options.paybackNoteType ?? DEFAULTS.NOTE_TYPE
|
|
2162
2614
|
);
|
|
2163
2615
|
const accountIdObj = parseAccountId(options.accountId);
|
|
@@ -2196,7 +2648,7 @@ function useSwap() {
|
|
|
2196
2648
|
},
|
|
2197
2649
|
[client, isReady, prover, runExclusive, sync]
|
|
2198
2650
|
);
|
|
2199
|
-
const reset =
|
|
2651
|
+
const reset = useCallback18(() => {
|
|
2200
2652
|
setResult(null);
|
|
2201
2653
|
setIsLoading(false);
|
|
2202
2654
|
setStage("idle");
|
|
@@ -2211,41 +2663,40 @@ function useSwap() {
|
|
|
2211
2663
|
reset
|
|
2212
2664
|
};
|
|
2213
2665
|
}
|
|
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
2666
|
|
|
2227
2667
|
// src/hooks/useTransaction.ts
|
|
2228
|
-
import { useCallback as
|
|
2668
|
+
import { useCallback as useCallback19, useRef as useRef5, useState as useState15 } from "react";
|
|
2229
2669
|
function useTransaction() {
|
|
2230
2670
|
const { client, isReady, sync, runExclusive, prover } = useMiden();
|
|
2231
2671
|
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
2232
|
-
const
|
|
2233
|
-
const [
|
|
2234
|
-
const [
|
|
2235
|
-
const [
|
|
2236
|
-
const
|
|
2672
|
+
const isBusyRef = useRef5(false);
|
|
2673
|
+
const [result, setResult] = useState15(null);
|
|
2674
|
+
const [isLoading, setIsLoading] = useState15(false);
|
|
2675
|
+
const [stage, setStage] = useState15("idle");
|
|
2676
|
+
const [error, setError] = useState15(null);
|
|
2677
|
+
const execute = useCallback19(
|
|
2237
2678
|
async (options) => {
|
|
2238
2679
|
if (!client || !isReady) {
|
|
2239
2680
|
throw new Error("Miden client is not ready");
|
|
2240
2681
|
}
|
|
2682
|
+
if (isBusyRef.current) {
|
|
2683
|
+
throw new MidenError(
|
|
2684
|
+
"A transaction is already in progress. Await the previous transaction before starting another.",
|
|
2685
|
+
{ code: "SEND_BUSY" }
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2688
|
+
isBusyRef.current = true;
|
|
2241
2689
|
setIsLoading(true);
|
|
2242
2690
|
setStage("executing");
|
|
2243
2691
|
setError(null);
|
|
2244
2692
|
try {
|
|
2693
|
+
if (!options.skipSync) {
|
|
2694
|
+
await sync();
|
|
2695
|
+
}
|
|
2696
|
+
const txRequest = await resolveRequest(options.request, client);
|
|
2245
2697
|
setStage("proving");
|
|
2246
2698
|
const txResult = await runExclusiveSafe(async () => {
|
|
2247
2699
|
const accountIdObj = resolveAccountId2(options.accountId);
|
|
2248
|
-
const txRequest = await resolveRequest(options.request, client);
|
|
2249
2700
|
const txId = prover ? await client.submitNewTransactionWithProver(
|
|
2250
2701
|
accountIdObj,
|
|
2251
2702
|
txRequest,
|
|
@@ -2264,11 +2715,12 @@ function useTransaction() {
|
|
|
2264
2715
|
throw error2;
|
|
2265
2716
|
} finally {
|
|
2266
2717
|
setIsLoading(false);
|
|
2718
|
+
isBusyRef.current = false;
|
|
2267
2719
|
}
|
|
2268
2720
|
},
|
|
2269
2721
|
[client, isReady, prover, runExclusive, sync]
|
|
2270
2722
|
);
|
|
2271
|
-
const reset =
|
|
2723
|
+
const reset = useCallback19(() => {
|
|
2272
2724
|
setResult(null);
|
|
2273
2725
|
setIsLoading(false);
|
|
2274
2726
|
setStage("idle");
|
|
@@ -2293,16 +2745,337 @@ async function resolveRequest(request, client) {
|
|
|
2293
2745
|
return request;
|
|
2294
2746
|
}
|
|
2295
2747
|
|
|
2748
|
+
// src/hooks/useSessionAccount.ts
|
|
2749
|
+
import { useCallback as useCallback20, useEffect as useEffect8, useRef as useRef6, useState as useState16 } from "react";
|
|
2750
|
+
import { AccountStorageMode as AccountStorageMode3 } from "@miden-sdk/miden-sdk";
|
|
2751
|
+
function useSessionAccount(options) {
|
|
2752
|
+
const { client, isReady, sync, runExclusive } = useMiden();
|
|
2753
|
+
const runExclusiveSafe = runExclusive ?? runExclusiveDirect;
|
|
2754
|
+
const setAccounts = useMidenStore((state) => state.setAccounts);
|
|
2755
|
+
const [sessionAccountId, setSessionAccountId] = useState16(null);
|
|
2756
|
+
const [step, setStep] = useState16("idle");
|
|
2757
|
+
const [error, setError] = useState16(null);
|
|
2758
|
+
const cancelledRef = useRef6(false);
|
|
2759
|
+
const isBusyRef = useRef6(false);
|
|
2760
|
+
const storagePrefix = options.storagePrefix ?? "miden-session";
|
|
2761
|
+
const pollIntervalMs = options.pollIntervalMs ?? 3e3;
|
|
2762
|
+
const maxWaitMs = options.maxWaitMs ?? 6e4;
|
|
2763
|
+
const storageMode = options.walletOptions?.storageMode ?? "public";
|
|
2764
|
+
const mutable = options.walletOptions?.mutable ?? DEFAULTS.WALLET_MUTABLE;
|
|
2765
|
+
const authScheme = options.walletOptions?.authScheme ?? DEFAULTS.AUTH_SCHEME;
|
|
2766
|
+
const fundRef = useRef6(options.fund);
|
|
2767
|
+
fundRef.current = options.fund;
|
|
2768
|
+
useEffect8(() => {
|
|
2769
|
+
try {
|
|
2770
|
+
const stored = localStorage.getItem(`${storagePrefix}:accountId`);
|
|
2771
|
+
const storedReady = localStorage.getItem(`${storagePrefix}:ready`);
|
|
2772
|
+
if (stored && stored.length > 0) {
|
|
2773
|
+
const validationId = parseAccountId(stored);
|
|
2774
|
+
validationId?.free?.();
|
|
2775
|
+
setSessionAccountId(stored);
|
|
2776
|
+
if (storedReady === "true") {
|
|
2777
|
+
setStep("ready");
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
} catch {
|
|
2781
|
+
localStorage.removeItem(`${storagePrefix}:accountId`);
|
|
2782
|
+
localStorage.removeItem(`${storagePrefix}:ready`);
|
|
2783
|
+
}
|
|
2784
|
+
}, [storagePrefix]);
|
|
2785
|
+
const initialize = useCallback20(async () => {
|
|
2786
|
+
if (!client || !isReady) {
|
|
2787
|
+
throw new Error("Miden client is not ready");
|
|
2788
|
+
}
|
|
2789
|
+
if (isBusyRef.current) {
|
|
2790
|
+
throw new MidenError(
|
|
2791
|
+
"Session account initialization is already in progress.",
|
|
2792
|
+
{ code: "OPERATION_BUSY" }
|
|
2793
|
+
);
|
|
2794
|
+
}
|
|
2795
|
+
isBusyRef.current = true;
|
|
2796
|
+
cancelledRef.current = false;
|
|
2797
|
+
setError(null);
|
|
2798
|
+
try {
|
|
2799
|
+
let walletId = sessionAccountId;
|
|
2800
|
+
if (!walletId) {
|
|
2801
|
+
setStep("creating");
|
|
2802
|
+
const resolvedStorageMode = getStorageMode3(storageMode);
|
|
2803
|
+
const wallet = await runExclusiveSafe(async () => {
|
|
2804
|
+
const w = await client.newWallet(
|
|
2805
|
+
resolvedStorageMode,
|
|
2806
|
+
mutable,
|
|
2807
|
+
authScheme
|
|
2808
|
+
);
|
|
2809
|
+
ensureAccountBech32(w);
|
|
2810
|
+
const accounts = await client.getAccounts();
|
|
2811
|
+
setAccounts(accounts);
|
|
2812
|
+
return w;
|
|
2813
|
+
});
|
|
2814
|
+
if (cancelledRef.current) return;
|
|
2815
|
+
walletId = wallet.id().toString();
|
|
2816
|
+
setSessionAccountId(walletId);
|
|
2817
|
+
localStorage.setItem(`${storagePrefix}:accountId`, walletId);
|
|
2818
|
+
}
|
|
2819
|
+
setStep("funding");
|
|
2820
|
+
await fundRef.current(walletId);
|
|
2821
|
+
if (cancelledRef.current) return;
|
|
2822
|
+
setStep("consuming");
|
|
2823
|
+
await waitAndConsume(
|
|
2824
|
+
client,
|
|
2825
|
+
runExclusiveSafe,
|
|
2826
|
+
walletId,
|
|
2827
|
+
pollIntervalMs,
|
|
2828
|
+
maxWaitMs,
|
|
2829
|
+
cancelledRef
|
|
2830
|
+
);
|
|
2831
|
+
if (cancelledRef.current) return;
|
|
2832
|
+
setStep("ready");
|
|
2833
|
+
localStorage.setItem(`${storagePrefix}:ready`, "true");
|
|
2834
|
+
await sync();
|
|
2835
|
+
} catch (err) {
|
|
2836
|
+
if (!cancelledRef.current) {
|
|
2837
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
2838
|
+
setError(error2);
|
|
2839
|
+
setStep("idle");
|
|
2840
|
+
throw error2;
|
|
2841
|
+
}
|
|
2842
|
+
} finally {
|
|
2843
|
+
isBusyRef.current = false;
|
|
2844
|
+
}
|
|
2845
|
+
}, [
|
|
2846
|
+
client,
|
|
2847
|
+
isReady,
|
|
2848
|
+
sync,
|
|
2849
|
+
runExclusiveSafe,
|
|
2850
|
+
sessionAccountId,
|
|
2851
|
+
storageMode,
|
|
2852
|
+
mutable,
|
|
2853
|
+
authScheme,
|
|
2854
|
+
storagePrefix,
|
|
2855
|
+
pollIntervalMs,
|
|
2856
|
+
maxWaitMs,
|
|
2857
|
+
setAccounts
|
|
2858
|
+
]);
|
|
2859
|
+
const reset = useCallback20(() => {
|
|
2860
|
+
cancelledRef.current = true;
|
|
2861
|
+
isBusyRef.current = false;
|
|
2862
|
+
setSessionAccountId(null);
|
|
2863
|
+
setStep("idle");
|
|
2864
|
+
setError(null);
|
|
2865
|
+
localStorage.removeItem(`${storagePrefix}:accountId`);
|
|
2866
|
+
localStorage.removeItem(`${storagePrefix}:ready`);
|
|
2867
|
+
}, [storagePrefix]);
|
|
2868
|
+
return {
|
|
2869
|
+
initialize,
|
|
2870
|
+
sessionAccountId,
|
|
2871
|
+
isReady: step === "ready",
|
|
2872
|
+
step,
|
|
2873
|
+
error,
|
|
2874
|
+
reset
|
|
2875
|
+
};
|
|
2876
|
+
}
|
|
2877
|
+
function getStorageMode3(mode) {
|
|
2878
|
+
switch (mode) {
|
|
2879
|
+
case "private":
|
|
2880
|
+
return AccountStorageMode3.private();
|
|
2881
|
+
case "public":
|
|
2882
|
+
return AccountStorageMode3.public();
|
|
2883
|
+
default:
|
|
2884
|
+
return AccountStorageMode3.public();
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
async function waitAndConsume(client, runExclusiveSafe, walletId, pollIntervalMs, maxWaitMs, cancelledRef) {
|
|
2888
|
+
const deadline = Date.now() + maxWaitMs;
|
|
2889
|
+
while (Date.now() < deadline) {
|
|
2890
|
+
if (cancelledRef.current) return;
|
|
2891
|
+
await runExclusiveSafe(
|
|
2892
|
+
() => client.syncState()
|
|
2893
|
+
);
|
|
2894
|
+
if (cancelledRef.current) return;
|
|
2895
|
+
const consumed = await runExclusiveSafe(async () => {
|
|
2896
|
+
const accountIdObj = parseAccountId(walletId);
|
|
2897
|
+
const consumable = await client.getConsumableNotes(accountIdObj);
|
|
2898
|
+
if (consumable.length === 0) return false;
|
|
2899
|
+
const notes = consumable.map((c) => c.inputNoteRecord().toNote());
|
|
2900
|
+
const txRequest = client.newConsumeTransactionRequest(notes);
|
|
2901
|
+
const freshAccountId = parseAccountId(walletId);
|
|
2902
|
+
await client.submitNewTransaction(freshAccountId, txRequest);
|
|
2903
|
+
return true;
|
|
2904
|
+
});
|
|
2905
|
+
if (consumed) return;
|
|
2906
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
2907
|
+
}
|
|
2908
|
+
throw new Error("Timeout waiting for session wallet funding");
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
// src/utils/bytes.ts
|
|
2912
|
+
function bytesToBigInt(bytes) {
|
|
2913
|
+
let result = 0n;
|
|
2914
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2915
|
+
result = result << 8n | BigInt(bytes[i]);
|
|
2916
|
+
}
|
|
2917
|
+
return result;
|
|
2918
|
+
}
|
|
2919
|
+
function bigIntToBytes(value, length) {
|
|
2920
|
+
if (value < 0n) {
|
|
2921
|
+
throw new RangeError("bigIntToBytes: value must be non-negative");
|
|
2922
|
+
}
|
|
2923
|
+
const maxValue = (1n << BigInt(length * 8)) - 1n;
|
|
2924
|
+
if (value > maxValue) {
|
|
2925
|
+
throw new RangeError(
|
|
2926
|
+
`bigIntToBytes: value ${value} does not fit in ${length} byte(s) (max ${maxValue})`
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
const bytes = new Uint8Array(length);
|
|
2930
|
+
let remaining = value;
|
|
2931
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
2932
|
+
bytes[i] = Number(remaining & 0xffn);
|
|
2933
|
+
remaining >>= 8n;
|
|
2934
|
+
}
|
|
2935
|
+
return bytes;
|
|
2936
|
+
}
|
|
2937
|
+
function concatBytes(...arrays) {
|
|
2938
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
2939
|
+
const result = new Uint8Array(totalLength);
|
|
2940
|
+
let offset = 0;
|
|
2941
|
+
for (const arr of arrays) {
|
|
2942
|
+
result.set(arr, offset);
|
|
2943
|
+
offset += arr.length;
|
|
2944
|
+
}
|
|
2945
|
+
return result;
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
// src/utils/walletDetection.ts
|
|
2949
|
+
async function waitForWalletDetection(adapter, timeoutMs = 5e3) {
|
|
2950
|
+
if (adapter.readyState === "Installed") return;
|
|
2951
|
+
return new Promise((resolve, reject) => {
|
|
2952
|
+
let settled = false;
|
|
2953
|
+
const settle = () => {
|
|
2954
|
+
if (settled) return;
|
|
2955
|
+
settled = true;
|
|
2956
|
+
clearTimeout(timer);
|
|
2957
|
+
adapter.off("readyStateChange", onReady);
|
|
2958
|
+
resolve();
|
|
2959
|
+
};
|
|
2960
|
+
const timer = setTimeout(() => {
|
|
2961
|
+
if (settled) return;
|
|
2962
|
+
settled = true;
|
|
2963
|
+
adapter.off("readyStateChange", onReady);
|
|
2964
|
+
reject(
|
|
2965
|
+
new Error(
|
|
2966
|
+
`Wallet extension not detected within ${timeoutMs}ms. Is the browser extension installed and enabled?`
|
|
2967
|
+
)
|
|
2968
|
+
);
|
|
2969
|
+
}, timeoutMs);
|
|
2970
|
+
const onReady = (state) => {
|
|
2971
|
+
if (state === "Installed") settle();
|
|
2972
|
+
};
|
|
2973
|
+
adapter.on("readyStateChange", onReady);
|
|
2974
|
+
if (adapter.readyState === "Installed") settle();
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
// src/utils/storage.ts
|
|
2979
|
+
async function migrateStorage(options) {
|
|
2980
|
+
if (typeof window === "undefined") return false;
|
|
2981
|
+
const versionKey = options.versionKey ?? "miden:storageVersion";
|
|
2982
|
+
const stored = localStorage.getItem(versionKey);
|
|
2983
|
+
if (stored === options.version) return false;
|
|
2984
|
+
if (options.onBeforeClear) {
|
|
2985
|
+
await options.onBeforeClear();
|
|
2986
|
+
}
|
|
2987
|
+
await clearMidenStorage();
|
|
2988
|
+
localStorage.setItem(versionKey, options.version);
|
|
2989
|
+
if (options.reloadOnClear !== false) {
|
|
2990
|
+
window.location.reload();
|
|
2991
|
+
}
|
|
2992
|
+
return true;
|
|
2993
|
+
}
|
|
2994
|
+
async function clearMidenStorage() {
|
|
2995
|
+
if (typeof indexedDB === "undefined") return;
|
|
2996
|
+
try {
|
|
2997
|
+
const databases = await indexedDB.databases();
|
|
2998
|
+
const midenDbs = databases.filter(
|
|
2999
|
+
(db) => db.name?.toLowerCase().includes("miden")
|
|
3000
|
+
);
|
|
3001
|
+
await Promise.all(
|
|
3002
|
+
midenDbs.map(
|
|
3003
|
+
(db) => new Promise((resolve, reject) => {
|
|
3004
|
+
if (!db.name) {
|
|
3005
|
+
resolve();
|
|
3006
|
+
return;
|
|
3007
|
+
}
|
|
3008
|
+
const req = indexedDB.deleteDatabase(db.name);
|
|
3009
|
+
req.onsuccess = () => resolve();
|
|
3010
|
+
req.onerror = () => reject(req.error);
|
|
3011
|
+
req.onblocked = () => {
|
|
3012
|
+
console.warn(
|
|
3013
|
+
`IndexedDB "${db.name}" delete was blocked \u2014 close other tabs using this database.`
|
|
3014
|
+
);
|
|
3015
|
+
resolve();
|
|
3016
|
+
};
|
|
3017
|
+
})
|
|
3018
|
+
)
|
|
3019
|
+
);
|
|
3020
|
+
} catch {
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
function createMidenStorage(prefix) {
|
|
3024
|
+
const fullKey = (key) => `${prefix}:${key}`;
|
|
3025
|
+
return {
|
|
3026
|
+
get(key) {
|
|
3027
|
+
try {
|
|
3028
|
+
const raw = localStorage.getItem(fullKey(key));
|
|
3029
|
+
if (raw === null) return null;
|
|
3030
|
+
return JSON.parse(raw);
|
|
3031
|
+
} catch {
|
|
3032
|
+
return null;
|
|
3033
|
+
}
|
|
3034
|
+
},
|
|
3035
|
+
set(key, value) {
|
|
3036
|
+
try {
|
|
3037
|
+
localStorage.setItem(fullKey(key), JSON.stringify(value));
|
|
3038
|
+
} catch (e) {
|
|
3039
|
+
console.warn(`Failed to write localStorage key "${fullKey(key)}":`, e);
|
|
3040
|
+
}
|
|
3041
|
+
},
|
|
3042
|
+
remove(key) {
|
|
3043
|
+
localStorage.removeItem(fullKey(key));
|
|
3044
|
+
},
|
|
3045
|
+
clear() {
|
|
3046
|
+
const keysToRemove = [];
|
|
3047
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
3048
|
+
const k = localStorage.key(i);
|
|
3049
|
+
if (k?.startsWith(`${prefix}:`)) {
|
|
3050
|
+
keysToRemove.push(k);
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
keysToRemove.forEach((k) => localStorage.removeItem(k));
|
|
3054
|
+
}
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
|
|
2296
3058
|
// src/index.ts
|
|
2297
3059
|
installAccountBech32();
|
|
2298
3060
|
export {
|
|
2299
3061
|
DEFAULTS,
|
|
3062
|
+
MidenError,
|
|
2300
3063
|
MidenProvider,
|
|
2301
3064
|
SignerContext,
|
|
3065
|
+
accountIdsEqual,
|
|
3066
|
+
bigIntToBytes,
|
|
3067
|
+
bytesToBigInt,
|
|
3068
|
+
clearMidenStorage,
|
|
3069
|
+
concatBytes,
|
|
3070
|
+
createMidenStorage,
|
|
3071
|
+
createNoteAttachment,
|
|
2302
3072
|
formatAssetAmount,
|
|
2303
3073
|
formatNoteSummary,
|
|
2304
3074
|
getNoteSummary,
|
|
3075
|
+
migrateStorage,
|
|
3076
|
+
normalizeAccountId,
|
|
2305
3077
|
parseAssetAmount,
|
|
3078
|
+
readNoteAttachment,
|
|
2306
3079
|
toBech32AccountId,
|
|
2307
3080
|
useAccount,
|
|
2308
3081
|
useAccounts,
|
|
@@ -2316,13 +3089,17 @@ export {
|
|
|
2316
3089
|
useMidenClient,
|
|
2317
3090
|
useMint,
|
|
2318
3091
|
useMultiSend,
|
|
3092
|
+
useNoteStream,
|
|
2319
3093
|
useNotes,
|
|
2320
3094
|
useSend,
|
|
3095
|
+
useSessionAccount,
|
|
2321
3096
|
useSigner,
|
|
2322
3097
|
useSwap,
|
|
2323
3098
|
useSyncState,
|
|
2324
3099
|
useTransaction,
|
|
2325
3100
|
useTransactionHistory,
|
|
2326
3101
|
useWaitForCommit,
|
|
2327
|
-
useWaitForNotes
|
|
3102
|
+
useWaitForNotes,
|
|
3103
|
+
waitForWalletDetection,
|
|
3104
|
+
wrapWasmError
|
|
2328
3105
|
};
|