@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,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./constants"), exports);
18
+ __exportStar(require("./misc"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA4B;AAC5B,yCAAuB"}
@@ -0,0 +1,13 @@
1
+ import { SignedOperation, PreSignOperation, Note, IncludedNote, Asset, MapWithObjectKeys, OperationStatus } from "@zill-protocol/core";
2
+ import { JoinSplitRequest } from "../operationRequest";
3
+ export declare function sortNotesByValue<T extends Note>(notes: T[]): T[];
4
+ export declare function getJoinSplitRequestTotalValue(joinSplitRequest: JoinSplitRequest): bigint;
5
+ export interface NullifierWithMerkleIndex {
6
+ nullifier: bigint;
7
+ merkleIndex: bigint;
8
+ }
9
+ export declare function getIncludedNotesFromOp(op: PreSignOperation): MapWithObjectKeys<Asset, IncludedNote[]>;
10
+ export declare function getMerkleIndicesAndNfsFromOp(op: PreSignOperation | SignedOperation): NullifierWithMerkleIndex[];
11
+ export declare function isFailedOpStatus(status: OperationStatus): boolean;
12
+ export declare function isTerminalOpStatus(status: OperationStatus): boolean;
13
+ //# sourceMappingURL=misc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"misc.d.ts","sourceRoot":"","sources":["../../../src/utils/misc.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,gBAAgB,EAGhB,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,iBAAiB,EAEjB,eAAe,EAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAIhE;AAED,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,gBAAgB,GACjC,MAAM,CAMR;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,gBAAgB,GACnB,iBAAiB,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC,CAmB1C;AAGD,wBAAgB,4BAA4B,CAC1C,EAAE,EAAE,gBAAgB,GAAG,eAAe,GACrC,wBAAwB,EAAE,CA+B5B;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAMjE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAInE"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sortNotesByValue = sortNotesByValue;
4
+ exports.getJoinSplitRequestTotalValue = getJoinSplitRequestTotalValue;
5
+ exports.getIncludedNotesFromOp = getIncludedNotesFromOp;
6
+ exports.getMerkleIndicesAndNfsFromOp = getMerkleIndicesAndNfsFromOp;
7
+ exports.isFailedOpStatus = isFailedOpStatus;
8
+ exports.isTerminalOpStatus = isTerminalOpStatus;
9
+ const core_1 = require("@zill-protocol/core");
10
+ function sortNotesByValue(notes) {
11
+ return notes.sort((a, b) => {
12
+ return Number(a.value - b.value);
13
+ });
14
+ }
15
+ function getJoinSplitRequestTotalValue(joinSplitRequest) {
16
+ let totalVal = joinSplitRequest.unwrapValue;
17
+ if (joinSplitRequest.payment !== undefined) {
18
+ totalVal += joinSplitRequest.payment.value;
19
+ }
20
+ return totalVal;
21
+ }
22
+ function getIncludedNotesFromOp(op) {
23
+ const notesMap = new core_1.MapWithObjectKeys();
24
+ op.joinSplits.forEach((joinSplit) => {
25
+ const { oldNoteA, oldNoteB } = joinSplit;
26
+ const notes = [oldNoteA, oldNoteB];
27
+ notes.forEach((note) => {
28
+ if (!notesMap.has(note.asset)) {
29
+ notesMap.set(note.asset, []);
30
+ }
31
+ const existingNotes = notesMap.get(note.asset);
32
+ notesMap.set(note.asset, [...existingNotes, note]);
33
+ });
34
+ });
35
+ return notesMap;
36
+ }
37
+ // returns the merkle indices of the notes spent in an op
38
+ function getMerkleIndicesAndNfsFromOp(op) {
39
+ return op.joinSplits.flatMap((joinSplit) => {
40
+ // get merkle index out of the path in the merkle proof
41
+ // how we do this depends on which kind of joinSplit it is (which depends on the op)
42
+ let merklePathA;
43
+ let merklePathB;
44
+ if (Object.hasOwn(joinSplit, "proofInputs")) {
45
+ // if it has "proofInputs", it's a `PreProofJoinSplit`
46
+ // in this case, we get the merkle path out of the proofInputs
47
+ merklePathA = joinSplit.proofInputs.merkleProofA
48
+ .path;
49
+ merklePathB = joinSplit.proofInputs.merkleProofB
50
+ .path;
51
+ }
52
+ else {
53
+ // otherwise, it's a `PreSignJoinSplit`, in which case we get it out of the joinsplit itself
54
+ merklePathA = joinSplit.merkleProofA.path;
55
+ merklePathB = joinSplit.merkleProofB.path;
56
+ }
57
+ return [
58
+ {
59
+ merkleIndex: (0, core_1.merklePathToIndex)(merklePathA, "LEAF_TO_ROOT"),
60
+ nullifier: joinSplit.nullifierA,
61
+ },
62
+ {
63
+ merkleIndex: (0, core_1.merklePathToIndex)(merklePathB, "LEAF_TO_ROOT"),
64
+ nullifier: joinSplit.nullifierB,
65
+ },
66
+ ];
67
+ });
68
+ }
69
+ function isFailedOpStatus(status) {
70
+ return (status === core_1.OperationStatus.BUNDLE_REVERTED ||
71
+ status === core_1.OperationStatus.OPERATION_EXECUTION_FAILED ||
72
+ status === core_1.OperationStatus.OPERATION_PROCESSING_FAILED);
73
+ }
74
+ function isTerminalOpStatus(status) {
75
+ return (status === core_1.OperationStatus.EXECUTED_SUCCESS || isFailedOpStatus(status));
76
+ }
77
+ //# sourceMappingURL=misc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"misc.js","sourceRoot":"","sources":["../../../src/utils/misc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"root":["../index.ts","../src/nocturneclient.ts","../src/nocturnedb.ts","../src/optracker.ts","../src/index.ts","../src/oprequestgas.ts","../src/prepareoperation.ts","../src/proveoperation.ts","../src/signoperation.ts","../src/snapjsonrpc.ts","../src/syncsdk.ts","../src/types.ts","../src/conversion/converter.ts","../src/conversion/index.ts","../src/conversion/mock.ts","../src/operationrequest/builder.ts","../src/operationrequest/index.ts","../src/operationrequest/operationrequest.ts","../src/utils/constants.ts","../src/utils/index.ts","../src/utils/misc.ts","../package.json"],"version":"5.9.3"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@zill-protocol/client",
3
+ "version": "4.1.2",
4
+ "license": "MIT",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/**/*",
9
+ "src/**/*"
10
+ ],
11
+ "scripts": {
12
+ "build": "yarn clean; tsc --build",
13
+ "check": "tsc --noEmit",
14
+ "clean": "rm -rf .turbo dist",
15
+ "gen-test-cases:operationhash": "npx ts-node ./scripts/genOperationHashTestCase.ts",
16
+ "gen-test-cases:registryentry": "npx ts-node ./scripts/genCanonAddrRegistryEntryHashTestCase.ts",
17
+ "gen-test-cases:poseidonext": "npx ts-node ./scripts/genPoseidonExtTestCase.ts",
18
+ "lint": "eslint --fix src --ext .ts",
19
+ "prettier:check": "prettier --check ./src ./test",
20
+ "prettier:write": "prettier --write ./src ./test",
21
+ "publish": "yarn build; yarn npm publish --access public",
22
+ "test:unit": "mocha --require ts-node/register test/** --timeout 45000 --exit"
23
+ },
24
+ "dependencies": {
25
+ "@zill-protocol/config": "^1.7.4",
26
+ "@zill-protocol/contracts": "^3.0.0",
27
+ "@zill-protocol/core": "^3.3.1",
28
+ "@zill-protocol/crypto": "^0.4.3",
29
+ "async-mutex": "^0.4.0",
30
+ "big-integer": "^1.6.42",
31
+ "bigint-json-serialization": "^1.0.1",
32
+ "ethers": "^5.7.2",
33
+ "js-sha256": "^0.9.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/chai": "^4.3.3",
37
+ "@types/chai-as-promised": "^7.1.5",
38
+ "@types/mocha": "^10.0.0",
39
+ "@types/node": "^25.0.1",
40
+ "@types/sinon": "^10.0.13",
41
+ "@types/sinon-chai": "^3.2.9",
42
+ "@typescript-eslint/eslint-plugin": "^8.49.0",
43
+ "@typescript-eslint/parser": "^8.49.0",
44
+ "chai": "^4.3.6",
45
+ "chai-as-promised": "^7.1.1",
46
+ "eslint": "^9.39.2",
47
+ "eslint-config-prettier": "^10.1.8",
48
+ "mocha": "^10.1.0",
49
+ "prettier": "^3.7.4",
50
+ "sinon": "^15.0.1",
51
+ "sinon-chai": "^3.7.0",
52
+ "ts-node": "^10.9.2",
53
+ "typescript": "^5.9.3"
54
+ },
55
+ "engines": {
56
+ "node": ">=18"
57
+ }
58
+ }
@@ -0,0 +1,415 @@
1
+ import { NocturneConfig, loadNocturneConfig } from "@zill-protocol/config";
2
+ import { Handler, Handler__factory } from "@zill-protocol/contracts";
3
+ import { NocturneViewer, StealthAddressTrait } from "@zill-protocol/crypto";
4
+ import ethers from "ethers";
5
+ import { GetNotesOpts, NocturneDB } from "./NocturneDB";
6
+ import { OpTracker } from "./OpTracker";
7
+ import { EthToTokenConverter } from "./conversion";
8
+ import { handleGasForOperationRequest } from "./opRequestGas";
9
+ import {
10
+ OperationRequest,
11
+ ensureOpRequestChainInfo,
12
+ } from "./operationRequest/operationRequest";
13
+ import { prepareOperation } from "./prepareOperation";
14
+ import { SyncOpts, syncSDK } from "./syncSDK";
15
+ import { OpHistoryRecord, OperationMetadata, PendingOutputRecord } from "./types";
16
+ import {
17
+ getJoinSplitRequestTotalValue,
18
+ getMerkleIndicesAndNfsFromOp,
19
+ } from "./utils";
20
+
21
+ import {
22
+ Asset,
23
+ AssetTrait,
24
+ AssetWithBalance,
25
+ IncludedNote,
26
+ MapWithObjectKeys,
27
+ Note,
28
+ NoteTrait,
29
+ OperationStatus,
30
+ OperationTrait,
31
+ PreSignOperation,
32
+ SDKSyncAdapter,
33
+ SignedOperation,
34
+ SparseMerkleProver,
35
+ TotalEntityIndex,
36
+ TotalEntityIndexTrait,
37
+ decryptNote,
38
+ maxArray,
39
+ } from "@zill-protocol/core";
40
+
41
+ const PRUNE_OPTIMISTIC_NFS_TIMER = 60 * 1000; // 1 minute
42
+
43
+ export class NocturneClient {
44
+ protected provider: ethers.providers.Provider;
45
+ protected config: NocturneConfig;
46
+ protected handlerContract: Handler;
47
+ protected merkleProver: SparseMerkleProver;
48
+ protected db: NocturneDB;
49
+ protected syncAdapter: SDKSyncAdapter;
50
+ protected tokenConverter: EthToTokenConverter;
51
+ protected opTracker: OpTracker;
52
+ protected pendingOutputsListeners: Set<
53
+ (outputs: PendingOutputRecord[]) => void
54
+ > = new Set();
55
+
56
+ readonly viewer: NocturneViewer;
57
+ readonly gasAssets: Map<string, Asset>;
58
+
59
+ constructor(
60
+ viewer: NocturneViewer,
61
+ provider: ethers.providers.Provider,
62
+ configOrNetworkName: NocturneConfig | string,
63
+ merkleProver: SparseMerkleProver,
64
+ db: NocturneDB,
65
+ syncAdapter: SDKSyncAdapter,
66
+ tokenConverter: EthToTokenConverter,
67
+ nulliferChecker: OpTracker
68
+ ) {
69
+ if (typeof configOrNetworkName == "string") {
70
+ this.config = loadNocturneConfig(configOrNetworkName);
71
+ } else {
72
+ this.config = configOrNetworkName;
73
+ }
74
+
75
+ this.provider = provider;
76
+
77
+ this.gasAssets = new Map(
78
+ Array.from(this.config.erc20s.entries())
79
+ .filter(([_, config]) => config.isGasAsset)
80
+ .map(([ticker, config]) => {
81
+ return [ticker, AssetTrait.erc20AddressToAsset(config.address)];
82
+ })
83
+ );
84
+
85
+ this.viewer = viewer;
86
+ console.log(`Canonical address: `, this.viewer.canonicalAddress());
87
+ this.handlerContract = Handler__factory.connect(
88
+ this.config.handlerAddress,
89
+ provider
90
+ );
91
+ this.merkleProver = merkleProver;
92
+ this.db = db;
93
+ this.syncAdapter = syncAdapter;
94
+ this.tokenConverter = tokenConverter;
95
+ this.opTracker = nulliferChecker;
96
+
97
+ // set an interval to prune optimistic nfs to ensure they don't get stuck
98
+ const prune = async () => {
99
+ await this.pruneOptimisticNullifiers();
100
+ setTimeout(prune, PRUNE_OPTIMISTIC_NFS_TIMER);
101
+ };
102
+ void prune();
103
+ }
104
+
105
+ async clearDb(): Promise<void> {
106
+ await this.db.kv.clear();
107
+ }
108
+
109
+ // Sync SDK, returning last synced merkle index of last state diff
110
+ async sync(opts?: SyncOpts): Promise<number | undefined> {
111
+ const hasListeners = this.pendingOutputsListeners.size > 0;
112
+ const shouldNotify = hasListeners || opts?.onPendingOutputsConfirmed;
113
+ const onPendingOutputsConfirmed = shouldNotify
114
+ ? (outputs: PendingOutputRecord[]) => {
115
+ if (!outputs.length) {
116
+ return;
117
+ }
118
+ if (hasListeners) {
119
+ for (const listener of this.pendingOutputsListeners) {
120
+ try {
121
+ listener(outputs);
122
+ } catch (err) {
123
+ console.warn("[sync] pending output listener failed", err);
124
+ }
125
+ }
126
+ }
127
+ opts?.onPendingOutputsConfirmed?.(outputs);
128
+ }
129
+ : undefined;
130
+
131
+ const baseOpts = opts
132
+ ? {
133
+ ...opts,
134
+ finalityBlocks: opts.finalityBlocks ?? this.config.finalityBlocks,
135
+ }
136
+ : undefined;
137
+ const mergedOpts = onPendingOutputsConfirmed
138
+ ? { ...(baseOpts ?? {}), onPendingOutputsConfirmed }
139
+ : baseOpts;
140
+
141
+ const latestSyncedMerkleIndex = await syncSDK(
142
+ { viewer: this.viewer },
143
+ this.syncAdapter,
144
+ this.db,
145
+ this.merkleProver,
146
+ mergedOpts
147
+ );
148
+
149
+ return latestSyncedMerkleIndex;
150
+ }
151
+
152
+ async prepareOperation(
153
+ opRequest: OperationRequest,
154
+ gasMultiplier: number
155
+ ): Promise<PreSignOperation> {
156
+ opRequest = await ensureOpRequestChainInfo(opRequest, this.provider);
157
+
158
+ const deps = {
159
+ db: this.db,
160
+ gasAssets: this.gasAssets,
161
+ tokenConverter: this.tokenConverter,
162
+ handlerContract: this.handlerContract,
163
+ merkle: this.merkleProver,
164
+ viewer: this.viewer,
165
+ };
166
+ const gasAccountedOpRequest = await handleGasForOperationRequest(
167
+ deps,
168
+ opRequest,
169
+ gasMultiplier
170
+ );
171
+
172
+ return await prepareOperation(deps, gasAccountedOpRequest);
173
+ }
174
+
175
+ async getAllAssetBalances(opts?: GetNotesOpts): Promise<AssetWithBalance[]> {
176
+ const notes = await this.db.getAllNotes(opts);
177
+ return Array.from(notes.entries()).map(([assetString, notes]) => {
178
+ const asset = NocturneDB.parseAssetKey(assetString);
179
+ const balance = notes.reduce((a, b) => a + b.value, 0n);
180
+ return {
181
+ asset,
182
+ balance,
183
+ numNotes: notes.length,
184
+ };
185
+ });
186
+ }
187
+
188
+ async getBalanceForAsset(asset: Asset, opts?: GetNotesOpts): Promise<bigint> {
189
+ return await this.db.getBalanceForAsset(asset, opts);
190
+ }
191
+
192
+ async getNotesForAsset(
193
+ asset: Asset,
194
+ opts?: GetNotesOpts
195
+ ): Promise<IncludedNote[]> {
196
+ return await this.db.getNotesForAsset(asset, opts);
197
+ }
198
+
199
+ async getPrivateBalanceBreakdown(asset: Asset): Promise<{
200
+ confirmed: bigint;
201
+ pendingUncommitted: bigint;
202
+ pendingLocal: bigint;
203
+ pending: bigint;
204
+ total: bigint;
205
+ }> {
206
+ const [confirmed, totalUncommitted, pendingLocal] = await Promise.all([
207
+ this.db.getBalanceForAsset(asset),
208
+ this.db.getBalanceForAsset(asset, { includeUncommitted: true }),
209
+ this.db.getPendingBalanceForAsset(asset),
210
+ ]);
211
+ const pendingUncommitted =
212
+ totalUncommitted > confirmed ? totalUncommitted - confirmed : 0n;
213
+ const pending = pendingUncommitted + pendingLocal;
214
+ return {
215
+ confirmed,
216
+ pendingUncommitted,
217
+ pendingLocal,
218
+ pending,
219
+ total: confirmed + pending,
220
+ };
221
+ }
222
+
223
+ async getPendingBalanceForAsset(asset: Asset): Promise<bigint> {
224
+ return await this.db.getPendingBalanceForAsset(asset);
225
+ }
226
+
227
+ async getPendingOutputs(
228
+ opts?: { asset?: Asset }
229
+ ): Promise<PendingOutputRecord[]> {
230
+ return await this.db.getPendingOutputs(opts);
231
+ }
232
+
233
+ async listPendingNotes(
234
+ opts?: { asset?: Asset }
235
+ ): Promise<PendingOutputRecord[]> {
236
+ return await this.db.getPendingOutputs(opts);
237
+ }
238
+
239
+ async addPendingOutputs(outputs: PendingOutputRecord[]): Promise<void> {
240
+ await this.db.addPendingOutputs(outputs);
241
+ }
242
+
243
+ async addPendingOutputsFromOperation(
244
+ op: PreSignOperation | SignedOperation
245
+ ): Promise<void> {
246
+ const senderOwner = StealthAddressTrait.fromCanonAddress(
247
+ this.viewer.canonicalAddress()
248
+ );
249
+ const senderKey = StealthAddressTrait.toString(senderOwner);
250
+ const digest = OperationTrait.computeDigest(op);
251
+ const outputs: PendingOutputRecord[] = [];
252
+
253
+ for (const joinSplit of op.joinSplits) {
254
+ let notes: Note[] = [];
255
+ if ("newNoteA" in joinSplit) {
256
+ notes = [joinSplit.newNoteA, joinSplit.newNoteB];
257
+ } else {
258
+ const decryptedNotes = [
259
+ joinSplit.newNoteAEncrypted,
260
+ joinSplit.newNoteBEncrypted,
261
+ ]
262
+ .map((encryptedNote): Note | null => {
263
+ try {
264
+ const { sender, ...note } = decryptNote(this.viewer, encryptedNote);
265
+ return note;
266
+ } catch (err) {
267
+ return null;
268
+ }
269
+ })
270
+ .filter((note): note is Note => note !== null);
271
+ notes = decryptedNotes;
272
+ }
273
+ for (const note of notes) {
274
+ if (note.value <= 0n) {
275
+ continue;
276
+ }
277
+ const ownerKey = StealthAddressTrait.toString(note.owner);
278
+ if (ownerKey !== senderKey) {
279
+ continue;
280
+ }
281
+ outputs.push({
282
+ commitment: NoteTrait.toCommitment(note),
283
+ asset: note.asset,
284
+ value: note.value,
285
+ createdAt: Date.now(),
286
+ opDigest: digest,
287
+ });
288
+ }
289
+ }
290
+
291
+ await this.db.addPendingOutputs(outputs);
292
+ }
293
+
294
+ async removePendingOutputsByOpDigest(opDigest: bigint): Promise<void> {
295
+ await this.db.removePendingOutputsByOpDigest(opDigest);
296
+ }
297
+
298
+ onPendingNoteConfirmed(
299
+ listener: (outputs: PendingOutputRecord[]) => void
300
+ ): () => void {
301
+ this.pendingOutputsListeners.add(listener);
302
+ return () => {
303
+ this.pendingOutputsListeners.delete(listener);
304
+ };
305
+ }
306
+
307
+ async getLatestSyncedMerkleIndex(): Promise<number | undefined> {
308
+ return await this.db.latestSyncedMerkleIndex();
309
+ }
310
+
311
+ async getLatestCommittedMerkleIndex(): Promise<number | undefined> {
312
+ return await this.db.latestCommittedMerkleIndex();
313
+ }
314
+
315
+ /**
316
+ * Force-nullify notes locally by nullifier. This is useful as a mitigation
317
+ * when the sync source cannot supply nullifiers (e.g. Mirror down) but the
318
+ * bundler reports the nullifier already exists on-chain.
319
+ */
320
+ async forceNullifyNotes(nullifiers: bigint[]): Promise<number[]> {
321
+ return await this.db.nullifyNotes(nullifiers);
322
+ }
323
+
324
+ getCurrentTreeRoot(): bigint {
325
+ return this.merkleProver.getRoot();
326
+ }
327
+
328
+ async hasEnoughBalanceForOperationRequest(
329
+ opRequest: OperationRequest
330
+ ): Promise<boolean> {
331
+ const assetRequestedAmounts = new MapWithObjectKeys<Asset, bigint>();
332
+ for (const joinSplitRequest of opRequest.joinSplitRequests) {
333
+ const asset = joinSplitRequest.asset;
334
+ let currentAmount = assetRequestedAmounts.get(asset) || 0n;
335
+ currentAmount += getJoinSplitRequestTotalValue(joinSplitRequest);
336
+ assetRequestedAmounts.set(asset, currentAmount);
337
+ }
338
+
339
+ for (const [asset, requestedAmount] of assetRequestedAmounts.entries()) {
340
+ const balance = await this.db.getBalanceForAsset(asset);
341
+ if (balance < requestedAmount) {
342
+ return false;
343
+ }
344
+ }
345
+
346
+ return true;
347
+ }
348
+
349
+ async getCreationBlockOfNewestNoteInOp(
350
+ op: PreSignOperation | SignedOperation
351
+ ): Promise<number> {
352
+ const totalEntityIndex = await getTotalEntityIndexOfNewestNoteInOp(
353
+ this.db,
354
+ op
355
+ );
356
+ return Number(
357
+ TotalEntityIndexTrait.toComponents(totalEntityIndex).blockNumber
358
+ );
359
+ }
360
+
361
+ async addOpToHistory(
362
+ op: PreSignOperation | SignedOperation,
363
+ metadata: OperationMetadata
364
+ ): Promise<void> {
365
+ await this.db.addOpToHistory(op, metadata);
366
+ }
367
+
368
+ async removeOpFromHistory(digest: bigint): Promise<void> {
369
+ await this.db.removeOpFromHistory(digest);
370
+ }
371
+
372
+ async getOpHistory(includePending?: boolean): Promise<OpHistoryRecord[]> {
373
+ return await this.db.getHistory(includePending);
374
+ }
375
+
376
+ async getOpHistoryRecord(
377
+ digest: bigint
378
+ ): Promise<OpHistoryRecord | undefined> {
379
+ return await this.db.getHistoryRecord(digest);
380
+ }
381
+
382
+ async setOpStatusInHistory(
383
+ digest: bigint,
384
+ status: OperationStatus
385
+ ): Promise<void> {
386
+ await this.db.setStatusForOp(digest, status);
387
+ }
388
+
389
+ async pruneOptimisticNullifiers(): Promise<void> {
390
+ await this.db.pruneOptimisticNFs();
391
+ }
392
+ }
393
+
394
+ export async function getTotalEntityIndexOfNewestNoteInOp(
395
+ db: NocturneDB,
396
+ op: PreSignOperation | SignedOperation
397
+ ): Promise<TotalEntityIndex> {
398
+ // get the max merkle index of any note in any joinsplit in the op
399
+ const maxMerkleIndex = maxArray(
400
+ getMerkleIndicesAndNfsFromOp(op).map(({ merkleIndex }) => merkleIndex)
401
+ );
402
+
403
+ // get the corresponding TotalEntityIndex
404
+ const totalEntityIndex = await db.getTotalEntityIndexForMerkleIndex(
405
+ Number(maxMerkleIndex)
406
+ );
407
+
408
+ if (totalEntityIndex === undefined) {
409
+ throw new Error(
410
+ `totalEntityIndex not found for newest note with merkle index ${maxMerkleIndex}`
411
+ );
412
+ }
413
+
414
+ return totalEntityIndex;
415
+ }