@veil-cash/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +446 -0
- package/dist/cli/index.cjs +6431 -0
- package/dist/index.cjs +1912 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2099 -0
- package/dist/index.d.ts +2099 -0
- package/dist/index.js +1840 -0
- package/dist/index.js.map +1 -0
- package/keys/transaction16.wasm +0 -0
- package/keys/transaction16.zkey +0 -0
- package/keys/transaction2.wasm +0 -0
- package/keys/transaction2.zkey +0 -0
- package/package.json +70 -0
- package/src/abi.ts +631 -0
- package/src/addresses.ts +53 -0
- package/src/balance.ts +266 -0
- package/src/cli/commands/balance.ts +118 -0
- package/src/cli/commands/deposit.ts +115 -0
- package/src/cli/commands/init.ts +147 -0
- package/src/cli/commands/keypair.ts +31 -0
- package/src/cli/commands/private-balance.ts +68 -0
- package/src/cli/commands/queue-balance.ts +58 -0
- package/src/cli/commands/register.ts +119 -0
- package/src/cli/commands/transfer.ts +137 -0
- package/src/cli/commands/withdraw.ts +79 -0
- package/src/cli/config.ts +58 -0
- package/src/cli/errors.ts +114 -0
- package/src/cli/index.ts +52 -0
- package/src/cli/wallet.ts +228 -0
- package/src/deposit.ts +183 -0
- package/src/index.ts +160 -0
- package/src/keypair.ts +170 -0
- package/src/merkle.ts +71 -0
- package/src/prover.ts +176 -0
- package/src/relay.ts +216 -0
- package/src/transaction.ts +260 -0
- package/src/transfer.ts +462 -0
- package/src/types.ts +306 -0
- package/src/utils.ts +151 -0
- package/src/utxo.ts +119 -0
- package/src/withdraw.ts +299 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1912 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ethers = require('ethers');
|
|
4
|
+
var crypto = require('crypto');
|
|
5
|
+
var viem = require('viem');
|
|
6
|
+
var chains = require('viem/chains');
|
|
7
|
+
var MerkleTree = require('fixed-merkle-tree-legacy');
|
|
8
|
+
var snarkjs = require('snarkjs');
|
|
9
|
+
var path = require('path');
|
|
10
|
+
var fs = require('fs');
|
|
11
|
+
var url = require('url');
|
|
12
|
+
|
|
13
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
14
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
|
+
|
|
16
|
+
function _interopNamespace(e) {
|
|
17
|
+
if (e && e.__esModule) return e;
|
|
18
|
+
var n = Object.create(null);
|
|
19
|
+
if (e) {
|
|
20
|
+
Object.keys(e).forEach(function (k) {
|
|
21
|
+
if (k !== 'default') {
|
|
22
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
23
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return e[k]; }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
n.default = e;
|
|
31
|
+
return Object.freeze(n);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
|
|
35
|
+
var MerkleTree__default = /*#__PURE__*/_interopDefault(MerkleTree);
|
|
36
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
37
|
+
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
38
|
+
|
|
39
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
40
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
41
|
+
}) : x)(function(x) {
|
|
42
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
43
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
44
|
+
});
|
|
45
|
+
var circomlib = __require("circomlib");
|
|
46
|
+
var poseidon = circomlib.poseidon;
|
|
47
|
+
var FIELD_SIZE = BigInt(
|
|
48
|
+
"21888242871839275222246405745257275088548364400416034343698204186575808495617"
|
|
49
|
+
);
|
|
50
|
+
var poseidonHash = (items) => BigInt(poseidon(items).toString());
|
|
51
|
+
var poseidonHash2 = (a, b) => poseidonHash([a, b]);
|
|
52
|
+
var randomBN = (nbytes = 31) => {
|
|
53
|
+
const bytes = crypto__namespace.randomBytes(nbytes);
|
|
54
|
+
let hex = "0x";
|
|
55
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
56
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
57
|
+
}
|
|
58
|
+
return BigInt(hex);
|
|
59
|
+
};
|
|
60
|
+
function toFixedHex(number, length = 32) {
|
|
61
|
+
let hexValue;
|
|
62
|
+
if (number instanceof Buffer) {
|
|
63
|
+
hexValue = number.toString("hex");
|
|
64
|
+
} else {
|
|
65
|
+
let bigIntValue = BigInt(number);
|
|
66
|
+
if (bigIntValue < 0n) {
|
|
67
|
+
const maxValue = 1n << BigInt(length * 8);
|
|
68
|
+
bigIntValue = maxValue + bigIntValue;
|
|
69
|
+
}
|
|
70
|
+
hexValue = bigIntValue.toString(16);
|
|
71
|
+
}
|
|
72
|
+
return "0x" + hexValue.padStart(length * 2, "0");
|
|
73
|
+
}
|
|
74
|
+
var toBuffer = (value, length) => {
|
|
75
|
+
const bigIntValue = BigInt(value);
|
|
76
|
+
const hex = bigIntValue.toString(16).padStart(length * 2, "0");
|
|
77
|
+
return Buffer.from(hex, "hex");
|
|
78
|
+
};
|
|
79
|
+
function getExtDataHash(extData) {
|
|
80
|
+
const { ethers: ethers2 } = __require("ethers");
|
|
81
|
+
const abi = ethers2.AbiCoder.defaultAbiCoder();
|
|
82
|
+
const encodedData = abi.encode(
|
|
83
|
+
["tuple(address,int256,address,uint256,bytes,bytes)"],
|
|
84
|
+
[[
|
|
85
|
+
extData.recipient,
|
|
86
|
+
extData.extAmount,
|
|
87
|
+
extData.relayer,
|
|
88
|
+
extData.fee,
|
|
89
|
+
extData.encryptedOutput1,
|
|
90
|
+
extData.encryptedOutput2
|
|
91
|
+
]]
|
|
92
|
+
);
|
|
93
|
+
const hash = ethers2.keccak256(encodedData);
|
|
94
|
+
return BigInt(hash) % FIELD_SIZE;
|
|
95
|
+
}
|
|
96
|
+
function shuffle(array) {
|
|
97
|
+
let currentIndex = array.length;
|
|
98
|
+
let randomIndex;
|
|
99
|
+
while (currentIndex !== 0) {
|
|
100
|
+
randomIndex = Math.floor(Math.random() * currentIndex);
|
|
101
|
+
currentIndex--;
|
|
102
|
+
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
|
|
103
|
+
}
|
|
104
|
+
return array;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/keypair.ts
|
|
108
|
+
var ethSigUtil = __require("eth-sig-util");
|
|
109
|
+
function packEncryptedMessage(encryptedMessage) {
|
|
110
|
+
const nonceBuf = Buffer.from(encryptedMessage.nonce, "base64");
|
|
111
|
+
const ephemPublicKeyBuf = Buffer.from(encryptedMessage.ephemPublicKey, "base64");
|
|
112
|
+
const ciphertextBuf = Buffer.from(encryptedMessage.ciphertext, "base64");
|
|
113
|
+
const messageBuff = Buffer.concat([
|
|
114
|
+
Buffer.alloc(24 - nonceBuf.length),
|
|
115
|
+
nonceBuf,
|
|
116
|
+
Buffer.alloc(32 - ephemPublicKeyBuf.length),
|
|
117
|
+
ephemPublicKeyBuf,
|
|
118
|
+
ciphertextBuf
|
|
119
|
+
]);
|
|
120
|
+
return "0x" + messageBuff.toString("hex");
|
|
121
|
+
}
|
|
122
|
+
function unpackEncryptedMessage(encryptedMessage) {
|
|
123
|
+
if (encryptedMessage.slice(0, 2) === "0x") {
|
|
124
|
+
encryptedMessage = encryptedMessage.slice(2);
|
|
125
|
+
}
|
|
126
|
+
const messageBuff = Buffer.from(encryptedMessage, "hex");
|
|
127
|
+
const nonceBuf = messageBuff.slice(0, 24);
|
|
128
|
+
const ephemPublicKeyBuf = messageBuff.slice(24, 56);
|
|
129
|
+
const ciphertextBuf = messageBuff.slice(56);
|
|
130
|
+
return {
|
|
131
|
+
version: "x25519-xsalsa20-poly1305",
|
|
132
|
+
nonce: nonceBuf.toString("base64"),
|
|
133
|
+
ephemPublicKey: ephemPublicKeyBuf.toString("base64"),
|
|
134
|
+
ciphertext: ciphertextBuf.toString("base64")
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
var Keypair = class _Keypair {
|
|
138
|
+
/** Private key (null if created from public deposit key only) */
|
|
139
|
+
privkey;
|
|
140
|
+
/** Public key (Poseidon hash of private key) */
|
|
141
|
+
pubkey;
|
|
142
|
+
/** x25519 encryption public key */
|
|
143
|
+
encryptionKey;
|
|
144
|
+
/**
|
|
145
|
+
* Create a new Keypair
|
|
146
|
+
* @param privkey - Optional private key. If not provided, generates a random one.
|
|
147
|
+
*/
|
|
148
|
+
constructor(privkey = ethers.ethers.Wallet.createRandom().privateKey) {
|
|
149
|
+
this.privkey = privkey;
|
|
150
|
+
this.pubkey = poseidonHash([this.privkey]);
|
|
151
|
+
this.encryptionKey = ethSigUtil.getEncryptionPublicKey(privkey.slice(2));
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get the deposit key for this keypair
|
|
155
|
+
* This is what you register on-chain
|
|
156
|
+
* @returns Deposit key as hex string (130 chars with 0x prefix)
|
|
157
|
+
*/
|
|
158
|
+
toString() {
|
|
159
|
+
return toFixedHex(this.pubkey) + Buffer.from(this.encryptionKey, "base64").toString("hex");
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Alias for toString() - returns the deposit key
|
|
163
|
+
* @returns Deposit key as hex string
|
|
164
|
+
*/
|
|
165
|
+
depositKey() {
|
|
166
|
+
return this.toString();
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Create a Keypair from a public deposit key (without private key)
|
|
170
|
+
* Useful for sending transfers to other users
|
|
171
|
+
* @param str - Deposit key (128 or 130 hex chars)
|
|
172
|
+
* @returns Keypair instance (privkey will be null)
|
|
173
|
+
*/
|
|
174
|
+
static fromString(str) {
|
|
175
|
+
if (str.length === 130) {
|
|
176
|
+
str = str.slice(2);
|
|
177
|
+
}
|
|
178
|
+
if (str.length !== 128) {
|
|
179
|
+
throw new Error("Invalid deposit key length. Expected 128 hex chars (or 130 with 0x prefix)");
|
|
180
|
+
}
|
|
181
|
+
return Object.assign(new _Keypair(), {
|
|
182
|
+
privkey: null,
|
|
183
|
+
pubkey: BigInt("0x" + str.slice(0, 64)),
|
|
184
|
+
encryptionKey: Buffer.from(str.slice(64, 128), "hex").toString("base64")
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Sign a message using the private key
|
|
189
|
+
* @param commitment - Commitment hash
|
|
190
|
+
* @param merklePath - Merkle path
|
|
191
|
+
* @returns Signature as bigint
|
|
192
|
+
*/
|
|
193
|
+
sign(commitment, merklePath) {
|
|
194
|
+
if (!this.privkey) {
|
|
195
|
+
throw new Error("Cannot sign without private key");
|
|
196
|
+
}
|
|
197
|
+
return poseidonHash([this.privkey, commitment, merklePath]);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Encrypt data using the encryption key
|
|
201
|
+
* @param bytes - Data to encrypt
|
|
202
|
+
* @returns Encrypted data as hex string
|
|
203
|
+
*/
|
|
204
|
+
encrypt(bytes) {
|
|
205
|
+
return packEncryptedMessage(
|
|
206
|
+
ethSigUtil.encrypt(
|
|
207
|
+
this.encryptionKey,
|
|
208
|
+
{ data: bytes.toString("base64") },
|
|
209
|
+
"x25519-xsalsa20-poly1305"
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Decrypt data using the private key
|
|
215
|
+
* @param data - Encrypted data as hex string
|
|
216
|
+
* @returns Decrypted data as Buffer
|
|
217
|
+
*/
|
|
218
|
+
decrypt(data) {
|
|
219
|
+
if (!this.privkey) {
|
|
220
|
+
throw new Error("Cannot decrypt without private key");
|
|
221
|
+
}
|
|
222
|
+
return Buffer.from(
|
|
223
|
+
ethSigUtil.decrypt(unpackEncryptedMessage(data), this.privkey.slice(2)),
|
|
224
|
+
"base64"
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// src/utxo.ts
|
|
230
|
+
var Utxo = class _Utxo {
|
|
231
|
+
amount;
|
|
232
|
+
blinding;
|
|
233
|
+
keypair;
|
|
234
|
+
index;
|
|
235
|
+
_commitment;
|
|
236
|
+
_nullifier;
|
|
237
|
+
/**
|
|
238
|
+
* Create a new UTXO
|
|
239
|
+
* @param params - UTXO parameters
|
|
240
|
+
*/
|
|
241
|
+
constructor(params = {}) {
|
|
242
|
+
const { amount = 0, keypair = new Keypair(), blinding = randomBN(), index } = params;
|
|
243
|
+
this.amount = BigInt(amount);
|
|
244
|
+
this.blinding = BigInt(blinding);
|
|
245
|
+
this.keypair = keypair;
|
|
246
|
+
this.index = index;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get the commitment for this UTXO
|
|
250
|
+
* commitment = poseidonHash([amount, pubkey, blinding])
|
|
251
|
+
* @returns Commitment as bigint
|
|
252
|
+
*/
|
|
253
|
+
getCommitment() {
|
|
254
|
+
if (!this._commitment) {
|
|
255
|
+
this._commitment = poseidonHash([this.amount, this.keypair.pubkey, this.blinding]);
|
|
256
|
+
}
|
|
257
|
+
return this._commitment;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get the nullifier for this UTXO
|
|
261
|
+
* Requires index and private key to be set
|
|
262
|
+
* nullifier = poseidonHash([commitment, index, signature])
|
|
263
|
+
* @returns Nullifier as bigint
|
|
264
|
+
*/
|
|
265
|
+
getNullifier() {
|
|
266
|
+
if (!this._nullifier) {
|
|
267
|
+
if (this.amount > 0n && (this.index === void 0 || !this.keypair.privkey)) {
|
|
268
|
+
throw new Error("Cannot compute nullifier without UTXO index or private key");
|
|
269
|
+
}
|
|
270
|
+
const signature = this.keypair.privkey ? this.keypair.sign(this.getCommitment(), this.index || 0) : 0n;
|
|
271
|
+
this._nullifier = poseidonHash([this.getCommitment(), this.index || 0, signature]);
|
|
272
|
+
}
|
|
273
|
+
return this._nullifier;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Encrypt UTXO data using the keypair
|
|
277
|
+
* @returns Encrypted data as 0x-prefixed hex string
|
|
278
|
+
*/
|
|
279
|
+
encrypt() {
|
|
280
|
+
const bytes = Buffer.concat([
|
|
281
|
+
toBuffer(this.amount, 31),
|
|
282
|
+
toBuffer(this.blinding, 31)
|
|
283
|
+
]);
|
|
284
|
+
return this.keypair.encrypt(bytes);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Decrypt an encrypted output to create a UTXO
|
|
288
|
+
* Only succeeds if the keypair owns this UTXO
|
|
289
|
+
*
|
|
290
|
+
* @param data - Encrypted output as hex string
|
|
291
|
+
* @param keypair - Keypair to decrypt with
|
|
292
|
+
* @returns Decrypted UTXO
|
|
293
|
+
* @throws If decryption fails (wrong keypair)
|
|
294
|
+
*/
|
|
295
|
+
static decrypt(data, keypair) {
|
|
296
|
+
const buf = keypair.decrypt(data);
|
|
297
|
+
return new _Utxo({
|
|
298
|
+
amount: BigInt("0x" + buf.slice(0, 31).toString("hex")),
|
|
299
|
+
blinding: BigInt("0x" + buf.slice(31, 62).toString("hex")),
|
|
300
|
+
keypair
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// src/addresses.ts
|
|
306
|
+
var ADDRESSES = {
|
|
307
|
+
entry: "0xc2535c547B64b997A4BD9202E1663deaF11c78a5",
|
|
308
|
+
ethPool: "0x293dCda114533FF8f477271c5cA517209FFDEEe7",
|
|
309
|
+
ethQueue: "0xA4a926A2E7a22c38e8DFC6744A61a6aA8b06B230",
|
|
310
|
+
usdcPool: "0x5c50d58E49C59d112680c187De2Bf989d2a91242",
|
|
311
|
+
usdcQueue: "0x5530241b24504bF05C9a22e95A1F5458888e6a9B",
|
|
312
|
+
usdcToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
313
|
+
chainId: 8453,
|
|
314
|
+
relayUrl: "https://veil-relay.up.railway.app"
|
|
315
|
+
};
|
|
316
|
+
var POOL_CONFIG = {
|
|
317
|
+
eth: {
|
|
318
|
+
decimals: 18,
|
|
319
|
+
displayDecimals: 4,
|
|
320
|
+
symbol: "ETH",
|
|
321
|
+
name: "Ethereum"
|
|
322
|
+
},
|
|
323
|
+
usdc: {
|
|
324
|
+
decimals: 6,
|
|
325
|
+
displayDecimals: 2,
|
|
326
|
+
symbol: "USDC",
|
|
327
|
+
name: "USD Coin"
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
function getAddresses() {
|
|
331
|
+
return ADDRESSES;
|
|
332
|
+
}
|
|
333
|
+
function getRelayUrl() {
|
|
334
|
+
return ADDRESSES.relayUrl;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/abi.ts
|
|
338
|
+
var ENTRY_ABI = [
|
|
339
|
+
// ============ ERRORS ============
|
|
340
|
+
{ inputs: [], name: "DepositsDisabled", type: "error" },
|
|
341
|
+
{ inputs: [], name: "FeeTransferFailed", type: "error" },
|
|
342
|
+
{ inputs: [], name: "InvalidDepositKey", type: "error" },
|
|
343
|
+
{ inputs: [], name: "InvalidDepositKeyForUser", type: "error" },
|
|
344
|
+
{ inputs: [], name: "InvalidInitialization", type: "error" },
|
|
345
|
+
{ inputs: [], name: "MinimumDepositNotMet", type: "error" },
|
|
346
|
+
{ inputs: [], name: "NotAllowedToDeposit", type: "error" },
|
|
347
|
+
{ inputs: [], name: "NotInitializing", type: "error" },
|
|
348
|
+
{ inputs: [], name: "OnlyOperatorAllowed", type: "error" },
|
|
349
|
+
{ inputs: [], name: "OnlyOwnerCanRegister", type: "error" },
|
|
350
|
+
{ inputs: [], name: "OnlyQueueContractAllowed", type: "error" },
|
|
351
|
+
{ inputs: [{ name: "owner", type: "address" }], name: "OwnableInvalidOwner", type: "error" },
|
|
352
|
+
{ inputs: [{ name: "account", type: "address" }], name: "OwnableUnauthorizedAccount", type: "error" },
|
|
353
|
+
{ inputs: [], name: "ReentrancyGuardReentrantCall", type: "error" },
|
|
354
|
+
{ inputs: [], name: "USDCTransferFailed", type: "error" },
|
|
355
|
+
{ inputs: [], name: "UserAlreadyRegistered", type: "error" },
|
|
356
|
+
{ inputs: [], name: "UserNotRegistered", type: "error" },
|
|
357
|
+
// ============ EVENTS ============
|
|
358
|
+
{
|
|
359
|
+
anonymous: false,
|
|
360
|
+
inputs: [
|
|
361
|
+
{ indexed: true, name: "owner", type: "address" },
|
|
362
|
+
{ indexed: false, name: "key", type: "bytes" }
|
|
363
|
+
],
|
|
364
|
+
name: "DepositKey",
|
|
365
|
+
type: "event"
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
anonymous: false,
|
|
369
|
+
inputs: [
|
|
370
|
+
{ indexed: true, name: "depositor", type: "address" },
|
|
371
|
+
{ indexed: false, name: "amount", type: "uint256" }
|
|
372
|
+
],
|
|
373
|
+
name: "DepositedETH",
|
|
374
|
+
type: "event"
|
|
375
|
+
},
|
|
376
|
+
// ============ FUNCTIONS ============
|
|
377
|
+
// Register deposit key
|
|
378
|
+
{
|
|
379
|
+
inputs: [
|
|
380
|
+
{
|
|
381
|
+
components: [
|
|
382
|
+
{ name: "owner", type: "address" },
|
|
383
|
+
{ name: "depositKey", type: "bytes" }
|
|
384
|
+
],
|
|
385
|
+
name: "_account",
|
|
386
|
+
type: "tuple"
|
|
387
|
+
}
|
|
388
|
+
],
|
|
389
|
+
name: "register",
|
|
390
|
+
outputs: [],
|
|
391
|
+
stateMutability: "nonpayable",
|
|
392
|
+
type: "function"
|
|
393
|
+
},
|
|
394
|
+
// Queue ETH deposit
|
|
395
|
+
{
|
|
396
|
+
inputs: [{ name: "_depositKey", type: "bytes" }],
|
|
397
|
+
name: "queueETH",
|
|
398
|
+
outputs: [],
|
|
399
|
+
stateMutability: "payable",
|
|
400
|
+
type: "function"
|
|
401
|
+
},
|
|
402
|
+
// Queue USDC deposit
|
|
403
|
+
{
|
|
404
|
+
inputs: [
|
|
405
|
+
{ name: "_amount", type: "uint256" },
|
|
406
|
+
{ name: "_depositKey", type: "bytes" }
|
|
407
|
+
],
|
|
408
|
+
name: "queueUSDC",
|
|
409
|
+
outputs: [],
|
|
410
|
+
stateMutability: "nonpayable",
|
|
411
|
+
type: "function"
|
|
412
|
+
},
|
|
413
|
+
// Read deposit keys
|
|
414
|
+
{
|
|
415
|
+
inputs: [{ name: "", type: "address" }],
|
|
416
|
+
name: "depositKeys",
|
|
417
|
+
outputs: [{ name: "", type: "bytes" }],
|
|
418
|
+
stateMutability: "view",
|
|
419
|
+
type: "function"
|
|
420
|
+
},
|
|
421
|
+
// Check if deposits are enabled
|
|
422
|
+
{
|
|
423
|
+
inputs: [],
|
|
424
|
+
name: "depositETHEnabled",
|
|
425
|
+
outputs: [{ name: "", type: "bool" }],
|
|
426
|
+
stateMutability: "view",
|
|
427
|
+
type: "function"
|
|
428
|
+
},
|
|
429
|
+
// Get deposit fee
|
|
430
|
+
{
|
|
431
|
+
inputs: [],
|
|
432
|
+
name: "depositFee",
|
|
433
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
434
|
+
stateMutability: "view",
|
|
435
|
+
type: "function"
|
|
436
|
+
},
|
|
437
|
+
// Get minimum deposit
|
|
438
|
+
{
|
|
439
|
+
inputs: [],
|
|
440
|
+
name: "minimumDeposit",
|
|
441
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
442
|
+
stateMutability: "view",
|
|
443
|
+
type: "function"
|
|
444
|
+
},
|
|
445
|
+
// Calculate fee and net deposit
|
|
446
|
+
{
|
|
447
|
+
inputs: [{ name: "_totalAmount", type: "uint256" }],
|
|
448
|
+
name: "getFeeAndNetDeposit",
|
|
449
|
+
outputs: [
|
|
450
|
+
{ name: "netDeposit", type: "uint256" },
|
|
451
|
+
{ name: "fee", type: "uint256" }
|
|
452
|
+
],
|
|
453
|
+
stateMutability: "view",
|
|
454
|
+
type: "function"
|
|
455
|
+
},
|
|
456
|
+
// Calculate deposit amount with fee
|
|
457
|
+
{
|
|
458
|
+
inputs: [{ name: "_netDepositAmount", type: "uint256" }],
|
|
459
|
+
name: "getDepositAmountWithFee",
|
|
460
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
461
|
+
stateMutability: "view",
|
|
462
|
+
type: "function"
|
|
463
|
+
},
|
|
464
|
+
// Check if user is allowed depositor
|
|
465
|
+
{
|
|
466
|
+
inputs: [{ name: "_depositor", type: "address" }],
|
|
467
|
+
name: "isAllowedDepositor",
|
|
468
|
+
outputs: [{ name: "", type: "bool" }],
|
|
469
|
+
stateMutability: "view",
|
|
470
|
+
type: "function"
|
|
471
|
+
}
|
|
472
|
+
];
|
|
473
|
+
var QUEUE_ABI = [
|
|
474
|
+
// Get all pending deposit nonces
|
|
475
|
+
{
|
|
476
|
+
inputs: [],
|
|
477
|
+
name: "getPendingDeposits",
|
|
478
|
+
outputs: [{ name: "nonces", type: "uint256[]" }],
|
|
479
|
+
stateMutability: "view",
|
|
480
|
+
type: "function"
|
|
481
|
+
},
|
|
482
|
+
// Get deposit details by nonce
|
|
483
|
+
{
|
|
484
|
+
inputs: [{ name: "_nonce", type: "uint256" }],
|
|
485
|
+
name: "getDeposit",
|
|
486
|
+
outputs: [
|
|
487
|
+
{
|
|
488
|
+
components: [
|
|
489
|
+
{ name: "fallbackReceiver", type: "address" },
|
|
490
|
+
{ name: "amountIn", type: "uint256" },
|
|
491
|
+
{ name: "fee", type: "uint256" },
|
|
492
|
+
{ name: "shieldAmount", type: "uint256" },
|
|
493
|
+
{ name: "timestamp", type: "uint256" },
|
|
494
|
+
{ name: "status", type: "uint8" },
|
|
495
|
+
{ name: "depositKey", type: "bytes" }
|
|
496
|
+
],
|
|
497
|
+
name: "deposit",
|
|
498
|
+
type: "tuple"
|
|
499
|
+
}
|
|
500
|
+
],
|
|
501
|
+
stateMutability: "view",
|
|
502
|
+
type: "function"
|
|
503
|
+
},
|
|
504
|
+
// Get current nonce counter
|
|
505
|
+
{
|
|
506
|
+
inputs: [],
|
|
507
|
+
name: "depositQueueNonce",
|
|
508
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
509
|
+
stateMutability: "view",
|
|
510
|
+
type: "function"
|
|
511
|
+
},
|
|
512
|
+
// Get pending count
|
|
513
|
+
{
|
|
514
|
+
inputs: [],
|
|
515
|
+
name: "getPendingCount",
|
|
516
|
+
outputs: [{ name: "count", type: "uint256" }],
|
|
517
|
+
stateMutability: "view",
|
|
518
|
+
type: "function"
|
|
519
|
+
}
|
|
520
|
+
];
|
|
521
|
+
var POOL_ABI = [
|
|
522
|
+
// ============ ERRORS ============
|
|
523
|
+
{ inputs: [], name: "CannotWithdrawToZeroAddress", type: "error" },
|
|
524
|
+
{ inputs: [], name: "ETHTransferFailed", type: "error" },
|
|
525
|
+
{ inputs: [], name: "IncorrectExternalDataHash", type: "error" },
|
|
526
|
+
{ inputs: [], name: "InputAlreadySpent", type: "error" },
|
|
527
|
+
{ inputs: [], name: "InvalidExtAmount", type: "error" },
|
|
528
|
+
{ inputs: [], name: "InvalidFee", type: "error" },
|
|
529
|
+
{ inputs: [], name: "InvalidMerkleRoot", type: "error" },
|
|
530
|
+
{ inputs: [], name: "InvalidPublicAmount", type: "error" },
|
|
531
|
+
{ inputs: [], name: "InvalidRange", type: "error" },
|
|
532
|
+
{ inputs: [], name: "InvalidTransactionProof", type: "error" },
|
|
533
|
+
{ inputs: [], name: "OnlyForDeposits", type: "error" },
|
|
534
|
+
{ inputs: [], name: "OnlyForTransfers", type: "error" },
|
|
535
|
+
{ inputs: [], name: "OnlyForWithdrawals", type: "error" },
|
|
536
|
+
{ inputs: [], name: "OnlyValidatorContractAllowed", type: "error" },
|
|
537
|
+
{ inputs: [], name: "OnlyWETHContractAllowed", type: "error" },
|
|
538
|
+
{ inputs: [{ name: "owner", type: "address" }], name: "OwnableInvalidOwner", type: "error" },
|
|
539
|
+
{ inputs: [{ name: "account", type: "address" }], name: "OwnableUnauthorizedAccount", type: "error" },
|
|
540
|
+
{ inputs: [], name: "ProofAmountMismatch", type: "error" },
|
|
541
|
+
{ inputs: [], name: "ReentrancyGuardReentrantCall", type: "error" },
|
|
542
|
+
{ inputs: [], name: "UnsupportedInputCount", type: "error" },
|
|
543
|
+
{ inputs: [], name: "WETHDepositFailed", type: "error" },
|
|
544
|
+
{ inputs: [], name: "WETHUnwrapFailed", type: "error" },
|
|
545
|
+
// ============ EVENTS ============
|
|
546
|
+
{
|
|
547
|
+
anonymous: false,
|
|
548
|
+
inputs: [
|
|
549
|
+
{ indexed: false, name: "commitment", type: "bytes32" },
|
|
550
|
+
{ indexed: false, name: "index", type: "uint256" },
|
|
551
|
+
{ indexed: false, name: "encryptedOutput", type: "bytes" }
|
|
552
|
+
],
|
|
553
|
+
name: "NewCommitment",
|
|
554
|
+
type: "event"
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
anonymous: false,
|
|
558
|
+
inputs: [{ indexed: false, name: "nullifier", type: "bytes32" }],
|
|
559
|
+
name: "NewNullifier",
|
|
560
|
+
type: "event"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
anonymous: false,
|
|
564
|
+
inputs: [
|
|
565
|
+
{ indexed: true, name: "previousOwner", type: "address" },
|
|
566
|
+
{ indexed: true, name: "newOwner", type: "address" }
|
|
567
|
+
],
|
|
568
|
+
name: "OwnershipTransferred",
|
|
569
|
+
type: "event"
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
anonymous: false,
|
|
573
|
+
inputs: [{ indexed: true, name: "newValidatorContract", type: "address" }],
|
|
574
|
+
name: "ValidatorContractUpdated",
|
|
575
|
+
type: "event"
|
|
576
|
+
},
|
|
577
|
+
// ============ VIEW FUNCTIONS ============
|
|
578
|
+
{
|
|
579
|
+
inputs: [],
|
|
580
|
+
name: "FIELD_SIZE",
|
|
581
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
582
|
+
stateMutability: "view",
|
|
583
|
+
type: "function"
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
inputs: [],
|
|
587
|
+
name: "ROOT_HISTORY_SIZE",
|
|
588
|
+
outputs: [{ name: "", type: "uint32" }],
|
|
589
|
+
stateMutability: "view",
|
|
590
|
+
type: "function"
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
inputs: [],
|
|
594
|
+
name: "ZERO_VALUE",
|
|
595
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
596
|
+
stateMutability: "view",
|
|
597
|
+
type: "function"
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
inputs: [{ name: "", type: "uint256" }],
|
|
601
|
+
name: "commitments",
|
|
602
|
+
outputs: [{ name: "", type: "bytes32" }],
|
|
603
|
+
stateMutability: "view",
|
|
604
|
+
type: "function"
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
inputs: [],
|
|
608
|
+
name: "currentRootIndex",
|
|
609
|
+
outputs: [{ name: "", type: "uint32" }],
|
|
610
|
+
stateMutability: "view",
|
|
611
|
+
type: "function"
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
inputs: [{ name: "", type: "uint256" }],
|
|
615
|
+
name: "encryptedOutputs",
|
|
616
|
+
outputs: [{ name: "", type: "bytes" }],
|
|
617
|
+
stateMutability: "view",
|
|
618
|
+
type: "function"
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
inputs: [{ name: "", type: "uint256" }],
|
|
622
|
+
name: "filledSubtrees",
|
|
623
|
+
outputs: [{ name: "", type: "bytes32" }],
|
|
624
|
+
stateMutability: "view",
|
|
625
|
+
type: "function"
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
inputs: [
|
|
629
|
+
{ name: "startIndex", type: "uint256" },
|
|
630
|
+
{ name: "endIndex", type: "uint256" }
|
|
631
|
+
],
|
|
632
|
+
name: "getCommitments",
|
|
633
|
+
outputs: [{ name: "", type: "bytes32[]" }],
|
|
634
|
+
stateMutability: "view",
|
|
635
|
+
type: "function"
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
inputs: [
|
|
639
|
+
{ name: "startIndex", type: "uint256" },
|
|
640
|
+
{ name: "endIndex", type: "uint256" }
|
|
641
|
+
],
|
|
642
|
+
name: "getEncryptedOutputs",
|
|
643
|
+
outputs: [{ name: "", type: "bytes[]" }],
|
|
644
|
+
stateMutability: "view",
|
|
645
|
+
type: "function"
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
inputs: [],
|
|
649
|
+
name: "getLastRoot",
|
|
650
|
+
outputs: [{ name: "", type: "bytes32" }],
|
|
651
|
+
stateMutability: "view",
|
|
652
|
+
type: "function"
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
inputs: [
|
|
656
|
+
{ name: "_left", type: "bytes32" },
|
|
657
|
+
{ name: "_right", type: "bytes32" }
|
|
658
|
+
],
|
|
659
|
+
name: "hashLeftRight",
|
|
660
|
+
outputs: [{ name: "", type: "bytes32" }],
|
|
661
|
+
stateMutability: "view",
|
|
662
|
+
type: "function"
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
inputs: [],
|
|
666
|
+
name: "hasher",
|
|
667
|
+
outputs: [{ name: "", type: "address" }],
|
|
668
|
+
stateMutability: "view",
|
|
669
|
+
type: "function"
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
inputs: [{ name: "_root", type: "bytes32" }],
|
|
673
|
+
name: "isKnownRoot",
|
|
674
|
+
outputs: [{ name: "", type: "bool" }],
|
|
675
|
+
stateMutability: "view",
|
|
676
|
+
type: "function"
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
inputs: [{ name: "_nullifierHash", type: "bytes32" }],
|
|
680
|
+
name: "isSpent",
|
|
681
|
+
outputs: [{ name: "", type: "bool" }],
|
|
682
|
+
stateMutability: "view",
|
|
683
|
+
type: "function"
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
inputs: [],
|
|
687
|
+
name: "levels",
|
|
688
|
+
outputs: [{ name: "", type: "uint32" }],
|
|
689
|
+
stateMutability: "view",
|
|
690
|
+
type: "function"
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
inputs: [],
|
|
694
|
+
name: "nextIndex",
|
|
695
|
+
outputs: [{ name: "", type: "uint32" }],
|
|
696
|
+
stateMutability: "view",
|
|
697
|
+
type: "function"
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
inputs: [{ name: "", type: "bytes32" }],
|
|
701
|
+
name: "nullifierHashes",
|
|
702
|
+
outputs: [{ name: "", type: "bool" }],
|
|
703
|
+
stateMutability: "view",
|
|
704
|
+
type: "function"
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
inputs: [],
|
|
708
|
+
name: "owner",
|
|
709
|
+
outputs: [{ name: "", type: "address" }],
|
|
710
|
+
stateMutability: "view",
|
|
711
|
+
type: "function"
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
inputs: [{ name: "", type: "uint256" }],
|
|
715
|
+
name: "roots",
|
|
716
|
+
outputs: [{ name: "", type: "bytes32" }],
|
|
717
|
+
stateMutability: "view",
|
|
718
|
+
type: "function"
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
inputs: [],
|
|
722
|
+
name: "validatorContract",
|
|
723
|
+
outputs: [{ name: "", type: "address" }],
|
|
724
|
+
stateMutability: "view",
|
|
725
|
+
type: "function"
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
inputs: [],
|
|
729
|
+
name: "verifier16",
|
|
730
|
+
outputs: [{ name: "", type: "address" }],
|
|
731
|
+
stateMutability: "view",
|
|
732
|
+
type: "function"
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
inputs: [],
|
|
736
|
+
name: "verifier2",
|
|
737
|
+
outputs: [{ name: "", type: "address" }],
|
|
738
|
+
stateMutability: "view",
|
|
739
|
+
type: "function"
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
inputs: [],
|
|
743
|
+
name: "weth",
|
|
744
|
+
outputs: [{ name: "", type: "address" }],
|
|
745
|
+
stateMutability: "view",
|
|
746
|
+
type: "function"
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
inputs: [{ name: "i", type: "uint256" }],
|
|
750
|
+
name: "zeros",
|
|
751
|
+
outputs: [{ name: "", type: "bytes32" }],
|
|
752
|
+
stateMutability: "pure",
|
|
753
|
+
type: "function"
|
|
754
|
+
},
|
|
755
|
+
// ============ PURE FUNCTIONS ============
|
|
756
|
+
{
|
|
757
|
+
inputs: [
|
|
758
|
+
{ name: "_extAmount", type: "int256" },
|
|
759
|
+
{ name: "_fee", type: "uint256" }
|
|
760
|
+
],
|
|
761
|
+
name: "calculatePublicAmount",
|
|
762
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
763
|
+
stateMutability: "pure",
|
|
764
|
+
type: "function"
|
|
765
|
+
},
|
|
766
|
+
// ============ WRITE FUNCTIONS ============
|
|
767
|
+
{
|
|
768
|
+
inputs: [
|
|
769
|
+
{
|
|
770
|
+
components: [
|
|
771
|
+
{ name: "proof", type: "bytes" },
|
|
772
|
+
{ name: "root", type: "bytes32" },
|
|
773
|
+
{ name: "inputNullifiers", type: "bytes32[]" },
|
|
774
|
+
{ name: "outputCommitments", type: "bytes32[2]" },
|
|
775
|
+
{ name: "publicAmount", type: "uint256" },
|
|
776
|
+
{ name: "extDataHash", type: "bytes32" }
|
|
777
|
+
],
|
|
778
|
+
name: "_args",
|
|
779
|
+
type: "tuple"
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
components: [
|
|
783
|
+
{ name: "recipient", type: "address" },
|
|
784
|
+
{ name: "extAmount", type: "int256" },
|
|
785
|
+
{ name: "relayer", type: "address" },
|
|
786
|
+
{ name: "fee", type: "uint256" },
|
|
787
|
+
{ name: "encryptedOutput1", type: "bytes" },
|
|
788
|
+
{ name: "encryptedOutput2", type: "bytes" }
|
|
789
|
+
],
|
|
790
|
+
name: "_extData",
|
|
791
|
+
type: "tuple"
|
|
792
|
+
}
|
|
793
|
+
],
|
|
794
|
+
name: "depositETH",
|
|
795
|
+
outputs: [],
|
|
796
|
+
stateMutability: "payable",
|
|
797
|
+
type: "function"
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
inputs: [
|
|
801
|
+
{
|
|
802
|
+
components: [
|
|
803
|
+
{ name: "proof", type: "bytes" },
|
|
804
|
+
{ name: "root", type: "bytes32" },
|
|
805
|
+
{ name: "inputNullifiers", type: "bytes32[]" },
|
|
806
|
+
{ name: "outputCommitments", type: "bytes32[2]" },
|
|
807
|
+
{ name: "publicAmount", type: "uint256" },
|
|
808
|
+
{ name: "extDataHash", type: "bytes32" }
|
|
809
|
+
],
|
|
810
|
+
name: "_args",
|
|
811
|
+
type: "tuple"
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
components: [
|
|
815
|
+
{ name: "recipient", type: "address" },
|
|
816
|
+
{ name: "extAmount", type: "int256" },
|
|
817
|
+
{ name: "relayer", type: "address" },
|
|
818
|
+
{ name: "fee", type: "uint256" },
|
|
819
|
+
{ name: "encryptedOutput1", type: "bytes" },
|
|
820
|
+
{ name: "encryptedOutput2", type: "bytes" }
|
|
821
|
+
],
|
|
822
|
+
name: "_extData",
|
|
823
|
+
type: "tuple"
|
|
824
|
+
}
|
|
825
|
+
],
|
|
826
|
+
name: "transactETH",
|
|
827
|
+
outputs: [],
|
|
828
|
+
stateMutability: "nonpayable",
|
|
829
|
+
type: "function"
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
inputs: [
|
|
833
|
+
{
|
|
834
|
+
components: [
|
|
835
|
+
{ name: "proof", type: "bytes" },
|
|
836
|
+
{ name: "root", type: "bytes32" },
|
|
837
|
+
{ name: "inputNullifiers", type: "bytes32[]" },
|
|
838
|
+
{ name: "outputCommitments", type: "bytes32[2]" },
|
|
839
|
+
{ name: "publicAmount", type: "uint256" },
|
|
840
|
+
{ name: "extDataHash", type: "bytes32" }
|
|
841
|
+
],
|
|
842
|
+
name: "_args",
|
|
843
|
+
type: "tuple"
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
components: [
|
|
847
|
+
{ name: "recipient", type: "address" },
|
|
848
|
+
{ name: "extAmount", type: "int256" },
|
|
849
|
+
{ name: "relayer", type: "address" },
|
|
850
|
+
{ name: "fee", type: "uint256" },
|
|
851
|
+
{ name: "encryptedOutput1", type: "bytes" },
|
|
852
|
+
{ name: "encryptedOutput2", type: "bytes" }
|
|
853
|
+
],
|
|
854
|
+
name: "_extData",
|
|
855
|
+
type: "tuple"
|
|
856
|
+
}
|
|
857
|
+
],
|
|
858
|
+
name: "withdrawETH",
|
|
859
|
+
outputs: [],
|
|
860
|
+
stateMutability: "nonpayable",
|
|
861
|
+
type: "function"
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
inputs: [
|
|
865
|
+
{
|
|
866
|
+
components: [
|
|
867
|
+
{ name: "proof", type: "bytes" },
|
|
868
|
+
{ name: "root", type: "bytes32" },
|
|
869
|
+
{ name: "inputNullifiers", type: "bytes32[]" },
|
|
870
|
+
{ name: "outputCommitments", type: "bytes32[2]" },
|
|
871
|
+
{ name: "publicAmount", type: "uint256" },
|
|
872
|
+
{ name: "extDataHash", type: "bytes32" }
|
|
873
|
+
],
|
|
874
|
+
name: "_args",
|
|
875
|
+
type: "tuple"
|
|
876
|
+
}
|
|
877
|
+
],
|
|
878
|
+
name: "verifyProof",
|
|
879
|
+
outputs: [{ name: "", type: "bool" }],
|
|
880
|
+
stateMutability: "view",
|
|
881
|
+
type: "function"
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
inputs: [],
|
|
885
|
+
name: "renounceOwnership",
|
|
886
|
+
outputs: [],
|
|
887
|
+
stateMutability: "nonpayable",
|
|
888
|
+
type: "function"
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
inputs: [{ name: "newOwner", type: "address" }],
|
|
892
|
+
name: "transferOwnership",
|
|
893
|
+
outputs: [],
|
|
894
|
+
stateMutability: "nonpayable",
|
|
895
|
+
type: "function"
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
inputs: [{ name: "_newValidator", type: "address" }],
|
|
899
|
+
name: "updateValidatorContract",
|
|
900
|
+
outputs: [],
|
|
901
|
+
stateMutability: "nonpayable",
|
|
902
|
+
type: "function"
|
|
903
|
+
},
|
|
904
|
+
// ============ RECEIVE ============
|
|
905
|
+
{ stateMutability: "payable", type: "receive" }
|
|
906
|
+
];
|
|
907
|
+
var ERC20_ABI = [
|
|
908
|
+
{
|
|
909
|
+
inputs: [
|
|
910
|
+
{ name: "spender", type: "address" },
|
|
911
|
+
{ name: "amount", type: "uint256" }
|
|
912
|
+
],
|
|
913
|
+
name: "approve",
|
|
914
|
+
outputs: [{ name: "", type: "bool" }],
|
|
915
|
+
stateMutability: "nonpayable",
|
|
916
|
+
type: "function"
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
inputs: [{ name: "account", type: "address" }],
|
|
920
|
+
name: "balanceOf",
|
|
921
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
922
|
+
stateMutability: "view",
|
|
923
|
+
type: "function"
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
inputs: [
|
|
927
|
+
{ name: "owner", type: "address" },
|
|
928
|
+
{ name: "spender", type: "address" }
|
|
929
|
+
],
|
|
930
|
+
name: "allowance",
|
|
931
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
932
|
+
stateMutability: "view",
|
|
933
|
+
type: "function"
|
|
934
|
+
}
|
|
935
|
+
];
|
|
936
|
+
|
|
937
|
+
// src/deposit.ts
|
|
938
|
+
function buildRegisterTx(depositKey, ownerAddress) {
|
|
939
|
+
const addresses = getAddresses();
|
|
940
|
+
const data = viem.encodeFunctionData({
|
|
941
|
+
abi: ENTRY_ABI,
|
|
942
|
+
functionName: "register",
|
|
943
|
+
args: [{
|
|
944
|
+
owner: ownerAddress,
|
|
945
|
+
depositKey
|
|
946
|
+
}]
|
|
947
|
+
});
|
|
948
|
+
return {
|
|
949
|
+
to: addresses.entry,
|
|
950
|
+
data
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
function buildDepositETHTx(options) {
|
|
954
|
+
const { depositKey, amount } = options;
|
|
955
|
+
const addresses = getAddresses();
|
|
956
|
+
const value = viem.parseEther(amount);
|
|
957
|
+
const data = viem.encodeFunctionData({
|
|
958
|
+
abi: ENTRY_ABI,
|
|
959
|
+
functionName: "queueETH",
|
|
960
|
+
args: [depositKey]
|
|
961
|
+
});
|
|
962
|
+
return {
|
|
963
|
+
to: addresses.entry,
|
|
964
|
+
data,
|
|
965
|
+
value
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
function buildApproveUSDCTx(options) {
|
|
969
|
+
const { amount } = options;
|
|
970
|
+
const addresses = getAddresses();
|
|
971
|
+
const amountWei = viem.parseUnits(amount, POOL_CONFIG.usdc.decimals);
|
|
972
|
+
const data = viem.encodeFunctionData({
|
|
973
|
+
abi: ERC20_ABI,
|
|
974
|
+
functionName: "approve",
|
|
975
|
+
args: [addresses.entry, amountWei]
|
|
976
|
+
});
|
|
977
|
+
return {
|
|
978
|
+
to: addresses.usdcToken,
|
|
979
|
+
data
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
function buildDepositUSDCTx(options) {
|
|
983
|
+
const { depositKey, amount } = options;
|
|
984
|
+
const addresses = getAddresses();
|
|
985
|
+
const amountWei = viem.parseUnits(amount, POOL_CONFIG.usdc.decimals);
|
|
986
|
+
const data = viem.encodeFunctionData({
|
|
987
|
+
abi: ENTRY_ABI,
|
|
988
|
+
functionName: "queueUSDC",
|
|
989
|
+
args: [amountWei, depositKey]
|
|
990
|
+
});
|
|
991
|
+
return {
|
|
992
|
+
to: addresses.entry,
|
|
993
|
+
data
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function buildDepositTx(options) {
|
|
997
|
+
const { token = "ETH", ...rest } = options;
|
|
998
|
+
if (token === "USDC") {
|
|
999
|
+
return buildDepositUSDCTx(rest);
|
|
1000
|
+
}
|
|
1001
|
+
return buildDepositETHTx(rest);
|
|
1002
|
+
}
|
|
1003
|
+
var DEPOSIT_STATUS_MAP = {
|
|
1004
|
+
0: "pending",
|
|
1005
|
+
1: "accepted",
|
|
1006
|
+
2: "rejected",
|
|
1007
|
+
3: "refunded"
|
|
1008
|
+
};
|
|
1009
|
+
async function getQueueBalance(options) {
|
|
1010
|
+
const { address, rpcUrl, onProgress } = options;
|
|
1011
|
+
const addresses = getAddresses();
|
|
1012
|
+
const publicClient = viem.createPublicClient({
|
|
1013
|
+
chain: chains.base,
|
|
1014
|
+
transport: viem.http(rpcUrl)
|
|
1015
|
+
});
|
|
1016
|
+
onProgress?.("Fetching pending deposits...");
|
|
1017
|
+
const pendingNonces = await publicClient.readContract({
|
|
1018
|
+
address: addresses.ethQueue,
|
|
1019
|
+
abi: QUEUE_ABI,
|
|
1020
|
+
functionName: "getPendingDeposits"
|
|
1021
|
+
});
|
|
1022
|
+
onProgress?.("Queue status", `${pendingNonces.length} pending deposits in queue`);
|
|
1023
|
+
const pendingDeposits = [];
|
|
1024
|
+
let totalQueueBalance = 0n;
|
|
1025
|
+
for (let i = 0; i < pendingNonces.length; i++) {
|
|
1026
|
+
const nonce = pendingNonces[i];
|
|
1027
|
+
onProgress?.("Checking deposit", `${i + 1}/${pendingNonces.length}`);
|
|
1028
|
+
const deposit = await publicClient.readContract({
|
|
1029
|
+
address: addresses.ethQueue,
|
|
1030
|
+
abi: QUEUE_ABI,
|
|
1031
|
+
functionName: "getDeposit",
|
|
1032
|
+
args: [nonce]
|
|
1033
|
+
});
|
|
1034
|
+
if (deposit.fallbackReceiver.toLowerCase() === address.toLowerCase()) {
|
|
1035
|
+
totalQueueBalance += deposit.amountIn;
|
|
1036
|
+
pendingDeposits.push({
|
|
1037
|
+
nonce: nonce.toString(),
|
|
1038
|
+
status: DEPOSIT_STATUS_MAP[deposit.status] || "pending",
|
|
1039
|
+
amount: viem.formatEther(deposit.amountIn),
|
|
1040
|
+
amountWei: deposit.amountIn.toString(),
|
|
1041
|
+
timestamp: new Date(Number(deposit.timestamp) * 1e3).toISOString()
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
if (pendingDeposits.length > 0) {
|
|
1046
|
+
onProgress?.("Found", `${pendingDeposits.length} deposits for your address`);
|
|
1047
|
+
}
|
|
1048
|
+
return {
|
|
1049
|
+
address,
|
|
1050
|
+
queueBalance: viem.formatEther(totalQueueBalance),
|
|
1051
|
+
queueBalanceWei: totalQueueBalance.toString(),
|
|
1052
|
+
pendingDeposits,
|
|
1053
|
+
pendingCount: pendingDeposits.length
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
async function getPrivateBalance(options) {
|
|
1057
|
+
const { keypair, rpcUrl, onProgress } = options;
|
|
1058
|
+
const addresses = getAddresses();
|
|
1059
|
+
if (!keypair.privkey) {
|
|
1060
|
+
throw new Error("Keypair must have a private key to calculate private balance");
|
|
1061
|
+
}
|
|
1062
|
+
const publicClient = viem.createPublicClient({
|
|
1063
|
+
chain: chains.base,
|
|
1064
|
+
transport: viem.http(rpcUrl)
|
|
1065
|
+
});
|
|
1066
|
+
onProgress?.("Fetching pool index...");
|
|
1067
|
+
const nextIndex = await publicClient.readContract({
|
|
1068
|
+
address: addresses.ethPool,
|
|
1069
|
+
abi: POOL_ABI,
|
|
1070
|
+
functionName: "nextIndex"
|
|
1071
|
+
});
|
|
1072
|
+
onProgress?.("Pool index", `${nextIndex} commitments`);
|
|
1073
|
+
if (nextIndex === 0) {
|
|
1074
|
+
return {
|
|
1075
|
+
privateBalance: "0",
|
|
1076
|
+
privateBalanceWei: "0",
|
|
1077
|
+
utxoCount: 0,
|
|
1078
|
+
spentCount: 0,
|
|
1079
|
+
unspentCount: 0,
|
|
1080
|
+
utxos: []
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
const BATCH_SIZE = 5e3;
|
|
1084
|
+
const allEncryptedOutputs = [];
|
|
1085
|
+
const totalBatches = Math.ceil(nextIndex / BATCH_SIZE);
|
|
1086
|
+
for (let start = 0; start < nextIndex; start += BATCH_SIZE) {
|
|
1087
|
+
const end = Math.min(start + BATCH_SIZE, nextIndex);
|
|
1088
|
+
const batchNum = Math.floor(start / BATCH_SIZE) + 1;
|
|
1089
|
+
onProgress?.("Fetching encrypted outputs", `batch ${batchNum}/${totalBatches} (${start}-${end})`);
|
|
1090
|
+
const batch = await publicClient.readContract({
|
|
1091
|
+
address: addresses.ethPool,
|
|
1092
|
+
abi: POOL_ABI,
|
|
1093
|
+
functionName: "getEncryptedOutputs",
|
|
1094
|
+
args: [BigInt(start), BigInt(end)]
|
|
1095
|
+
});
|
|
1096
|
+
allEncryptedOutputs.push(...batch);
|
|
1097
|
+
}
|
|
1098
|
+
onProgress?.("Decrypting outputs", `scanning ${allEncryptedOutputs.length} outputs...`);
|
|
1099
|
+
const decryptedUtxos = [];
|
|
1100
|
+
for (let i = 0; i < allEncryptedOutputs.length; i++) {
|
|
1101
|
+
try {
|
|
1102
|
+
const utxo = Utxo.decrypt(allEncryptedOutputs[i], keypair);
|
|
1103
|
+
utxo.index = i;
|
|
1104
|
+
if (utxo.amount > 0n) {
|
|
1105
|
+
decryptedUtxos.push({ utxo, index: i });
|
|
1106
|
+
}
|
|
1107
|
+
} catch {
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
onProgress?.("Found UTXOs", `${decryptedUtxos.length} belonging to you`);
|
|
1111
|
+
const utxoInfos = [];
|
|
1112
|
+
let totalBalance = 0n;
|
|
1113
|
+
let spentCount = 0;
|
|
1114
|
+
let unspentCount = 0;
|
|
1115
|
+
for (let i = 0; i < decryptedUtxos.length; i++) {
|
|
1116
|
+
const { utxo, index } = decryptedUtxos[i];
|
|
1117
|
+
onProgress?.("Checking spent status", `UTXO ${i + 1}/${decryptedUtxos.length}`);
|
|
1118
|
+
const nullifier = utxo.getNullifier();
|
|
1119
|
+
const nullifierHex = toFixedHex(nullifier);
|
|
1120
|
+
const isSpent = await publicClient.readContract({
|
|
1121
|
+
address: addresses.ethPool,
|
|
1122
|
+
abi: POOL_ABI,
|
|
1123
|
+
functionName: "isSpent",
|
|
1124
|
+
args: [nullifierHex]
|
|
1125
|
+
});
|
|
1126
|
+
utxoInfos.push({
|
|
1127
|
+
index,
|
|
1128
|
+
amount: viem.formatEther(utxo.amount),
|
|
1129
|
+
amountWei: utxo.amount.toString(),
|
|
1130
|
+
isSpent
|
|
1131
|
+
});
|
|
1132
|
+
if (isSpent) {
|
|
1133
|
+
spentCount++;
|
|
1134
|
+
} else {
|
|
1135
|
+
unspentCount++;
|
|
1136
|
+
totalBalance += utxo.amount;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
return {
|
|
1140
|
+
privateBalance: viem.formatEther(totalBalance),
|
|
1141
|
+
privateBalanceWei: totalBalance.toString(),
|
|
1142
|
+
utxoCount: decryptedUtxos.length,
|
|
1143
|
+
spentCount,
|
|
1144
|
+
unspentCount,
|
|
1145
|
+
utxos: utxoInfos
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
var MERKLE_TREE_HEIGHT = 23;
|
|
1149
|
+
async function buildMerkleTree(commitments) {
|
|
1150
|
+
const leaves = commitments.map((commitment) => toFixedHex(commitment));
|
|
1151
|
+
const hashFunction = (left, right) => {
|
|
1152
|
+
const result = poseidonHash2(left, right);
|
|
1153
|
+
return result.toString();
|
|
1154
|
+
};
|
|
1155
|
+
const tree = new MerkleTree__default.default(MERKLE_TREE_HEIGHT, leaves, { hashFunction });
|
|
1156
|
+
return tree;
|
|
1157
|
+
}
|
|
1158
|
+
function getMerklePath(tree, commitment) {
|
|
1159
|
+
const commitmentHex = toFixedHex(commitment);
|
|
1160
|
+
const index = tree.indexOf(commitmentHex);
|
|
1161
|
+
if (index < 0) {
|
|
1162
|
+
throw new Error(`Commitment ${commitmentHex} not found in merkle tree`);
|
|
1163
|
+
}
|
|
1164
|
+
const { pathElements } = tree.path(index);
|
|
1165
|
+
return {
|
|
1166
|
+
pathElements: pathElements.map((el) => BigInt(el)),
|
|
1167
|
+
pathIndices: index
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
var utils = null;
|
|
1171
|
+
try {
|
|
1172
|
+
const ffjavascript = __require("ffjavascript");
|
|
1173
|
+
utils = ffjavascript.utils;
|
|
1174
|
+
} catch {
|
|
1175
|
+
console.warn("ffjavascript not found. Proof generation may not work.");
|
|
1176
|
+
}
|
|
1177
|
+
function findKeysDirectory() {
|
|
1178
|
+
const possiblePaths = [
|
|
1179
|
+
// When running from package (installed via npm)
|
|
1180
|
+
path__namespace.resolve(__dirname, "..", "keys"),
|
|
1181
|
+
path__namespace.resolve(__dirname, "..", "..", "keys"),
|
|
1182
|
+
// When running from source
|
|
1183
|
+
path__namespace.resolve(process.cwd(), "keys")
|
|
1184
|
+
// ESM module path
|
|
1185
|
+
];
|
|
1186
|
+
try {
|
|
1187
|
+
const currentFilePath = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
1188
|
+
const currentDir = path__namespace.dirname(currentFilePath);
|
|
1189
|
+
possiblePaths.unshift(path__namespace.resolve(currentDir, "..", "keys"));
|
|
1190
|
+
} catch {
|
|
1191
|
+
}
|
|
1192
|
+
for (const p of possiblePaths) {
|
|
1193
|
+
if (fs__namespace.existsSync(p) && fs__namespace.existsSync(path__namespace.join(p, "transaction2.wasm"))) {
|
|
1194
|
+
return p;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
throw new Error(
|
|
1198
|
+
"Circuit keys not found. Expected to find keys/ directory with transaction2.wasm and transaction2.zkey files."
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
async function prove(input, circuitName) {
|
|
1202
|
+
if (!utils) {
|
|
1203
|
+
throw new Error("ffjavascript is required for proof generation. Please install it: npm install ffjavascript");
|
|
1204
|
+
}
|
|
1205
|
+
const keysDir = findKeysDirectory();
|
|
1206
|
+
const wasmPath = path__namespace.join(keysDir, `${circuitName}.wasm`);
|
|
1207
|
+
const zkeyPath = path__namespace.join(keysDir, `${circuitName}.zkey`);
|
|
1208
|
+
if (!fs__namespace.existsSync(wasmPath)) {
|
|
1209
|
+
throw new Error(`Circuit WASM file not found: ${wasmPath}`);
|
|
1210
|
+
}
|
|
1211
|
+
if (!fs__namespace.existsSync(zkeyPath)) {
|
|
1212
|
+
throw new Error(`Circuit zkey file not found: ${zkeyPath}`);
|
|
1213
|
+
}
|
|
1214
|
+
const result = await snarkjs.groth16.fullProve(
|
|
1215
|
+
utils.stringifyBigInts(input),
|
|
1216
|
+
wasmPath,
|
|
1217
|
+
zkeyPath
|
|
1218
|
+
);
|
|
1219
|
+
const proof = result.proof;
|
|
1220
|
+
return "0x" + toFixedHex(proof.pi_a[0]).slice(2) + toFixedHex(proof.pi_a[1]).slice(2) + toFixedHex(proof.pi_b[0][1]).slice(2) + toFixedHex(proof.pi_b[0][0]).slice(2) + toFixedHex(proof.pi_b[1][1]).slice(2) + toFixedHex(proof.pi_b[1][0]).slice(2) + toFixedHex(proof.pi_c[0]).slice(2) + toFixedHex(proof.pi_c[1]).slice(2);
|
|
1221
|
+
}
|
|
1222
|
+
var CIRCUIT_CONFIG = {
|
|
1223
|
+
transaction2: { maxInputs: 2, maxOutputs: 2 },
|
|
1224
|
+
transaction16: { maxInputs: 16, maxOutputs: 2 }
|
|
1225
|
+
};
|
|
1226
|
+
function selectCircuit(inputCount) {
|
|
1227
|
+
if (inputCount <= 2) {
|
|
1228
|
+
return "transaction2";
|
|
1229
|
+
} else if (inputCount <= 16) {
|
|
1230
|
+
return "transaction16";
|
|
1231
|
+
} else {
|
|
1232
|
+
throw new Error(`Too many inputs: ${inputCount}. Maximum supported is 16.`);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// src/transaction.ts
|
|
1237
|
+
async function getProof({
|
|
1238
|
+
inputs,
|
|
1239
|
+
outputs,
|
|
1240
|
+
tree,
|
|
1241
|
+
extAmount,
|
|
1242
|
+
fee,
|
|
1243
|
+
recipient,
|
|
1244
|
+
relayer,
|
|
1245
|
+
onProgress
|
|
1246
|
+
}) {
|
|
1247
|
+
inputs = shuffle([...inputs]);
|
|
1248
|
+
outputs = shuffle([...outputs]);
|
|
1249
|
+
onProgress?.("Building merkle paths...");
|
|
1250
|
+
const inputMerklePathIndices = [];
|
|
1251
|
+
const inputMerklePathElements = [];
|
|
1252
|
+
for (const input of inputs) {
|
|
1253
|
+
if (input.amount > 0n) {
|
|
1254
|
+
const inputIndex = tree.indexOf(toFixedHex(input.getCommitment()));
|
|
1255
|
+
if (inputIndex < 0) {
|
|
1256
|
+
throw new Error(`Input commitment ${toFixedHex(input.getCommitment())} was not found in merkle tree`);
|
|
1257
|
+
}
|
|
1258
|
+
input.index = inputIndex;
|
|
1259
|
+
inputMerklePathIndices.push(inputIndex);
|
|
1260
|
+
inputMerklePathElements.push(
|
|
1261
|
+
tree.path(inputIndex).pathElements.map((el) => BigInt(el))
|
|
1262
|
+
);
|
|
1263
|
+
} else {
|
|
1264
|
+
inputMerklePathIndices.push(0);
|
|
1265
|
+
inputMerklePathElements.push(new Array(tree.levels).fill(0));
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
onProgress?.("Encrypting outputs...");
|
|
1269
|
+
const extData = {
|
|
1270
|
+
recipient: toFixedHex(recipient, 20),
|
|
1271
|
+
extAmount: extAmount.toString(),
|
|
1272
|
+
relayer: toFixedHex(relayer, 20),
|
|
1273
|
+
fee: fee.toString(),
|
|
1274
|
+
encryptedOutput1: outputs[0].encrypt(),
|
|
1275
|
+
encryptedOutput2: outputs[1].encrypt()
|
|
1276
|
+
};
|
|
1277
|
+
const extDataHashInput = {
|
|
1278
|
+
recipient,
|
|
1279
|
+
extAmount,
|
|
1280
|
+
relayer,
|
|
1281
|
+
fee,
|
|
1282
|
+
encryptedOutput1: extData.encryptedOutput1,
|
|
1283
|
+
encryptedOutput2: extData.encryptedOutput2
|
|
1284
|
+
};
|
|
1285
|
+
const extDataHash = getExtDataHash(extDataHashInput);
|
|
1286
|
+
const proofInput = {
|
|
1287
|
+
root: BigInt(tree.root()),
|
|
1288
|
+
inputNullifier: inputs.map((x) => x.getNullifier()),
|
|
1289
|
+
outputCommitment: outputs.map((x) => x.getCommitment()),
|
|
1290
|
+
publicAmount: ((BigInt(extAmount) - BigInt(fee) + FIELD_SIZE) % FIELD_SIZE).toString(),
|
|
1291
|
+
extDataHash,
|
|
1292
|
+
// Input UTXO data
|
|
1293
|
+
inAmount: inputs.map((x) => x.amount),
|
|
1294
|
+
inPrivateKey: inputs.map((x) => x.keypair.privkey),
|
|
1295
|
+
inBlinding: inputs.map((x) => x.blinding),
|
|
1296
|
+
inPathIndices: inputMerklePathIndices,
|
|
1297
|
+
inPathElements: inputMerklePathElements,
|
|
1298
|
+
// Output UTXO data
|
|
1299
|
+
outAmount: outputs.map((x) => x.amount),
|
|
1300
|
+
outBlinding: outputs.map((x) => x.blinding),
|
|
1301
|
+
outPubkey: outputs.map((x) => x.keypair.pubkey)
|
|
1302
|
+
};
|
|
1303
|
+
onProgress?.("Generating ZK proof...", `${inputs.length} inputs`);
|
|
1304
|
+
const circuitName = selectCircuit(inputs.length);
|
|
1305
|
+
const proof = await prove(proofInput, circuitName);
|
|
1306
|
+
const args = {
|
|
1307
|
+
proof,
|
|
1308
|
+
root: toFixedHex(proofInput.root),
|
|
1309
|
+
inputNullifiers: inputs.map((x) => toFixedHex(x.getNullifier())),
|
|
1310
|
+
outputCommitments: outputs.map((x) => toFixedHex(x.getCommitment())),
|
|
1311
|
+
publicAmount: toFixedHex(proofInput.publicAmount),
|
|
1312
|
+
extDataHash: toFixedHex(extDataHash)
|
|
1313
|
+
};
|
|
1314
|
+
onProgress?.("Proof generated successfully");
|
|
1315
|
+
return {
|
|
1316
|
+
args,
|
|
1317
|
+
extData
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
async function prepareTransaction({
|
|
1321
|
+
commitments,
|
|
1322
|
+
inputs = [],
|
|
1323
|
+
outputs = [],
|
|
1324
|
+
fee = 0,
|
|
1325
|
+
recipient = 0,
|
|
1326
|
+
relayer = 0,
|
|
1327
|
+
onProgress
|
|
1328
|
+
}) {
|
|
1329
|
+
if (inputs.length > 16 || outputs.length > 2) {
|
|
1330
|
+
throw new Error("Incorrect inputs/outputs count. Maximum: 16 inputs, 2 outputs.");
|
|
1331
|
+
}
|
|
1332
|
+
while (inputs.length !== 2 && inputs.length < 16) {
|
|
1333
|
+
inputs.push(new Utxo());
|
|
1334
|
+
}
|
|
1335
|
+
while (outputs.length < 2) {
|
|
1336
|
+
outputs.push(new Utxo());
|
|
1337
|
+
}
|
|
1338
|
+
const extAmount = BigInt(fee) + outputs.reduce((sum, x) => sum + x.amount, 0n) - inputs.reduce((sum, x) => sum + x.amount, 0n);
|
|
1339
|
+
onProgress?.("Building merkle tree...");
|
|
1340
|
+
const tree = await buildMerkleTree(commitments);
|
|
1341
|
+
const result = await getProof({
|
|
1342
|
+
inputs,
|
|
1343
|
+
outputs,
|
|
1344
|
+
tree,
|
|
1345
|
+
extAmount,
|
|
1346
|
+
fee: BigInt(fee),
|
|
1347
|
+
recipient: String(recipient),
|
|
1348
|
+
relayer: String(relayer),
|
|
1349
|
+
onProgress
|
|
1350
|
+
});
|
|
1351
|
+
return result;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// src/relay.ts
|
|
1355
|
+
var RelayError = class extends Error {
|
|
1356
|
+
/** HTTP status code */
|
|
1357
|
+
statusCode;
|
|
1358
|
+
/** Seconds until rate limit resets (only for 429 errors) */
|
|
1359
|
+
retryAfter;
|
|
1360
|
+
/** Network the error occurred on */
|
|
1361
|
+
network;
|
|
1362
|
+
constructor(message, statusCode, retryAfter, network) {
|
|
1363
|
+
super(message);
|
|
1364
|
+
this.name = "RelayError";
|
|
1365
|
+
this.statusCode = statusCode;
|
|
1366
|
+
this.retryAfter = retryAfter;
|
|
1367
|
+
this.network = network;
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
async function submitRelay(options) {
|
|
1371
|
+
const {
|
|
1372
|
+
type,
|
|
1373
|
+
pool = "eth",
|
|
1374
|
+
proofArgs,
|
|
1375
|
+
extData,
|
|
1376
|
+
metadata,
|
|
1377
|
+
relayUrl: customRelayUrl
|
|
1378
|
+
} = options;
|
|
1379
|
+
if (type !== "withdraw" && type !== "transfer") {
|
|
1380
|
+
throw new RelayError('Invalid type. Must be "withdraw" or "transfer"', 400);
|
|
1381
|
+
}
|
|
1382
|
+
if (pool !== "eth" && pool !== "usdc") {
|
|
1383
|
+
throw new RelayError('Invalid pool. Must be "eth" or "usdc"', 400);
|
|
1384
|
+
}
|
|
1385
|
+
if (!proofArgs || !extData) {
|
|
1386
|
+
throw new RelayError("Missing proofArgs or extData", 400);
|
|
1387
|
+
}
|
|
1388
|
+
const relayUrl = customRelayUrl || getRelayUrl();
|
|
1389
|
+
const endpoint = `${relayUrl}/relay/${pool}`;
|
|
1390
|
+
const response = await fetch(endpoint, {
|
|
1391
|
+
method: "POST",
|
|
1392
|
+
headers: {
|
|
1393
|
+
"Content-Type": "application/json"
|
|
1394
|
+
},
|
|
1395
|
+
body: JSON.stringify({
|
|
1396
|
+
type,
|
|
1397
|
+
proofArgs,
|
|
1398
|
+
extData,
|
|
1399
|
+
metadata
|
|
1400
|
+
})
|
|
1401
|
+
});
|
|
1402
|
+
const data = await response.json();
|
|
1403
|
+
if (!response.ok) {
|
|
1404
|
+
const errorData = data;
|
|
1405
|
+
throw new RelayError(
|
|
1406
|
+
errorData.error || errorData.message || "Relay request failed",
|
|
1407
|
+
response.status,
|
|
1408
|
+
errorData.retryAfter,
|
|
1409
|
+
errorData.network
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
return data;
|
|
1413
|
+
}
|
|
1414
|
+
async function checkRelayHealth(relayUrl) {
|
|
1415
|
+
const url = relayUrl || getRelayUrl();
|
|
1416
|
+
const response = await fetch(`${url}/health`);
|
|
1417
|
+
if (!response.ok) {
|
|
1418
|
+
throw new RelayError("Relay service health check failed", response.status);
|
|
1419
|
+
}
|
|
1420
|
+
return response.json();
|
|
1421
|
+
}
|
|
1422
|
+
async function getRelayInfo(relayUrl) {
|
|
1423
|
+
const url = relayUrl || getRelayUrl();
|
|
1424
|
+
const response = await fetch(url);
|
|
1425
|
+
if (!response.ok) {
|
|
1426
|
+
throw new RelayError("Failed to get relay service info", response.status);
|
|
1427
|
+
}
|
|
1428
|
+
return response.json();
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// src/withdraw.ts
|
|
1432
|
+
function selectUtxosForWithdraw(utxos, amount, decimals = 18) {
|
|
1433
|
+
const withdrawWei = viem.parseUnits(amount, decimals);
|
|
1434
|
+
const sortedUtxos = [...utxos].sort((a, b) => Number(b.amount - a.amount));
|
|
1435
|
+
let totalSelected = 0n;
|
|
1436
|
+
const selectedUtxos = [];
|
|
1437
|
+
for (const utxo of sortedUtxos) {
|
|
1438
|
+
selectedUtxos.push(utxo);
|
|
1439
|
+
totalSelected += utxo.amount;
|
|
1440
|
+
if (totalSelected >= withdrawWei) {
|
|
1441
|
+
break;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (totalSelected < withdrawWei) {
|
|
1445
|
+
throw new Error(
|
|
1446
|
+
`Insufficient balance. Need ${amount}, have ${viem.formatUnits(totalSelected, decimals)}`
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
const changeAmount = totalSelected - withdrawWei;
|
|
1450
|
+
return { selectedUtxos, totalSelected, changeAmount };
|
|
1451
|
+
}
|
|
1452
|
+
async function fetchCommitments(rpcUrl, poolAddress, onProgress) {
|
|
1453
|
+
const publicClient = viem.createPublicClient({
|
|
1454
|
+
chain: chains.base,
|
|
1455
|
+
transport: viem.http(rpcUrl)
|
|
1456
|
+
});
|
|
1457
|
+
onProgress?.("Fetching commitment count...");
|
|
1458
|
+
const nextIndex = await publicClient.readContract({
|
|
1459
|
+
address: poolAddress,
|
|
1460
|
+
abi: POOL_ABI,
|
|
1461
|
+
functionName: "nextIndex"
|
|
1462
|
+
});
|
|
1463
|
+
if (nextIndex === 0) {
|
|
1464
|
+
return [];
|
|
1465
|
+
}
|
|
1466
|
+
const BATCH_SIZE = 5e3;
|
|
1467
|
+
const commitments = [];
|
|
1468
|
+
const totalBatches = Math.ceil(nextIndex / BATCH_SIZE);
|
|
1469
|
+
for (let start = 0; start < nextIndex; start += BATCH_SIZE) {
|
|
1470
|
+
const end = Math.min(start + BATCH_SIZE, nextIndex);
|
|
1471
|
+
const batchNum = Math.floor(start / BATCH_SIZE) + 1;
|
|
1472
|
+
onProgress?.("Fetching commitments", `batch ${batchNum}/${totalBatches}`);
|
|
1473
|
+
const batch = await publicClient.readContract({
|
|
1474
|
+
address: poolAddress,
|
|
1475
|
+
abi: POOL_ABI,
|
|
1476
|
+
functionName: "getCommitments",
|
|
1477
|
+
args: [BigInt(start), BigInt(end)]
|
|
1478
|
+
});
|
|
1479
|
+
commitments.push(...batch.map((c) => c.toString()));
|
|
1480
|
+
}
|
|
1481
|
+
return commitments;
|
|
1482
|
+
}
|
|
1483
|
+
async function buildWithdrawProof(options) {
|
|
1484
|
+
const {
|
|
1485
|
+
amount,
|
|
1486
|
+
recipient,
|
|
1487
|
+
keypair,
|
|
1488
|
+
rpcUrl,
|
|
1489
|
+
onProgress
|
|
1490
|
+
} = options;
|
|
1491
|
+
const addresses = getAddresses();
|
|
1492
|
+
const poolConfig = POOL_CONFIG.eth;
|
|
1493
|
+
const poolAddress = addresses.ethPool;
|
|
1494
|
+
onProgress?.("Fetching your UTXOs...");
|
|
1495
|
+
const balanceResult = await getPrivateBalance({
|
|
1496
|
+
keypair,
|
|
1497
|
+
rpcUrl,
|
|
1498
|
+
onProgress
|
|
1499
|
+
});
|
|
1500
|
+
const unspentUtxoInfos = balanceResult.utxos.filter((u) => !u.isSpent);
|
|
1501
|
+
if (unspentUtxoInfos.length === 0) {
|
|
1502
|
+
throw new Error("No unspent UTXOs available for withdrawal");
|
|
1503
|
+
}
|
|
1504
|
+
onProgress?.("Preparing UTXOs...");
|
|
1505
|
+
const publicClient = viem.createPublicClient({
|
|
1506
|
+
chain: chains.base,
|
|
1507
|
+
transport: viem.http(rpcUrl)
|
|
1508
|
+
});
|
|
1509
|
+
const utxos = [];
|
|
1510
|
+
for (const utxoInfo of unspentUtxoInfos) {
|
|
1511
|
+
const encryptedOutputs = await publicClient.readContract({
|
|
1512
|
+
address: poolAddress,
|
|
1513
|
+
abi: POOL_ABI,
|
|
1514
|
+
functionName: "getEncryptedOutputs",
|
|
1515
|
+
args: [BigInt(utxoInfo.index), BigInt(utxoInfo.index + 1)]
|
|
1516
|
+
});
|
|
1517
|
+
if (encryptedOutputs.length > 0) {
|
|
1518
|
+
try {
|
|
1519
|
+
const utxo = Utxo.decrypt(encryptedOutputs[0], keypair);
|
|
1520
|
+
utxo.index = utxoInfo.index;
|
|
1521
|
+
utxos.push(utxo);
|
|
1522
|
+
} catch {
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
if (utxos.length === 0) {
|
|
1527
|
+
throw new Error("Failed to decrypt UTXOs");
|
|
1528
|
+
}
|
|
1529
|
+
onProgress?.("Selecting UTXOs...");
|
|
1530
|
+
const { selectedUtxos, changeAmount } = selectUtxosForWithdraw(
|
|
1531
|
+
utxos,
|
|
1532
|
+
amount,
|
|
1533
|
+
poolConfig.decimals
|
|
1534
|
+
);
|
|
1535
|
+
const outputs = [];
|
|
1536
|
+
if (changeAmount > 0n) {
|
|
1537
|
+
const changeUtxo = new Utxo({
|
|
1538
|
+
amount: changeAmount,
|
|
1539
|
+
keypair
|
|
1540
|
+
});
|
|
1541
|
+
outputs.push(changeUtxo);
|
|
1542
|
+
}
|
|
1543
|
+
const commitments = await fetchCommitments(rpcUrl, poolAddress, onProgress);
|
|
1544
|
+
onProgress?.("Building ZK proof...");
|
|
1545
|
+
const result = await prepareTransaction({
|
|
1546
|
+
commitments,
|
|
1547
|
+
inputs: selectedUtxos,
|
|
1548
|
+
outputs,
|
|
1549
|
+
fee: 0,
|
|
1550
|
+
recipient,
|
|
1551
|
+
relayer: "0x0000000000000000000000000000000000000000",
|
|
1552
|
+
onProgress
|
|
1553
|
+
});
|
|
1554
|
+
return {
|
|
1555
|
+
proofArgs: {
|
|
1556
|
+
proof: result.args.proof,
|
|
1557
|
+
root: result.args.root,
|
|
1558
|
+
inputNullifiers: result.args.inputNullifiers,
|
|
1559
|
+
outputCommitments: result.args.outputCommitments,
|
|
1560
|
+
publicAmount: result.args.publicAmount,
|
|
1561
|
+
extDataHash: result.args.extDataHash
|
|
1562
|
+
},
|
|
1563
|
+
extData: result.extData,
|
|
1564
|
+
inputCount: selectedUtxos.length,
|
|
1565
|
+
outputCount: outputs.length,
|
|
1566
|
+
amount
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
async function withdraw(options) {
|
|
1570
|
+
const { amount, recipient, onProgress } = options;
|
|
1571
|
+
const proof = await buildWithdrawProof(options);
|
|
1572
|
+
onProgress?.("Submitting to relay...");
|
|
1573
|
+
const relayResult = await submitRelay({
|
|
1574
|
+
type: "withdraw",
|
|
1575
|
+
pool: "eth",
|
|
1576
|
+
proofArgs: proof.proofArgs,
|
|
1577
|
+
extData: proof.extData,
|
|
1578
|
+
metadata: {
|
|
1579
|
+
amount,
|
|
1580
|
+
recipient,
|
|
1581
|
+
inputUtxoCount: proof.inputCount,
|
|
1582
|
+
outputUtxoCount: proof.outputCount
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
return {
|
|
1586
|
+
success: relayResult.success,
|
|
1587
|
+
transactionHash: relayResult.transactionHash,
|
|
1588
|
+
blockNumber: relayResult.blockNumber,
|
|
1589
|
+
amount,
|
|
1590
|
+
recipient
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
async function checkRecipientRegistration(address, rpcUrl) {
|
|
1594
|
+
const addresses = getAddresses();
|
|
1595
|
+
const publicClient = viem.createPublicClient({
|
|
1596
|
+
chain: chains.base,
|
|
1597
|
+
transport: viem.http(rpcUrl)
|
|
1598
|
+
});
|
|
1599
|
+
const depositKey = await publicClient.readContract({
|
|
1600
|
+
address: addresses.entry,
|
|
1601
|
+
abi: ENTRY_ABI,
|
|
1602
|
+
functionName: "depositKeys",
|
|
1603
|
+
args: [address]
|
|
1604
|
+
});
|
|
1605
|
+
const isRegistered = !!(depositKey && depositKey !== "0x" && depositKey.length > 2);
|
|
1606
|
+
return {
|
|
1607
|
+
isRegistered,
|
|
1608
|
+
depositKey: isRegistered ? depositKey : void 0
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
async function fetchCommitments2(rpcUrl, poolAddress, onProgress) {
|
|
1612
|
+
const publicClient = viem.createPublicClient({
|
|
1613
|
+
chain: chains.base,
|
|
1614
|
+
transport: viem.http(rpcUrl)
|
|
1615
|
+
});
|
|
1616
|
+
onProgress?.("Fetching commitment count...");
|
|
1617
|
+
const nextIndex = await publicClient.readContract({
|
|
1618
|
+
address: poolAddress,
|
|
1619
|
+
abi: POOL_ABI,
|
|
1620
|
+
functionName: "nextIndex"
|
|
1621
|
+
});
|
|
1622
|
+
if (nextIndex === 0) {
|
|
1623
|
+
return [];
|
|
1624
|
+
}
|
|
1625
|
+
const BATCH_SIZE = 5e3;
|
|
1626
|
+
const commitments = [];
|
|
1627
|
+
const totalBatches = Math.ceil(nextIndex / BATCH_SIZE);
|
|
1628
|
+
for (let start = 0; start < nextIndex; start += BATCH_SIZE) {
|
|
1629
|
+
const end = Math.min(start + BATCH_SIZE, nextIndex);
|
|
1630
|
+
const batchNum = Math.floor(start / BATCH_SIZE) + 1;
|
|
1631
|
+
onProgress?.("Fetching commitments", `batch ${batchNum}/${totalBatches}`);
|
|
1632
|
+
const batch = await publicClient.readContract({
|
|
1633
|
+
address: poolAddress,
|
|
1634
|
+
abi: POOL_ABI,
|
|
1635
|
+
functionName: "getCommitments",
|
|
1636
|
+
args: [BigInt(start), BigInt(end)]
|
|
1637
|
+
});
|
|
1638
|
+
commitments.push(...batch.map((c) => c.toString()));
|
|
1639
|
+
}
|
|
1640
|
+
return commitments;
|
|
1641
|
+
}
|
|
1642
|
+
async function buildTransferProof(options) {
|
|
1643
|
+
const {
|
|
1644
|
+
amount,
|
|
1645
|
+
recipientAddress,
|
|
1646
|
+
senderKeypair,
|
|
1647
|
+
rpcUrl,
|
|
1648
|
+
onProgress
|
|
1649
|
+
} = options;
|
|
1650
|
+
const addresses = getAddresses();
|
|
1651
|
+
const poolConfig = POOL_CONFIG.eth;
|
|
1652
|
+
const poolAddress = addresses.ethPool;
|
|
1653
|
+
onProgress?.("Checking recipient registration...");
|
|
1654
|
+
const { isRegistered, depositKey } = await checkRecipientRegistration(
|
|
1655
|
+
recipientAddress,
|
|
1656
|
+
rpcUrl
|
|
1657
|
+
);
|
|
1658
|
+
if (!isRegistered || !depositKey) {
|
|
1659
|
+
throw new Error(`Recipient ${recipientAddress} is not registered. They need to register first.`);
|
|
1660
|
+
}
|
|
1661
|
+
onProgress?.("Fetching your UTXOs...");
|
|
1662
|
+
const balanceResult = await getPrivateBalance({
|
|
1663
|
+
keypair: senderKeypair,
|
|
1664
|
+
rpcUrl,
|
|
1665
|
+
onProgress
|
|
1666
|
+
});
|
|
1667
|
+
const unspentUtxoInfos = balanceResult.utxos.filter((u) => !u.isSpent);
|
|
1668
|
+
if (unspentUtxoInfos.length === 0) {
|
|
1669
|
+
throw new Error("No unspent UTXOs available for transfer");
|
|
1670
|
+
}
|
|
1671
|
+
onProgress?.("Preparing UTXOs...");
|
|
1672
|
+
const publicClient = viem.createPublicClient({
|
|
1673
|
+
chain: chains.base,
|
|
1674
|
+
transport: viem.http(rpcUrl)
|
|
1675
|
+
});
|
|
1676
|
+
const utxos = [];
|
|
1677
|
+
for (const utxoInfo of unspentUtxoInfos) {
|
|
1678
|
+
const encryptedOutputs = await publicClient.readContract({
|
|
1679
|
+
address: poolAddress,
|
|
1680
|
+
abi: POOL_ABI,
|
|
1681
|
+
functionName: "getEncryptedOutputs",
|
|
1682
|
+
args: [BigInt(utxoInfo.index), BigInt(utxoInfo.index + 1)]
|
|
1683
|
+
});
|
|
1684
|
+
if (encryptedOutputs.length > 0) {
|
|
1685
|
+
try {
|
|
1686
|
+
const utxo = Utxo.decrypt(encryptedOutputs[0], senderKeypair);
|
|
1687
|
+
utxo.index = utxoInfo.index;
|
|
1688
|
+
utxos.push(utxo);
|
|
1689
|
+
} catch {
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
if (utxos.length === 0) {
|
|
1694
|
+
throw new Error("Failed to decrypt UTXOs");
|
|
1695
|
+
}
|
|
1696
|
+
onProgress?.("Selecting UTXOs...");
|
|
1697
|
+
const { selectedUtxos, changeAmount } = selectUtxosForWithdraw(
|
|
1698
|
+
utxos,
|
|
1699
|
+
amount,
|
|
1700
|
+
poolConfig.decimals
|
|
1701
|
+
);
|
|
1702
|
+
const outputs = [];
|
|
1703
|
+
const transferWei = viem.parseUnits(amount, poolConfig.decimals);
|
|
1704
|
+
const recipientKeypair = Keypair.fromString(depositKey);
|
|
1705
|
+
const recipientUtxo = new Utxo({
|
|
1706
|
+
amount: transferWei,
|
|
1707
|
+
keypair: recipientKeypair
|
|
1708
|
+
});
|
|
1709
|
+
outputs.push(recipientUtxo);
|
|
1710
|
+
if (changeAmount > 0n) {
|
|
1711
|
+
const changeUtxo = new Utxo({
|
|
1712
|
+
amount: changeAmount,
|
|
1713
|
+
keypair: senderKeypair
|
|
1714
|
+
});
|
|
1715
|
+
outputs.push(changeUtxo);
|
|
1716
|
+
}
|
|
1717
|
+
const commitments = await fetchCommitments2(rpcUrl, poolAddress, onProgress);
|
|
1718
|
+
onProgress?.("Building ZK proof...");
|
|
1719
|
+
const result = await prepareTransaction({
|
|
1720
|
+
commitments,
|
|
1721
|
+
inputs: selectedUtxos,
|
|
1722
|
+
outputs,
|
|
1723
|
+
fee: 0,
|
|
1724
|
+
recipient: "0x0000000000000000000000000000000000000000",
|
|
1725
|
+
relayer: "0x0000000000000000000000000000000000000000",
|
|
1726
|
+
onProgress
|
|
1727
|
+
});
|
|
1728
|
+
return {
|
|
1729
|
+
proofArgs: {
|
|
1730
|
+
proof: result.args.proof,
|
|
1731
|
+
root: result.args.root,
|
|
1732
|
+
inputNullifiers: result.args.inputNullifiers,
|
|
1733
|
+
outputCommitments: result.args.outputCommitments,
|
|
1734
|
+
publicAmount: result.args.publicAmount,
|
|
1735
|
+
extDataHash: result.args.extDataHash
|
|
1736
|
+
},
|
|
1737
|
+
extData: result.extData,
|
|
1738
|
+
inputCount: selectedUtxos.length,
|
|
1739
|
+
outputCount: outputs.length,
|
|
1740
|
+
amount
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
async function transfer(options) {
|
|
1744
|
+
const { amount, recipientAddress, onProgress } = options;
|
|
1745
|
+
const proof = await buildTransferProof(options);
|
|
1746
|
+
onProgress?.("Submitting to relay...");
|
|
1747
|
+
const relayResult = await submitRelay({
|
|
1748
|
+
type: "transfer",
|
|
1749
|
+
pool: "eth",
|
|
1750
|
+
proofArgs: proof.proofArgs,
|
|
1751
|
+
extData: proof.extData,
|
|
1752
|
+
metadata: {
|
|
1753
|
+
amount,
|
|
1754
|
+
recipient: recipientAddress,
|
|
1755
|
+
inputUtxoCount: proof.inputCount,
|
|
1756
|
+
outputUtxoCount: proof.outputCount
|
|
1757
|
+
}
|
|
1758
|
+
});
|
|
1759
|
+
return {
|
|
1760
|
+
success: relayResult.success,
|
|
1761
|
+
transactionHash: relayResult.transactionHash,
|
|
1762
|
+
blockNumber: relayResult.blockNumber,
|
|
1763
|
+
amount,
|
|
1764
|
+
recipient: recipientAddress
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
async function mergeUtxos(options) {
|
|
1768
|
+
const { amount, keypair, rpcUrl, onProgress } = options;
|
|
1769
|
+
const addresses = getAddresses();
|
|
1770
|
+
const poolConfig = POOL_CONFIG.eth;
|
|
1771
|
+
const poolAddress = addresses.ethPool;
|
|
1772
|
+
onProgress?.("Fetching your UTXOs...");
|
|
1773
|
+
const balanceResult = await getPrivateBalance({
|
|
1774
|
+
keypair,
|
|
1775
|
+
rpcUrl,
|
|
1776
|
+
onProgress
|
|
1777
|
+
});
|
|
1778
|
+
const unspentUtxoInfos = balanceResult.utxos.filter((u) => !u.isSpent);
|
|
1779
|
+
if (unspentUtxoInfos.length === 0) {
|
|
1780
|
+
throw new Error("No unspent UTXOs available for merge");
|
|
1781
|
+
}
|
|
1782
|
+
onProgress?.("Preparing UTXOs...");
|
|
1783
|
+
const publicClient = viem.createPublicClient({
|
|
1784
|
+
chain: chains.base,
|
|
1785
|
+
transport: viem.http(rpcUrl)
|
|
1786
|
+
});
|
|
1787
|
+
const utxos = [];
|
|
1788
|
+
for (const utxoInfo of unspentUtxoInfos) {
|
|
1789
|
+
const encryptedOutputs = await publicClient.readContract({
|
|
1790
|
+
address: poolAddress,
|
|
1791
|
+
abi: POOL_ABI,
|
|
1792
|
+
functionName: "getEncryptedOutputs",
|
|
1793
|
+
args: [BigInt(utxoInfo.index), BigInt(utxoInfo.index + 1)]
|
|
1794
|
+
});
|
|
1795
|
+
if (encryptedOutputs.length > 0) {
|
|
1796
|
+
try {
|
|
1797
|
+
const utxo = Utxo.decrypt(encryptedOutputs[0], keypair);
|
|
1798
|
+
utxo.index = utxoInfo.index;
|
|
1799
|
+
utxos.push(utxo);
|
|
1800
|
+
} catch {
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
if (utxos.length === 0) {
|
|
1805
|
+
throw new Error("Failed to decrypt UTXOs");
|
|
1806
|
+
}
|
|
1807
|
+
onProgress?.("Selecting UTXOs...");
|
|
1808
|
+
const { selectedUtxos, changeAmount } = selectUtxosForWithdraw(
|
|
1809
|
+
utxos,
|
|
1810
|
+
amount,
|
|
1811
|
+
poolConfig.decimals
|
|
1812
|
+
);
|
|
1813
|
+
const outputs = [];
|
|
1814
|
+
const mergeWei = viem.parseUnits(amount, poolConfig.decimals);
|
|
1815
|
+
const mergedUtxo = new Utxo({
|
|
1816
|
+
amount: mergeWei,
|
|
1817
|
+
keypair
|
|
1818
|
+
});
|
|
1819
|
+
outputs.push(mergedUtxo);
|
|
1820
|
+
if (changeAmount > 0n) {
|
|
1821
|
+
const changeUtxo = new Utxo({
|
|
1822
|
+
amount: changeAmount,
|
|
1823
|
+
keypair
|
|
1824
|
+
});
|
|
1825
|
+
outputs.push(changeUtxo);
|
|
1826
|
+
}
|
|
1827
|
+
const commitments = await fetchCommitments2(rpcUrl, poolAddress, onProgress);
|
|
1828
|
+
onProgress?.("Building ZK proof...");
|
|
1829
|
+
const result = await prepareTransaction({
|
|
1830
|
+
commitments,
|
|
1831
|
+
inputs: selectedUtxos,
|
|
1832
|
+
outputs,
|
|
1833
|
+
fee: 0,
|
|
1834
|
+
recipient: "0x0000000000000000000000000000000000000000",
|
|
1835
|
+
relayer: "0x0000000000000000000000000000000000000000",
|
|
1836
|
+
onProgress
|
|
1837
|
+
});
|
|
1838
|
+
onProgress?.("Submitting to relay...");
|
|
1839
|
+
const relayResult = await submitRelay({
|
|
1840
|
+
type: "transfer",
|
|
1841
|
+
pool: "eth",
|
|
1842
|
+
proofArgs: {
|
|
1843
|
+
proof: result.args.proof,
|
|
1844
|
+
root: result.args.root,
|
|
1845
|
+
inputNullifiers: result.args.inputNullifiers,
|
|
1846
|
+
outputCommitments: result.args.outputCommitments,
|
|
1847
|
+
publicAmount: result.args.publicAmount,
|
|
1848
|
+
extDataHash: result.args.extDataHash
|
|
1849
|
+
},
|
|
1850
|
+
extData: result.extData,
|
|
1851
|
+
metadata: {
|
|
1852
|
+
amount,
|
|
1853
|
+
inputUtxoCount: selectedUtxos.length,
|
|
1854
|
+
outputUtxoCount: outputs.length
|
|
1855
|
+
}
|
|
1856
|
+
});
|
|
1857
|
+
return {
|
|
1858
|
+
success: relayResult.success,
|
|
1859
|
+
transactionHash: relayResult.transactionHash,
|
|
1860
|
+
blockNumber: relayResult.blockNumber,
|
|
1861
|
+
amount,
|
|
1862
|
+
recipient: "self"
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
exports.ADDRESSES = ADDRESSES;
|
|
1867
|
+
exports.CIRCUIT_CONFIG = CIRCUIT_CONFIG;
|
|
1868
|
+
exports.ENTRY_ABI = ENTRY_ABI;
|
|
1869
|
+
exports.ERC20_ABI = ERC20_ABI;
|
|
1870
|
+
exports.FIELD_SIZE = FIELD_SIZE;
|
|
1871
|
+
exports.Keypair = Keypair;
|
|
1872
|
+
exports.MERKLE_TREE_HEIGHT = MERKLE_TREE_HEIGHT;
|
|
1873
|
+
exports.POOL_ABI = POOL_ABI;
|
|
1874
|
+
exports.POOL_CONFIG = POOL_CONFIG;
|
|
1875
|
+
exports.QUEUE_ABI = QUEUE_ABI;
|
|
1876
|
+
exports.RelayError = RelayError;
|
|
1877
|
+
exports.Utxo = Utxo;
|
|
1878
|
+
exports.buildApproveUSDCTx = buildApproveUSDCTx;
|
|
1879
|
+
exports.buildDepositETHTx = buildDepositETHTx;
|
|
1880
|
+
exports.buildDepositTx = buildDepositTx;
|
|
1881
|
+
exports.buildDepositUSDCTx = buildDepositUSDCTx;
|
|
1882
|
+
exports.buildMerkleTree = buildMerkleTree;
|
|
1883
|
+
exports.buildRegisterTx = buildRegisterTx;
|
|
1884
|
+
exports.buildTransferProof = buildTransferProof;
|
|
1885
|
+
exports.buildWithdrawProof = buildWithdrawProof;
|
|
1886
|
+
exports.checkRecipientRegistration = checkRecipientRegistration;
|
|
1887
|
+
exports.checkRelayHealth = checkRelayHealth;
|
|
1888
|
+
exports.getAddresses = getAddresses;
|
|
1889
|
+
exports.getExtDataHash = getExtDataHash;
|
|
1890
|
+
exports.getMerklePath = getMerklePath;
|
|
1891
|
+
exports.getPrivateBalance = getPrivateBalance;
|
|
1892
|
+
exports.getQueueBalance = getQueueBalance;
|
|
1893
|
+
exports.getRelayInfo = getRelayInfo;
|
|
1894
|
+
exports.getRelayUrl = getRelayUrl;
|
|
1895
|
+
exports.mergeUtxos = mergeUtxos;
|
|
1896
|
+
exports.packEncryptedMessage = packEncryptedMessage;
|
|
1897
|
+
exports.poseidonHash = poseidonHash;
|
|
1898
|
+
exports.poseidonHash2 = poseidonHash2;
|
|
1899
|
+
exports.prepareTransaction = prepareTransaction;
|
|
1900
|
+
exports.prove = prove;
|
|
1901
|
+
exports.randomBN = randomBN;
|
|
1902
|
+
exports.selectCircuit = selectCircuit;
|
|
1903
|
+
exports.selectUtxosForWithdraw = selectUtxosForWithdraw;
|
|
1904
|
+
exports.shuffle = shuffle;
|
|
1905
|
+
exports.submitRelay = submitRelay;
|
|
1906
|
+
exports.toBuffer = toBuffer;
|
|
1907
|
+
exports.toFixedHex = toFixedHex;
|
|
1908
|
+
exports.transfer = transfer;
|
|
1909
|
+
exports.unpackEncryptedMessage = unpackEncryptedMessage;
|
|
1910
|
+
exports.withdraw = withdraw;
|
|
1911
|
+
//# sourceMappingURL=index.cjs.map
|
|
1912
|
+
//# sourceMappingURL=index.cjs.map
|