testprivacycash-evm 1.2.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/.github/workflows/npm-publish.yml +55 -0
- package/README.md +17 -0
- package/circuits/transaction2.wasm +0 -0
- package/circuits/transaction2.zkey +0 -0
- package/dist/balance.d.ts +10 -0
- package/dist/balance.js +57 -0
- package/dist/deposit.d.ts +10 -0
- package/dist/deposit.js +345 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +8 -0
- package/dist/utils/ERCPool.abi.json +929 -0
- package/dist/utils/EtherPool.abi.json +798 -0
- package/dist/utils/constants.d.ts +8 -0
- package/dist/utils/constants.js +11 -0
- package/dist/utils/db.d.ts +17 -0
- package/dist/utils/db.js +276 -0
- package/dist/utils/encryption.d.ts +8 -0
- package/dist/utils/encryption.js +34 -0
- package/dist/utils/keypair.d.ts +9 -0
- package/dist/utils/keypair.js +19 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.js +35 -0
- package/dist/utils/networkConfig.d.ts +54 -0
- package/dist/utils/networkConfig.js +126 -0
- package/dist/utils/prover.d.ts +15 -0
- package/dist/utils/prover.js +30 -0
- package/dist/utils/remoteConfig.d.ts +21 -0
- package/dist/utils/remoteConfig.js +24 -0
- package/dist/utils/utils.d.ts +83 -0
- package/dist/utils/utils.js +275 -0
- package/dist/utils/utxo.d.ts +20 -0
- package/dist/utils/utxo.js +55 -0
- package/dist/withdraw.d.ts +12 -0
- package/dist/withdraw.js +253 -0
- package/package.json +37 -0
- package/src/balance.ts +74 -0
- package/src/deposit.ts +406 -0
- package/src/index.ts +16 -0
- package/src/utils/ERCPool.abi.json +929 -0
- package/src/utils/EtherPool.abi.json +798 -0
- package/src/utils/constants.ts +15 -0
- package/src/utils/db.ts +311 -0
- package/src/utils/encryption.ts +40 -0
- package/src/utils/keypair.ts +24 -0
- package/src/utils/logger.ts +42 -0
- package/src/utils/networkConfig.ts +169 -0
- package/src/utils/prover.ts +39 -0
- package/src/utils/remoteConfig.ts +49 -0
- package/src/utils/utils.ts +387 -0
- package/src/utils/utxo.ts +74 -0
- package/src/withdraw.ts +351 -0
- package/tsconfig.json +29 -0
package/src/deposit.ts
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { BigNumber, ethers } from 'ethers';
|
|
2
|
+
import ERCPoolAbi from './utils/ERCPool.abi.json' with { type: 'json' };
|
|
3
|
+
import EtherPoolAbi from './utils/EtherPool.abi.json' with { type: 'json' };
|
|
4
|
+
import { deriveKeys } from './utils/encryption.js';
|
|
5
|
+
import { logger } from './utils/logger.js';
|
|
6
|
+
import { NetworkConfig, PrivacyToken, getErc20TokenConfig, resolveNetwork, resolvePrivacyToken } from './utils/networkConfig.js';
|
|
7
|
+
import { getRemoteConfig } from './utils/remoteConfig.js';
|
|
8
|
+
import { findUnspentUtxos, prepareTransaction, toFixedHex } from './utils/utils.js';
|
|
9
|
+
import { Utxo } from './utils/utxo.js';
|
|
10
|
+
|
|
11
|
+
function getFeeBumpPercent(): number {
|
|
12
|
+
const value = Number(process.env.NEXT_PUBLIC_EVM_FEE_BUMP_PERCENT || process.env.EVM_FEE_BUMP_PERCENT);
|
|
13
|
+
if (Number.isFinite(value) && value >= 100) return Math.floor(value);
|
|
14
|
+
return 110;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function bumpFee(value: BigNumber, percent = getFeeBumpPercent()): BigNumber {
|
|
18
|
+
return value.mul(percent).div(100);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const MAX_PRIORITY_FEE_GWEI = process.env.NEXT_PUBLIC_MAX_PRIORITY_FEE_GWEI || process.env.MAX_PRIORITY_FEE_GWEI || '0.6';
|
|
22
|
+
const MAX_PRIORITY_FEE = ethers.utils.parseUnits(MAX_PRIORITY_FEE_GWEI, 'gwei');
|
|
23
|
+
|
|
24
|
+
function formatGwei(value?: BigNumber | null): string {
|
|
25
|
+
return value ? ethers.utils.formatUnits(value, 'gwei') : 'null';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getGasLimitBumpPercent(): number {
|
|
29
|
+
const value = Number(process.env.NEXT_PUBLIC_EVM_GAS_LIMIT_BUMP_PERCENT || process.env.EVM_GAS_LIMIT_BUMP_PERCENT);
|
|
30
|
+
if (Number.isFinite(value) && value >= 100) return Math.floor(value);
|
|
31
|
+
return 120;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function bumpGasLimit(value: BigNumber, percent = getGasLimitBumpPercent()): BigNumber {
|
|
35
|
+
return value.mul(percent).add(99).div(100);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getMinPriorityFee(net: NetworkConfig): BigNumber | null {
|
|
39
|
+
const configured = process.env.NEXT_PUBLIC_ETH_MIN_PRIORITY_FEE_GWEI || process.env.ETH_MIN_PRIORITY_FEE_GWEI;
|
|
40
|
+
const gwei = configured || (net.chainKey === 'eth' ? '0.2' : '');
|
|
41
|
+
return gwei ? ethers.utils.parseUnits(gwei, 'gwei') : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function selectPriorityFee(providerPriorityFee: BigNumber | null | undefined, minPriorityFee: BigNumber | null): BigNumber {
|
|
45
|
+
const priorityFee = providerPriorityFee && !providerPriorityFee.isZero()
|
|
46
|
+
? providerPriorityFee
|
|
47
|
+
: (minPriorityFee ?? BigNumber.from(0));
|
|
48
|
+
return priorityFee.gt(MAX_PRIORITY_FEE) ? MAX_PRIORITY_FEE : priorityFee;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function getFeeOverrides(
|
|
52
|
+
net: NetworkConfig,
|
|
53
|
+
provider: ethers.providers.JsonRpcProvider,
|
|
54
|
+
feeData?: ethers.providers.FeeData,
|
|
55
|
+
): Promise<ethers.utils.Deferrable<ethers.providers.TransactionRequest>> {
|
|
56
|
+
feeData ??= await provider.getFeeData();
|
|
57
|
+
|
|
58
|
+
// BNB Smart Chain's legacy gasPrice is the most widely supported path.
|
|
59
|
+
if (net.chainKey === 'bnb' && feeData.gasPrice) {
|
|
60
|
+
return { gasPrice: bumpFee(feeData.gasPrice) };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (feeData.maxFeePerGas || feeData.maxPriorityFeePerGas) {
|
|
64
|
+
const minPriorityFee = getMinPriorityFee(net);
|
|
65
|
+
logger.debug(
|
|
66
|
+
`Deposit feeData: gasPrice=${formatGwei(feeData.gasPrice)} gwei, ` +
|
|
67
|
+
`maxFeePerGas=${formatGwei(feeData.maxFeePerGas)} gwei, ` +
|
|
68
|
+
`maxPriorityFeePerGas=${formatGwei(feeData.maxPriorityFeePerGas)} gwei, ` +
|
|
69
|
+
`lastBaseFeePerGas=${formatGwei(feeData.lastBaseFeePerGas)} gwei, ` +
|
|
70
|
+
`defaultMinPriorityFee=${formatGwei(minPriorityFee)} gwei, ` +
|
|
71
|
+
`maxPriorityFeeCap=${MAX_PRIORITY_FEE_GWEI} gwei, ` +
|
|
72
|
+
`feeBumpPercent=${getFeeBumpPercent()}`,
|
|
73
|
+
);
|
|
74
|
+
const priorityFee = selectPriorityFee(feeData.maxPriorityFeePerGas, minPriorityFee);
|
|
75
|
+
if (priorityFee.isZero()) {
|
|
76
|
+
throw new Error('Failed to fetch network priority fee data');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let maxFeeBase = feeData.maxFeePerGas;
|
|
80
|
+
let fallbackBaseFee: BigNumber | null = null;
|
|
81
|
+
if (!maxFeeBase) {
|
|
82
|
+
const latestBlock = await provider.getBlock('latest');
|
|
83
|
+
fallbackBaseFee = latestBlock?.baseFeePerGas ?? null;
|
|
84
|
+
maxFeeBase = latestBlock?.baseFeePerGas
|
|
85
|
+
? latestBlock.baseFeePerGas.mul(2).add(priorityFee)
|
|
86
|
+
: null;
|
|
87
|
+
}
|
|
88
|
+
if (!maxFeeBase) {
|
|
89
|
+
throw new Error('Failed to fetch network max fee data');
|
|
90
|
+
}
|
|
91
|
+
const maxFeePerGas = bumpFee(maxFeeBase);
|
|
92
|
+
const finalMaxFeePerGas = maxFeePerGas.gt(priorityFee) ? maxFeePerGas : priorityFee.mul(2);
|
|
93
|
+
logger.debug(
|
|
94
|
+
`Deposit fee overrides: selectedPriorityFee=${formatGwei(priorityFee)} gwei, ` +
|
|
95
|
+
`fallbackLatestBaseFee=${formatGwei(fallbackBaseFee)} gwei, ` +
|
|
96
|
+
`maxFeeBase=${formatGwei(maxFeeBase)} gwei, ` +
|
|
97
|
+
`finalMaxFeePerGas=${formatGwei(finalMaxFeePerGas)} gwei`,
|
|
98
|
+
);
|
|
99
|
+
return {
|
|
100
|
+
type: 2,
|
|
101
|
+
maxPriorityFeePerGas: priorityFee,
|
|
102
|
+
maxFeePerGas: finalMaxFeePerGas,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (feeData.gasPrice) {
|
|
107
|
+
const gasPrice = bumpFee(feeData.gasPrice);
|
|
108
|
+
logger.debug(
|
|
109
|
+
`Deposit legacy feeData: providerGasPrice=${formatGwei(feeData.gasPrice)} gwei, ` +
|
|
110
|
+
`finalGasPrice=${formatGwei(gasPrice)} gwei, feeBumpPercent=${getFeeBumpPercent()}`,
|
|
111
|
+
);
|
|
112
|
+
return { gasPrice };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
throw new Error('Failed to fetch network fee data');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function estimateTransactGasLimit({
|
|
119
|
+
pool,
|
|
120
|
+
args,
|
|
121
|
+
extData,
|
|
122
|
+
estimateOverrides,
|
|
123
|
+
fallback,
|
|
124
|
+
}: {
|
|
125
|
+
pool: ethers.Contract,
|
|
126
|
+
args: any,
|
|
127
|
+
extData: any,
|
|
128
|
+
estimateOverrides: ethers.providers.TransactionRequest,
|
|
129
|
+
fallback: BigNumber,
|
|
130
|
+
}): Promise<BigNumber> {
|
|
131
|
+
try {
|
|
132
|
+
const estimated = await pool.estimateGas.transact(args, extData, estimateOverrides);
|
|
133
|
+
const gasLimit = bumpGasLimit(estimated);
|
|
134
|
+
logger.debug(`Estimated transact gas: ${estimated.toString()} (using limit ${gasLimit.toString()})`);
|
|
135
|
+
return gasLimit;
|
|
136
|
+
} catch (err) {
|
|
137
|
+
logger.warn(`Failed to estimate transact gas; using fallback ${fallback.toString()}. Error:`, err);
|
|
138
|
+
return fallback;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function deposit({ depositAmountInput, keyBasePath, signature, address, txSender, token, network }: {
|
|
143
|
+
depositAmountInput: number,
|
|
144
|
+
keyBasePath: string,
|
|
145
|
+
signature: string,
|
|
146
|
+
address: string,
|
|
147
|
+
txSender: any,
|
|
148
|
+
token?: PrivacyToken,
|
|
149
|
+
network?: NetworkConfig | number,
|
|
150
|
+
}) {
|
|
151
|
+
const net = resolveNetwork(network);
|
|
152
|
+
const resolvedToken = resolvePrivacyToken(net, token);
|
|
153
|
+
const readProvider = new ethers.providers.JsonRpcProvider(net.rpcUrl, {
|
|
154
|
+
name: net.chainKey,
|
|
155
|
+
chainId: net.chainId,
|
|
156
|
+
});
|
|
157
|
+
const erc20Token = getErc20TokenConfig(net, resolvedToken);
|
|
158
|
+
const isErc20 = erc20Token !== null;
|
|
159
|
+
const tokenSymbol = erc20Token?.symbol ?? net.nativeSymbol ?? (net.chainKey === 'bnb' ? 'BNB' : 'ETH');
|
|
160
|
+
const tokenDecimals = erc20Token?.decimals ?? 18;
|
|
161
|
+
|
|
162
|
+
const remoteConfig = await getRemoteConfig(net);
|
|
163
|
+
const minDeposit = remoteConfig.minimum_deposit[resolvedToken];
|
|
164
|
+
|
|
165
|
+
if (isErc20) {
|
|
166
|
+
if (depositAmountInput < minDeposit) {
|
|
167
|
+
throw new Error(`Deposit amount must be at least ${minDeposit} ${tokenSymbol}`);
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
if (depositAmountInput < minDeposit) {
|
|
171
|
+
throw new Error(`Deposit amount must be at least ${minDeposit} ${tokenSymbol}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const poolAddress = ethers.utils.getAddress(erc20Token ? erc20Token.poolAddress : net.etherPoolAddress);
|
|
176
|
+
const abi = isErc20 ? ERCPoolAbi : EtherPoolAbi;
|
|
177
|
+
|
|
178
|
+
logger.debug(`Depositor: ${address}`);
|
|
179
|
+
|
|
180
|
+
const { encryptionKey, keypair } = deriveKeys(signature);
|
|
181
|
+
logger.debug(`UTXO pubkey: ${toFixedHex(keypair.pubkey)}`);
|
|
182
|
+
|
|
183
|
+
const pool = new ethers.Contract(poolAddress, abi, readProvider);
|
|
184
|
+
|
|
185
|
+
if (!isErc20) {
|
|
186
|
+
const poolBalance = await readProvider.getBalance(pool.address);
|
|
187
|
+
logger.debug(`EtherPool: ${pool.address}`);
|
|
188
|
+
logger.debug(`Pool balance: ${ethers.utils.formatEther(poolBalance)} ${tokenSymbol}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const depositAmount = isErc20
|
|
192
|
+
? ethers.utils.parseUnits(depositAmountInput.toString(), tokenDecimals)
|
|
193
|
+
: ethers.utils.parseEther(depositAmountInput.toString());
|
|
194
|
+
|
|
195
|
+
const maxDeposit = await pool.maximumDepositAmount();
|
|
196
|
+
if (depositAmount.gt(maxDeposit)) {
|
|
197
|
+
const formatted = isErc20
|
|
198
|
+
? `${ethers.utils.formatUnits(maxDeposit, tokenDecimals)} ${tokenSymbol}`
|
|
199
|
+
: `${ethers.utils.formatEther(maxDeposit)} ${tokenSymbol}`;
|
|
200
|
+
throw new Error(`Please deposit less than ${formatted}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// post address to /screen_address of indexer to check if it's blacklisted
|
|
204
|
+
logger.info('screening wallet')
|
|
205
|
+
logger.debug(`screening address ${address}`)
|
|
206
|
+
try {
|
|
207
|
+
let res = await fetch(net.indexerUrl + '/screen_address', {
|
|
208
|
+
method: 'POST',
|
|
209
|
+
headers: { 'Content-Type': 'application/json' },
|
|
210
|
+
body: JSON.stringify({ address }),
|
|
211
|
+
});
|
|
212
|
+
let resJson = await res.json()
|
|
213
|
+
if (resJson.isRisk) {
|
|
214
|
+
throw new Error('Your wallet address is risky. Service rejected.', { cause: { type: 'RISKY_ADDRESS' } });
|
|
215
|
+
}
|
|
216
|
+
} catch (err: any) {
|
|
217
|
+
if (err.cause?.type === 'RISKY_ADDRESS') {
|
|
218
|
+
throw err;
|
|
219
|
+
}
|
|
220
|
+
logger.error('Failed to screen address, but proceeding with deposit. Error:', err);
|
|
221
|
+
}
|
|
222
|
+
logger.debug('Address screening passed');
|
|
223
|
+
|
|
224
|
+
// Scan on-chain events to find unspent UTXOs
|
|
225
|
+
logger.debug('Scanning on-chain events for unspent UTXOs...');
|
|
226
|
+
const unspent = await findUnspentUtxos({
|
|
227
|
+
etherPool: pool,
|
|
228
|
+
encryptionKey,
|
|
229
|
+
keypair,
|
|
230
|
+
address,
|
|
231
|
+
token: resolvedToken,
|
|
232
|
+
network: net,
|
|
233
|
+
});
|
|
234
|
+
logger.debug(`Unspent UTXOs found: ${unspent.length}`);
|
|
235
|
+
|
|
236
|
+
let inputs: Utxo[] = [];
|
|
237
|
+
let inputSum = BigNumber.from(0);
|
|
238
|
+
|
|
239
|
+
if (unspent.length >= 2) {
|
|
240
|
+
inputs = [unspent[0], unspent[1]];
|
|
241
|
+
inputSum = inputs[0].amount.add(inputs[1].amount);
|
|
242
|
+
} else if (unspent.length === 1) {
|
|
243
|
+
inputs = [unspent[0]];
|
|
244
|
+
inputSum = inputs[0].amount;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const outputAmount = inputSum.add(depositAmount);
|
|
248
|
+
// For ERC20 pools, embed the token contract address as mintAddress in the UTXO.
|
|
249
|
+
const mintAddress = erc20Token ? BigNumber.from(erc20Token.tokenAddress) : BigNumber.from(0);
|
|
250
|
+
const outputUtxo = new Utxo({ amount: outputAmount, keypair, mintAddress });
|
|
251
|
+
|
|
252
|
+
const formattedOutput = isErc20
|
|
253
|
+
? `${ethers.utils.formatUnits(outputAmount, tokenDecimals)} ${tokenSymbol}`
|
|
254
|
+
: `${ethers.utils.formatEther(outputAmount)} ${tokenSymbol}`;
|
|
255
|
+
logger.debug(`Depositing ${depositAmountInput} ${tokenSymbol} (new output: ${formattedOutput})`);
|
|
256
|
+
|
|
257
|
+
if (isErc20 && erc20Token) {
|
|
258
|
+
// Step 1: send ERC20 approve tx first, BEFORE generating the ZK proof.
|
|
259
|
+
// The proof fetches the Merkle root; if we generate it before the approve
|
|
260
|
+
// is mined the root can advance while waiting, causing proof verification
|
|
261
|
+
// to fail on-chain.
|
|
262
|
+
const erc20Abi = [
|
|
263
|
+
'function allowance(address owner, address spender) view returns (uint256)',
|
|
264
|
+
'function approve(address spender, uint256 amount) returns (bool)',
|
|
265
|
+
];
|
|
266
|
+
const erc20 = new ethers.Contract(erc20Token.tokenAddress, erc20Abi, readProvider);
|
|
267
|
+
const [network, feeData] = await Promise.all([
|
|
268
|
+
readProvider.getNetwork(),
|
|
269
|
+
readProvider.getFeeData(),
|
|
270
|
+
]);
|
|
271
|
+
const feeOverrides = await getFeeOverrides(net, readProvider, feeData);
|
|
272
|
+
const allowance: BigNumber = await erc20.allowance(address, poolAddress);
|
|
273
|
+
logger.debug(`Current ${tokenSymbol} allowance: ${ethers.utils.formatUnits(allowance, tokenDecimals)} ${tokenSymbol}`);
|
|
274
|
+
if (allowance.lt(depositAmount)) {
|
|
275
|
+
if (resolvedToken === 'usdt' && allowance.gt(0)) {
|
|
276
|
+
const resetApproveTx = await erc20.populateTransaction.approve(poolAddress, 0);
|
|
277
|
+
const unsignedResetApproveTx: ethers.utils.Deferrable<ethers.providers.TransactionRequest> = {
|
|
278
|
+
...resetApproveTx,
|
|
279
|
+
chainId: network.chainId,
|
|
280
|
+
...feeOverrides,
|
|
281
|
+
};
|
|
282
|
+
logger.info('waiting for user signature [approve-reset]');
|
|
283
|
+
await txSender(unsignedResetApproveTx, { stage: 'approve-reset', token: resolvedToken, chain: net.chainKey });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const approveTx = await erc20.populateTransaction.approve(poolAddress, depositAmount);
|
|
287
|
+
const unsignedApproveTx: ethers.utils.Deferrable<ethers.providers.TransactionRequest> = {
|
|
288
|
+
...approveTx,
|
|
289
|
+
chainId: network.chainId,
|
|
290
|
+
...feeOverrides,
|
|
291
|
+
};
|
|
292
|
+
logger.info('waiting for user signature [approve]');
|
|
293
|
+
await txSender(unsignedApproveTx, { stage: 'approve', token: resolvedToken, chain: net.chainKey });
|
|
294
|
+
} else {
|
|
295
|
+
logger.info(`${tokenSymbol} allowance is sufficient; skipping approve`);
|
|
296
|
+
}
|
|
297
|
+
// txSender is expected to wait for the approve to be mined before returning
|
|
298
|
+
// (the UI's txSender calls waitForTransactionReceipt for ERC20 deposits).
|
|
299
|
+
|
|
300
|
+
// Step 2: generate ZK proof with a fresh Merkle root now that approve is confirmed
|
|
301
|
+
logger.info('generating ZK proof')
|
|
302
|
+
const { args, extData } = await prepareTransaction({
|
|
303
|
+
inputs,
|
|
304
|
+
outputs: [outputUtxo],
|
|
305
|
+
encryptionKey,
|
|
306
|
+
keyBasePath,
|
|
307
|
+
token: resolvedToken,
|
|
308
|
+
network: net,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Step 3: send transact tx (no ETH value for ERC pool)
|
|
312
|
+
const gasLimit = await estimateTransactGasLimit({
|
|
313
|
+
pool,
|
|
314
|
+
args,
|
|
315
|
+
extData,
|
|
316
|
+
estimateOverrides: { from: address },
|
|
317
|
+
fallback: BigNumber.from(2000000),
|
|
318
|
+
});
|
|
319
|
+
const partialTx = await pool.populateTransaction.transact(args, extData, { gasLimit });
|
|
320
|
+
const [network2, feeData2] = await Promise.all([
|
|
321
|
+
readProvider.getNetwork(),
|
|
322
|
+
readProvider.getFeeData(),
|
|
323
|
+
]);
|
|
324
|
+
const unsignedTx: ethers.utils.Deferrable<ethers.providers.TransactionRequest> = {
|
|
325
|
+
...partialTx,
|
|
326
|
+
chainId: network2.chainId,
|
|
327
|
+
...(await getFeeOverrides(net, readProvider, feeData2)),
|
|
328
|
+
};
|
|
329
|
+
logger.info('waiting for user signature [deposit]');
|
|
330
|
+
const tx = await txSender(unsignedTx, { stage: 'deposit', token: resolvedToken, chain: net.chainKey });
|
|
331
|
+
|
|
332
|
+
logger.info('confirming transaction');
|
|
333
|
+
await confirmEncryptedOutput(extData.encryptedOutput1, resolvedToken, net);
|
|
334
|
+
return tx;
|
|
335
|
+
} else {
|
|
336
|
+
logger.info('generating ZK proof')
|
|
337
|
+
const { args, extData } = await prepareTransaction({
|
|
338
|
+
inputs,
|
|
339
|
+
outputs: [outputUtxo],
|
|
340
|
+
encryptionKey,
|
|
341
|
+
keyBasePath,
|
|
342
|
+
token: resolvedToken,
|
|
343
|
+
network: net,
|
|
344
|
+
});
|
|
345
|
+
const gasLimit = await estimateTransactGasLimit({
|
|
346
|
+
pool,
|
|
347
|
+
args,
|
|
348
|
+
extData,
|
|
349
|
+
estimateOverrides: {
|
|
350
|
+
from: address,
|
|
351
|
+
value: depositAmount,
|
|
352
|
+
},
|
|
353
|
+
fallback: BigNumber.from(3000000),
|
|
354
|
+
});
|
|
355
|
+
const partialTx = await pool.populateTransaction.transact(args, extData, {
|
|
356
|
+
value: depositAmount,
|
|
357
|
+
gasLimit,
|
|
358
|
+
});
|
|
359
|
+
const [network, nonce, feeData] = await Promise.all([
|
|
360
|
+
readProvider.getNetwork(),
|
|
361
|
+
readProvider.getTransactionCount(address, 'pending'),
|
|
362
|
+
readProvider.getFeeData(),
|
|
363
|
+
]);
|
|
364
|
+
|
|
365
|
+
const unsignedTx: ethers.utils.Deferrable<ethers.providers.TransactionRequest> = {
|
|
366
|
+
...partialTx,
|
|
367
|
+
nonce,
|
|
368
|
+
chainId: network.chainId,
|
|
369
|
+
};
|
|
370
|
+
Object.assign(unsignedTx, await getFeeOverrides(net, readProvider, feeData));
|
|
371
|
+
|
|
372
|
+
logger.info('waiting for user signature [deposit]');
|
|
373
|
+
const tx = await txSender(unsignedTx);
|
|
374
|
+
logger.info('confirming transaction');
|
|
375
|
+
await confirmEncryptedOutput(extData.encryptedOutput1, resolvedToken, net);
|
|
376
|
+
return tx;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function confirmEncryptedOutput(encryptedOutput1: string, token: PrivacyToken, net: NetworkConfig) {
|
|
381
|
+
logger.debug('verifying transaction on indexer...', encryptedOutput1);
|
|
382
|
+
let retryTimes = 0;
|
|
383
|
+
const intervalMs = 3000;
|
|
384
|
+
const maxRetries = 10;
|
|
385
|
+
const start = Date.now();
|
|
386
|
+
while (true) {
|
|
387
|
+
logger.debug('Confirming transaction..');
|
|
388
|
+
logger.debug(`retryTimes: ${retryTimes}`);
|
|
389
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
390
|
+
logger.debug('Fetching updated onchain state...');
|
|
391
|
+
let res = await fetch(net.indexerUrl + '/check_encrypted_output', {
|
|
392
|
+
method: 'POST',
|
|
393
|
+
headers: { 'Content-Type': 'application/json' },
|
|
394
|
+
body: JSON.stringify({ encryptedOutput: encryptedOutput1, token, chain: net.chainKey }),
|
|
395
|
+
});
|
|
396
|
+
let resJson = await res.json();
|
|
397
|
+
if (resJson.exists) {
|
|
398
|
+
logger.debug(`Top up successfully in ${((Date.now() - start) / 1000).toFixed(2)} seconds!`);
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
if (retryTimes >= maxRetries) {
|
|
402
|
+
throw new Error('Refresh the page to see latest balance.');
|
|
403
|
+
}
|
|
404
|
+
retryTimes++;
|
|
405
|
+
}
|
|
406
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { getBalance } from './balance.js'
|
|
2
|
+
export { deposit } from './deposit.js'
|
|
3
|
+
export { setLogger } from './utils/logger.js'
|
|
4
|
+
export { getRemoteConfig } from './utils/remoteConfig.js'
|
|
5
|
+
export type { FeeSnapshot, RemoteConfig } from './utils/remoteConfig.js'
|
|
6
|
+
export { withdraw } from './withdraw.js'
|
|
7
|
+
|
|
8
|
+
export { FEE_RECIPIENT_ADDRESS, INDEXER_URL, PRIVATE_USDC_CONTRACT_ADDRESS, USDC_CONTRACT_ADDRESS, USDC_DECIMALS } from './utils/constants.js'
|
|
9
|
+
export { clearCache } from './utils/utils.js'
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
BASE_NETWORK,
|
|
13
|
+
BNB_NETWORK,
|
|
14
|
+
ETH_NETWORK, getDefaultNetworkConfig, getNetworkConfig, NETWORKS, resolveNetwork
|
|
15
|
+
} from './utils/networkConfig.js'
|
|
16
|
+
export type { Erc20Token, NativeToken, NetworkConfig, PrivacyToken, SupportedChain } from './utils/networkConfig.js'
|