@zill-protocol/client 4.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +18 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/package.json +58 -0
  7. package/dist/src/NocturneClient.d.ts +68 -0
  8. package/dist/src/NocturneClient.d.ts.map +1 -0
  9. package/dist/src/NocturneClient.js +264 -0
  10. package/dist/src/NocturneClient.js.map +1 -0
  11. package/dist/src/NocturneDB.d.ts +100 -0
  12. package/dist/src/NocturneDB.d.ts.map +1 -0
  13. package/dist/src/NocturneDB.js +525 -0
  14. package/dist/src/NocturneDB.js.map +1 -0
  15. package/dist/src/OpTracker.d.ts +13 -0
  16. package/dist/src/OpTracker.d.ts.map +1 -0
  17. package/dist/src/OpTracker.js +34 -0
  18. package/dist/src/OpTracker.js.map +1 -0
  19. package/dist/src/conversion/converter.d.ts +5 -0
  20. package/dist/src/conversion/converter.d.ts.map +1 -0
  21. package/dist/src/conversion/converter.js +15 -0
  22. package/dist/src/conversion/converter.js.map +1 -0
  23. package/dist/src/conversion/index.d.ts +3 -0
  24. package/dist/src/conversion/index.d.ts.map +1 -0
  25. package/dist/src/conversion/index.js +21 -0
  26. package/dist/src/conversion/index.js.map +1 -0
  27. package/dist/src/conversion/mock.d.ts +6 -0
  28. package/dist/src/conversion/mock.d.ts.map +1 -0
  29. package/dist/src/conversion/mock.js +14 -0
  30. package/dist/src/conversion/mock.js.map +1 -0
  31. package/dist/src/index.d.ts +14 -0
  32. package/dist/src/index.d.ts.map +1 -0
  33. package/dist/src/index.js +39 -0
  34. package/dist/src/index.js.map +1 -0
  35. package/dist/src/opRequestGas.d.ts +20 -0
  36. package/dist/src/opRequestGas.d.ts.map +1 -0
  37. package/dist/src/opRequestGas.js +321 -0
  38. package/dist/src/opRequestGas.js.map +1 -0
  39. package/dist/src/operationRequest/builder.d.ts +40 -0
  40. package/dist/src/operationRequest/builder.d.ts.map +1 -0
  41. package/dist/src/operationRequest/builder.js +192 -0
  42. package/dist/src/operationRequest/builder.js.map +1 -0
  43. package/dist/src/operationRequest/index.d.ts +3 -0
  44. package/dist/src/operationRequest/index.d.ts.map +1 -0
  45. package/dist/src/operationRequest/index.js +6 -0
  46. package/dist/src/operationRequest/index.js.map +1 -0
  47. package/dist/src/operationRequest/operationRequest.d.ts +50 -0
  48. package/dist/src/operationRequest/operationRequest.d.ts.map +1 -0
  49. package/dist/src/operationRequest/operationRequest.js +16 -0
  50. package/dist/src/operationRequest/operationRequest.js.map +1 -0
  51. package/dist/src/prepareOperation.d.ts +21 -0
  52. package/dist/src/prepareOperation.d.ts.map +1 -0
  53. package/dist/src/prepareOperation.js +256 -0
  54. package/dist/src/prepareOperation.js.map +1 -0
  55. package/dist/src/proveOperation.d.ts +7 -0
  56. package/dist/src/proveOperation.d.ts.map +1 -0
  57. package/dist/src/proveOperation.js +79 -0
  58. package/dist/src/proveOperation.js.map +1 -0
  59. package/dist/src/signOperation.d.ts +3 -0
  60. package/dist/src/signOperation.d.ts.map +1 -0
  61. package/dist/src/signOperation.js +61 -0
  62. package/dist/src/signOperation.js.map +1 -0
  63. package/dist/src/snapJsonRpc.d.ts +55 -0
  64. package/dist/src/snapJsonRpc.d.ts.map +1 -0
  65. package/dist/src/snapJsonRpc.js +63 -0
  66. package/dist/src/snapJsonRpc.js.map +1 -0
  67. package/dist/src/syncSDK.d.ts +17 -0
  68. package/dist/src/syncSDK.d.ts.map +1 -0
  69. package/dist/src/syncSDK.js +188 -0
  70. package/dist/src/syncSDK.js.map +1 -0
  71. package/dist/src/types.d.ts +60 -0
  72. package/dist/src/types.d.ts.map +1 -0
  73. package/dist/src/types.js +3 -0
  74. package/dist/src/types.js.map +1 -0
  75. package/dist/src/utils/constants.d.ts +3 -0
  76. package/dist/src/utils/constants.d.ts.map +1 -0
  77. package/dist/src/utils/constants.js +20 -0
  78. package/dist/src/utils/constants.js.map +1 -0
  79. package/dist/src/utils/index.d.ts +3 -0
  80. package/dist/src/utils/index.d.ts.map +1 -0
  81. package/dist/src/utils/index.js +19 -0
  82. package/dist/src/utils/index.js.map +1 -0
  83. package/dist/src/utils/misc.d.ts +13 -0
  84. package/dist/src/utils/misc.d.ts.map +1 -0
  85. package/dist/src/utils/misc.js +77 -0
  86. package/dist/src/utils/misc.js.map +1 -0
  87. package/dist/tsconfig.tsbuildinfo +1 -0
  88. package/package.json +58 -0
  89. package/src/NocturneClient.ts +415 -0
  90. package/src/NocturneDB.ts +761 -0
  91. package/src/OpTracker.ts +44 -0
  92. package/src/conversion/converter.ts +22 -0
  93. package/src/conversion/index.ts +2 -0
  94. package/src/conversion/mock.ts +11 -0
  95. package/src/index.ts +14 -0
  96. package/src/opRequestGas.ts +487 -0
  97. package/src/operationRequest/builder.ts +359 -0
  98. package/src/operationRequest/index.ts +16 -0
  99. package/src/operationRequest/operationRequest.ts +87 -0
  100. package/src/prepareOperation.ts +420 -0
  101. package/src/proveOperation.ts +124 -0
  102. package/src/signOperation.ts +116 -0
  103. package/src/snapJsonRpc.ts +109 -0
  104. package/src/syncSDK.ts +285 -0
  105. package/src/types.ts +83 -0
  106. package/src/utils/constants.ts +16 -0
  107. package/src/utils/index.ts +2 -0
  108. package/src/utils/misc.ts +107 -0
@@ -0,0 +1,109 @@
1
+ import {
2
+ Address,
3
+ CanonAddrRegistryEntry,
4
+ PreSignOperation,
5
+ SignedOperation,
6
+ } from "@zill-protocol/core";
7
+ import {
8
+ CanonAddress,
9
+ NocturneSignature,
10
+ SpendPk,
11
+ ViewingKey,
12
+ } from "@zill-protocol/crypto";
13
+ import * as JSON from "bigint-json-serialization";
14
+ import { OperationWithMetadata } from "./types";
15
+
16
+ export interface SetSpendKeyMethod {
17
+ method: "nocturne_setSpendKey";
18
+ params: {
19
+ spendKey: string;
20
+ eoaAddress: Address;
21
+ };
22
+ return: string | null; // error string or null
23
+ }
24
+
25
+ export interface SignCanonAddrRegistryEntryMethod {
26
+ method: "nocturne_signCanonAddrRegistryEntry";
27
+ params: {
28
+ entry: CanonAddrRegistryEntry;
29
+ chainId: bigint;
30
+ registryAddress: string;
31
+ };
32
+ return: {
33
+ canonAddr: CanonAddress;
34
+ digest: bigint;
35
+ sig: NocturneSignature;
36
+ spendPubkey: SpendPk;
37
+ vkNonce: bigint;
38
+ };
39
+ }
40
+
41
+ export interface SignOperationMethod {
42
+ method: "nocturne_signOperation";
43
+ params: OperationWithMetadata<PreSignOperation>;
44
+ return: SignedOperation;
45
+ }
46
+
47
+ export interface RequestSpendKeyEoaMethod {
48
+ method: "nocturne_requestSpendKeyEoa";
49
+ params: null;
50
+ return: Address | null;
51
+ }
52
+
53
+ export interface RequestViewingKeyMethodResponse {
54
+ vk: ViewingKey;
55
+ vkNonce: bigint;
56
+ }
57
+
58
+ export interface RequestViewingKeyMethod {
59
+ method: "nocturne_requestViewingKey";
60
+ params: null;
61
+ return: RequestViewingKeyMethodResponse;
62
+ }
63
+
64
+ export type RpcRequestMethod =
65
+ | SetSpendKeyMethod
66
+ | SignCanonAddrRegistryEntryMethod
67
+ | SignOperationMethod
68
+ | RequestViewingKeyMethod
69
+ | RequestSpendKeyEoaMethod;
70
+
71
+ export type SnapRpcRequestHandlerArgs = {
72
+ origin: string;
73
+ request: RpcRequestMethod;
74
+ };
75
+
76
+ export type SnapRpcRequestHandler = (
77
+ args: SnapRpcRequestHandlerArgs
78
+ ) => Promise<RpcRequestMethod["return"]>;
79
+
80
+ export function assertAllRpcMethodsHandled(request: never): never {
81
+ // @ts-expect-error on request.method—if this fires at runtime, we want to see the method name
82
+ throw new Error("Snap JSON RPC method not handled: " + request.method);
83
+ }
84
+
85
+ export function parseObjectValues(params: object): object {
86
+ return Object.fromEntries(
87
+ Object.entries(params).map(([key, value]) => {
88
+ const parsedValue = JSON.parse(value);
89
+ if (parsedValue && parsedValue.__primitive) {
90
+ return [key, parsedValue.value ?? undefined];
91
+ } else {
92
+ return [key, parsedValue];
93
+ }
94
+ })
95
+ );
96
+ }
97
+
98
+ export function stringifyObjectValues(params: object): object {
99
+ return Object.fromEntries(
100
+ Object.entries(params).map(([key, value]) => [
101
+ key,
102
+ JSON.stringify(
103
+ typeof value === "object"
104
+ ? value
105
+ : { __primitive: true, value: value ?? null }
106
+ ),
107
+ ])
108
+ );
109
+ }
package/src/syncSDK.ts ADDED
@@ -0,0 +1,285 @@
1
+ import { NocturneViewer } from "@zill-protocol/crypto";
2
+ import { NocturneDB } from "./NocturneDB";
3
+ import { PendingOutputRecord } from "./types";
4
+ import {
5
+ IncludedEncryptedNote,
6
+ IncludedNote,
7
+ IncludedNoteCommitment,
8
+ NoteTrait,
9
+ EncryptedStateDiff,
10
+ StateDiff,
11
+ SDKSyncAdapter,
12
+ TotalEntityIndexTrait,
13
+ decryptNote,
14
+ SparseMerkleProver,
15
+ consecutiveChunks,
16
+ timed,
17
+ timedAsync,
18
+ Histogram,
19
+ } from "@zill-protocol/core";
20
+
21
+ export interface SyncOpts {
22
+ endBlock?: number;
23
+ timeoutSeconds?: number;
24
+ timing?: boolean;
25
+ finalityBlocks?: number;
26
+ debug?: boolean;
27
+ onPendingOutputsConfirmed?: (outputs: PendingOutputRecord[]) => void;
28
+ }
29
+
30
+ export interface SyncDeps {
31
+ viewer: NocturneViewer;
32
+ }
33
+
34
+ // Sync SDK, returning last synced merkle index of last state diff
35
+ export async function syncSDK(
36
+ { viewer }: SyncDeps,
37
+ adapter: SDKSyncAdapter,
38
+ db: NocturneDB,
39
+ merkle: SparseMerkleProver,
40
+ opts?: SyncOpts
41
+ ): Promise<number | undefined> {
42
+ const currTotalEntityIndex = await db.currentTotalEntityIndex();
43
+ const startTotalEntityIndex = currTotalEntityIndex
44
+ ? currTotalEntityIndex + 1n
45
+ : 0n;
46
+
47
+ const currentBlock = await adapter.getLatestIndexedBlock();
48
+ const endTotalEntityIndex = TotalEntityIndexTrait.fromBlockNumber(
49
+ opts?.endBlock ?? currentBlock
50
+ );
51
+ const range = {
52
+ startTotalEntityIndex,
53
+ endTotalEntityIndex,
54
+ startBlock: TotalEntityIndexTrait.toComponents(startTotalEntityIndex)
55
+ .blockNumber,
56
+ endBlock: currentBlock,
57
+ };
58
+ if (opts?.debug) {
59
+ console.log(
60
+ `[syncSDK] syncing SDK from totalEntityIndex ${startTotalEntityIndex} (block ${range.startBlock}) to ${endTotalEntityIndex} (block ${currentBlock})...`,
61
+ { range }
62
+ );
63
+ }
64
+
65
+ const newDiffs = adapter.iterStateDiffs(startTotalEntityIndex, {
66
+ endTotalEntityIndex,
67
+ timing: opts?.timing,
68
+ finalityBlocks: opts?.finalityBlocks,
69
+ });
70
+
71
+ // decrypt notes and compute nullifiers
72
+ const diffHistogram = opts?.timing
73
+ ? new Histogram("decryptStateDiff time (ms) per note")
74
+ : undefined;
75
+ const diffs = newDiffs.map((diff) => {
76
+ const [decrypted, time] = timed(() => decryptStateDiff(viewer, diff));
77
+ diffHistogram?.sample(time / diff.notes.length);
78
+ return decrypted;
79
+ });
80
+
81
+ let latestSyncedMerkleIndex: number | undefined =
82
+ await db.latestSyncedMerkleIndex();
83
+
84
+ if (opts?.timeoutSeconds) {
85
+ setTimeout(() => diffs.close(), opts.timeoutSeconds * 1000);
86
+ }
87
+
88
+ // apply diffs
89
+ const applyStateDiffHistogram = opts?.timing
90
+ ? new Histogram("applyStateDiff time (ms) per note or commitment")
91
+ : undefined;
92
+ const updateMerkleHistogram = opts?.timing
93
+ ? new Histogram("updateMerkle time (ms) per note or commitment")
94
+ : undefined;
95
+ for await (const diff of diffs.iter) {
96
+ if (opts?.debug) {
97
+ console.log(
98
+ "[syncSDK] diff latestNewlySyncedMerkleIndex",
99
+ diff.latestNewlySyncedMerkleIndex
100
+ );
101
+ }
102
+ // update notes in DB
103
+ const [nfIndices, nfTime] = await timedAsync(() =>
104
+ db.applyStateDiff(
105
+ diff,
106
+ opts?.onPendingOutputsConfirmed
107
+ ? { onPendingOutputsConfirmed: opts.onPendingOutputsConfirmed }
108
+ : undefined
109
+ )
110
+ );
111
+ applyStateDiffHistogram?.sample(nfTime / diff.notesAndCommitments.length);
112
+ latestSyncedMerkleIndex = await db.latestSyncedMerkleIndex();
113
+
114
+ // TODO: deal with case where we have failure between applying state diff to DB and merkle being persisted
115
+
116
+ if (diff.latestCommittedMerkleIndex) {
117
+ const [_, time] = await timedAsync(() =>
118
+ updateMerkle(
119
+ merkle,
120
+ diff.latestCommittedMerkleIndex!,
121
+ diff.notesAndCommitments.map((n) => n.inner),
122
+ nfIndices,
123
+ opts?.debug ?? false
124
+ )
125
+ );
126
+ updateMerkleHistogram?.sample(time / diff.notesAndCommitments.length);
127
+ }
128
+ }
129
+
130
+ diffHistogram?.print();
131
+ applyStateDiffHistogram?.print();
132
+ updateMerkleHistogram?.print();
133
+
134
+ return latestSyncedMerkleIndex;
135
+ }
136
+
137
+ async function updateMerkle(
138
+ merkle: SparseMerkleProver,
139
+ latestCommittedMerkleIndex: number,
140
+ notesAndCommitments: (IncludedNote | IncludedNoteCommitment)[],
141
+ nfIndices: number[],
142
+ logWarnings: boolean
143
+ ): Promise<void> {
144
+ const ordered = [...notesAndCommitments].sort(
145
+ (a, b) => a.merkleIndex - b.merkleIndex
146
+ );
147
+ const currentCount = merkle.count();
148
+ const filtered = ordered.filter(
149
+ (noteOrCommitment) => noteOrCommitment.merkleIndex >= currentCount
150
+ );
151
+ if (logWarnings && filtered.length !== ordered.length) {
152
+ console.warn("[syncSDK] skipping stale leaves", {
153
+ currentCount,
154
+ skipped: ordered.length - filtered.length,
155
+ first: ordered[0]?.merkleIndex,
156
+ last: ordered[ordered.length - 1]?.merkleIndex,
157
+ });
158
+ }
159
+
160
+ // add all new leaves as uncommitted leaves
161
+ const batches = consecutiveChunks(
162
+ filtered,
163
+ (noteOrCommitment) => noteOrCommitment.merkleIndex
164
+ );
165
+ for (const batch of batches) {
166
+ const startIndex = batch[0].merkleIndex;
167
+ const leaves = [];
168
+ const includes = [];
169
+ for (const noteOrCommitment of batch) {
170
+ if (NoteTrait.isCommitment(noteOrCommitment)) {
171
+ leaves.push(
172
+ (noteOrCommitment as IncludedNoteCommitment).noteCommitment
173
+ );
174
+ includes.push(false);
175
+ } else {
176
+ leaves.push(NoteTrait.toCommitment(noteOrCommitment as IncludedNote));
177
+ includes.push(true);
178
+ }
179
+ }
180
+ merkle.insertBatchUncommitted(startIndex, leaves, includes);
181
+ }
182
+
183
+ // commit up to latest subtree commit
184
+ merkle.commitUpToIndex(latestCommittedMerkleIndex);
185
+
186
+ // mark nullified ones for pruning
187
+ for (const index of nfIndices) {
188
+ try {
189
+ merkle.markForPruning(index);
190
+ } catch (err) {
191
+ if (logWarnings) {
192
+ console.warn("[syncSDK] skip pruning missing leaf", {
193
+ index,
194
+ error: err instanceof Error ? err.message : String(err),
195
+ });
196
+ }
197
+ }
198
+ }
199
+
200
+ // persist merkle to underlying KV store
201
+ await merkle.persist();
202
+ }
203
+
204
+ function decryptStateDiff(
205
+ viewer: NocturneViewer,
206
+ {
207
+ notes,
208
+ nullifiers,
209
+ latestCommittedMerkleIndex,
210
+ latestCommitTei,
211
+ latestNewlySyncedMerkleIndex,
212
+ totalEntityIndex,
213
+ }: EncryptedStateDiff
214
+ ): StateDiff {
215
+ const notesAndCommitments = notes.map(({ inner, totalEntityIndex }) => {
216
+ const note = inner;
217
+ const isEncrypted = NoteTrait.isEncryptedNote(note);
218
+ if (isEncrypted) {
219
+ // if it's encrypted, attempt to decrypt.
220
+ // if it succeeds, then return the decrypted note
221
+ // if it fails, assume it's not ours and just the commitment and merkle index
222
+ try {
223
+ const { merkleIndex, commitment, ...encryptedNote } =
224
+ note as IncludedEncryptedNote;
225
+
226
+ // TODO: come up with a way to handle sender mismatches when we implement history proofs
227
+ const includedNote: IncludedNote = {
228
+ ...decryptNote(viewer, encryptedNote),
229
+ merkleIndex,
230
+ };
231
+ const nullifier = NoteTrait.createNullifier(viewer, includedNote);
232
+ const res = { ...includedNote, nullifier };
233
+ return {
234
+ inner: res,
235
+ totalEntityIndex,
236
+ };
237
+ } catch (err) {
238
+ const encryptedNote = note as IncludedEncryptedNote;
239
+ const { commitment, merkleIndex } = encryptedNote;
240
+ const nc = {
241
+ noteCommitment: commitment,
242
+ merkleIndex,
243
+ };
244
+
245
+ return {
246
+ inner: nc,
247
+ totalEntityIndex,
248
+ };
249
+ }
250
+ } else {
251
+ // if it's not encrypted, check if it's ours.
252
+ // if it is, then return it
253
+ // if it's not, return only the commitment
254
+ const includedNote = note as IncludedNote;
255
+ const isOwn = viewer.isOwnAddress(includedNote.owner);
256
+
257
+ if (isOwn) {
258
+ const nullifier = NoteTrait.createNullifier(viewer, includedNote);
259
+ const res = { ...includedNote, nullifier };
260
+ return {
261
+ inner: res,
262
+ totalEntityIndex,
263
+ };
264
+ } else {
265
+ const nc = {
266
+ noteCommitment: NoteTrait.toCommitment(includedNote),
267
+ merkleIndex: includedNote.merkleIndex,
268
+ };
269
+ return {
270
+ inner: nc,
271
+ totalEntityIndex,
272
+ };
273
+ }
274
+ }
275
+ });
276
+
277
+ return {
278
+ notesAndCommitments,
279
+ nullifiers,
280
+ latestCommittedMerkleIndex,
281
+ latestCommitTei,
282
+ latestNewlySyncedMerkleIndex,
283
+ totalEntityIndex,
284
+ };
285
+ }
package/src/types.ts ADDED
@@ -0,0 +1,83 @@
1
+ import {
2
+ Address,
3
+ Asset,
4
+ CanonAddress,
5
+ Operation,
6
+ OperationStatus,
7
+ } from "@zill-protocol/core";
8
+
9
+ export type OpWithMetadata<O> = {
10
+ op: O;
11
+ metadata: OperationMetadata;
12
+ };
13
+
14
+ export interface OptimisticNFRecord {
15
+ nullifier: bigint;
16
+ expirationDate: number;
17
+ }
18
+
19
+ export type OpHistoryRecord = {
20
+ digest: bigint;
21
+ metadata: OperationMetadata;
22
+
23
+ spentNoteMerkleIndices: number[];
24
+
25
+ status?: OperationStatus;
26
+
27
+ createdAt: number;
28
+ lastModified: number;
29
+ };
30
+
31
+ export interface OperationMetadata {
32
+ items: OperationMetadataItem[];
33
+ }
34
+
35
+ export type PendingOutputRecord = {
36
+ commitment: bigint;
37
+ asset: Asset;
38
+ value: bigint;
39
+ createdAt: number;
40
+ opDigest?: bigint;
41
+ expectedInsertionIndex?: number;
42
+ };
43
+
44
+ export type OperationMetadataItem =
45
+ | ConfidentialPaymentMetadata
46
+ | ActionMetadata;
47
+
48
+ export type ActionMetadata =
49
+ | {
50
+ type: "Action";
51
+ actionType: "Transfer";
52
+ recipientAddress: Address;
53
+ erc20Address: Address;
54
+ amount: bigint;
55
+ }
56
+ | {
57
+ type: "Action";
58
+ actionType: "Weth To Wsteth";
59
+ amount: bigint;
60
+ }
61
+ | {
62
+ type: "Action";
63
+ actionType: "Transfer ETH";
64
+ recipientAddress: Address;
65
+ amount: bigint;
66
+ };
67
+
68
+ export interface ConfidentialPaymentMetadata {
69
+ type: "ConfidentialPayment";
70
+ recipient: CanonAddress;
71
+ asset: Asset;
72
+ amount: bigint;
73
+ }
74
+
75
+ export interface OpDigestWithMetadata {
76
+ opDigest: bigint;
77
+ metadata?: OperationMetadata;
78
+ }
79
+
80
+ export interface OperationWithMetadata<T extends Operation> {
81
+ op: T;
82
+ metadata?: OperationMetadata;
83
+ }
@@ -0,0 +1,16 @@
1
+ export function chainIdToNetworkName(chainId: bigint): string {
2
+ switch (chainId) {
3
+ case 1n:
4
+ return "mainnet";
5
+ case 5n:
6
+ return "goerli";
7
+ case 11155111n:
8
+ return "sepolia";
9
+ case 31337n:
10
+ return "localhost";
11
+ default:
12
+ throw new Error(`unsupported chainId: ${chainId}`);
13
+ }
14
+ }
15
+
16
+ export const OPTIMISTIC_RECORD_TTL: number = 30 * 60 * 1000; // 30 minutes
@@ -0,0 +1,2 @@
1
+ export * from "./constants";
2
+ export * from "./misc";
@@ -0,0 +1,107 @@
1
+ import {
2
+ SignedOperation,
3
+ PreSignOperation,
4
+ PreProofJoinSplit,
5
+ PreSignJoinSplit,
6
+ Note,
7
+ IncludedNote,
8
+ Asset,
9
+ MapWithObjectKeys,
10
+ merklePathToIndex,
11
+ OperationStatus,
12
+ } from "@zill-protocol/core";
13
+ import { JoinSplitRequest } from "../operationRequest";
14
+
15
+ export function sortNotesByValue<T extends Note>(notes: T[]): T[] {
16
+ return notes.sort((a, b) => {
17
+ return Number(a.value - b.value);
18
+ });
19
+ }
20
+
21
+ export function getJoinSplitRequestTotalValue(
22
+ joinSplitRequest: JoinSplitRequest
23
+ ): bigint {
24
+ let totalVal = joinSplitRequest.unwrapValue;
25
+ if (joinSplitRequest.payment !== undefined) {
26
+ totalVal += joinSplitRequest.payment.value;
27
+ }
28
+ return totalVal;
29
+ }
30
+
31
+ export interface NullifierWithMerkleIndex {
32
+ nullifier: bigint;
33
+ merkleIndex: bigint;
34
+ }
35
+
36
+ export function getIncludedNotesFromOp(
37
+ op: PreSignOperation
38
+ ): MapWithObjectKeys<Asset, IncludedNote[]> {
39
+ const notesMap: MapWithObjectKeys<Asset, IncludedNote[]> =
40
+ new MapWithObjectKeys();
41
+
42
+ op.joinSplits.forEach((joinSplit) => {
43
+ const { oldNoteA, oldNoteB } = joinSplit;
44
+ const notes = [oldNoteA, oldNoteB];
45
+
46
+ notes.forEach((note) => {
47
+ if (!notesMap.has(note.asset)) {
48
+ notesMap.set(note.asset, []);
49
+ }
50
+
51
+ const existingNotes = notesMap.get(note.asset)!;
52
+ notesMap.set(note.asset, [...existingNotes, note]);
53
+ });
54
+ });
55
+
56
+ return notesMap;
57
+ }
58
+
59
+ // returns the merkle indices of the notes spent in an op
60
+ export function getMerkleIndicesAndNfsFromOp(
61
+ op: PreSignOperation | SignedOperation
62
+ ): NullifierWithMerkleIndex[] {
63
+ return op.joinSplits.flatMap((joinSplit) => {
64
+ // get merkle index out of the path in the merkle proof
65
+ // how we do this depends on which kind of joinSplit it is (which depends on the op)
66
+ let merklePathA: bigint[];
67
+ let merklePathB: bigint[];
68
+
69
+ if (Object.hasOwn(joinSplit, "proofInputs")) {
70
+ // if it has "proofInputs", it's a `PreProofJoinSplit`
71
+ // in this case, we get the merkle path out of the proofInputs
72
+ merklePathA = (joinSplit as PreProofJoinSplit).proofInputs.merkleProofA
73
+ .path;
74
+ merklePathB = (joinSplit as PreProofJoinSplit).proofInputs.merkleProofB
75
+ .path;
76
+ } else {
77
+ // otherwise, it's a `PreSignJoinSplit`, in which case we get it out of the joinsplit itself
78
+ merklePathA = (joinSplit as PreSignJoinSplit).merkleProofA.path;
79
+ merklePathB = (joinSplit as PreSignJoinSplit).merkleProofB.path;
80
+ }
81
+
82
+ return [
83
+ {
84
+ merkleIndex: merklePathToIndex(merklePathA, "LEAF_TO_ROOT"),
85
+ nullifier: joinSplit.nullifierA,
86
+ },
87
+ {
88
+ merkleIndex: merklePathToIndex(merklePathB, "LEAF_TO_ROOT"),
89
+ nullifier: joinSplit.nullifierB,
90
+ },
91
+ ];
92
+ });
93
+ }
94
+
95
+ export function isFailedOpStatus(status: OperationStatus): boolean {
96
+ return (
97
+ status === OperationStatus.BUNDLE_REVERTED ||
98
+ status === OperationStatus.OPERATION_EXECUTION_FAILED ||
99
+ status === OperationStatus.OPERATION_PROCESSING_FAILED
100
+ );
101
+ }
102
+
103
+ export function isTerminalOpStatus(status: OperationStatus): boolean {
104
+ return (
105
+ status === OperationStatus.EXECUTED_SUCCESS || isFailedOpStatus(status)
106
+ );
107
+ }