Compare commits
2 Commits
f411159f15
...
8041bd7f78
| Author | SHA1 | Date | |
|---|---|---|---|
|
8041bd7f78
|
|||
|
acb7aa72a1
|
103
.eslintrc.js
103
.eslintrc.js
@@ -1,66 +1,43 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
"env": {
|
env: {
|
||||||
"es2021": true,
|
es2021: true,
|
||||||
"node": true
|
node: true,
|
||||||
},
|
},
|
||||||
"extends": [
|
extends: [
|
||||||
"eslint:recommended",
|
'prettier',
|
||||||
"plugin:@typescript-eslint/recommended",
|
'eslint:recommended',
|
||||||
"plugin:import/recommended",
|
'plugin:@typescript-eslint/recommended',
|
||||||
"plugin:import/typescript",
|
'plugin:import/recommended',
|
||||||
"prettier",
|
'plugin:import/typescript',
|
||||||
"plugin:prettier/recommended",
|
'plugin:prettier/recommended',
|
||||||
],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"env": {
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
".eslintrc.{js,cjs}"
|
|
||||||
],
|
|
||||||
"parserOptions": {
|
|
||||||
"sourceType": "script"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"plugins": [
|
|
||||||
"@typescript-eslint",
|
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"prettier/prettier": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
singleQuote: true,
|
|
||||||
printWidth: 120
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"import/order": ["error"],
|
overrides: [
|
||||||
/**
|
{
|
||||||
"indent": [
|
env: {
|
||||||
"error",
|
node: true,
|
||||||
2
|
},
|
||||||
|
files: ['.eslintrc.{js,cjs}'],
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'script',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
**/
|
parser: '@typescript-eslint/parser',
|
||||||
"linebreak-style": [
|
parserOptions: {
|
||||||
"error",
|
ecmaVersion: 'latest',
|
||||||
"unix"
|
sourceType: 'module',
|
||||||
],
|
},
|
||||||
"quotes": [
|
plugins: ['@typescript-eslint', 'prettier'],
|
||||||
"error",
|
rules: {
|
||||||
"single"
|
'prettier/prettier': [
|
||||||
],
|
'error',
|
||||||
"semi": [
|
{
|
||||||
"error",
|
tabWidth: 4,
|
||||||
"always"
|
singleQuote: true,
|
||||||
],
|
},
|
||||||
"@typescript-eslint/no-unused-vars": ["warn"],
|
],
|
||||||
"@typescript-eslint/no-unused-expressions": ["off"]
|
'import/order': ['error'],
|
||||||
}
|
'@typescript-eslint/no-unused-vars': ['warn'],
|
||||||
}
|
'@typescript-eslint/no-unused-expressions': ['off'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
22
.nycrc
22
.nycrc
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"all": true,
|
"all": true,
|
||||||
"extension": [".ts"],
|
"extension": [".ts"],
|
||||||
"report-dir": "coverage",
|
"report-dir": "coverage",
|
||||||
"reporter": [
|
"reporter": [
|
||||||
"html",
|
"html",
|
||||||
"lcov",
|
"lcov",
|
||||||
"text",
|
"text",
|
||||||
"text-summary"
|
"text-summary"
|
||||||
],
|
],
|
||||||
"include": ["src/**/*.ts"],
|
"include": ["src/**/*.ts"],
|
||||||
"exclude": ["src/typechain/**/*"]
|
"exclude": ["src/typechain/**/*"]
|
||||||
}
|
}
|
||||||
4
dist/batch.d.ts
vendored
4
dist/batch.d.ts
vendored
@@ -80,7 +80,7 @@ export declare class BatchEventsService {
|
|||||||
retryMax: number;
|
retryMax: number;
|
||||||
retryOn: number;
|
retryOn: number;
|
||||||
constructor({ provider, contract, onProgress, concurrencySize, blocksPerRequest, shouldRetry, retryMax, retryOn, }: BatchEventServiceConstructor);
|
constructor({ provider, contract, onProgress, concurrencySize, blocksPerRequest, shouldRetry, retryMax, retryOn, }: BatchEventServiceConstructor);
|
||||||
getPastEvents({ fromBlock, toBlock, type }: EventInput): Promise<EventLog[]>;
|
getPastEvents({ fromBlock, toBlock, type, }: EventInput): Promise<EventLog[]>;
|
||||||
createBatchRequest(batchArray: EventInput[]): Promise<EventLog[]>[];
|
createBatchRequest(batchArray: EventInput[]): Promise<EventLog[]>[];
|
||||||
getBatchEvents({ fromBlock, toBlock, type }: EventInput): Promise<EventLog[]>;
|
getBatchEvents({ fromBlock, toBlock, type, }: EventInput): Promise<EventLog[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
4
dist/deposits.d.ts
vendored
4
dist/deposits.d.ts
vendored
@@ -30,7 +30,7 @@ export interface parsedInvoiceExec extends DepositType {
|
|||||||
}
|
}
|
||||||
export declare function parseNote(noteString: string): parsedNoteExec | undefined;
|
export declare function parseNote(noteString: string): parsedNoteExec | undefined;
|
||||||
export declare function parseInvoice(invoiceString: string): parsedInvoiceExec | undefined;
|
export declare function parseInvoice(invoiceString: string): parsedInvoiceExec | undefined;
|
||||||
export declare function createDeposit({ nullifier, secret }: createDepositParams): Promise<createDepositObject>;
|
export declare function createDeposit({ nullifier, secret, }: createDepositParams): Promise<createDepositObject>;
|
||||||
export interface DepositConstructor {
|
export interface DepositConstructor {
|
||||||
currency: string;
|
currency: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
@@ -56,7 +56,7 @@ export declare class Deposit {
|
|||||||
nullifierHex: string;
|
nullifierHex: string;
|
||||||
constructor({ currency, amount, netId, nullifier, secret, note, noteHex, invoice, commitmentHex, nullifierHex, }: DepositConstructor);
|
constructor({ currency, amount, netId, nullifier, secret, note, noteHex, invoice, commitmentHex, nullifierHex, }: DepositConstructor);
|
||||||
toString(): string;
|
toString(): string;
|
||||||
static createNote({ currency, amount, netId, nullifier, secret }: createNoteParams): Promise<Deposit>;
|
static createNote({ currency, amount, netId, nullifier, secret, }: createNoteParams): Promise<Deposit>;
|
||||||
static parseNote(noteString: string): Promise<Deposit>;
|
static parseNote(noteString: string): Promise<Deposit>;
|
||||||
}
|
}
|
||||||
export declare class Invoice {
|
export declare class Invoice {
|
||||||
|
|||||||
9
dist/encryptedNotes.d.ts
vendored
9
dist/encryptedNotes.d.ts
vendored
@@ -1,7 +1,6 @@
|
|||||||
import { EthEncryptedData } from '@metamask/eth-sig-util';
|
import { EthEncryptedData } from '@metamask/eth-sig-util';
|
||||||
import { Signer, Wallet } from 'ethers';
|
import { Signer, Wallet } from 'ethers';
|
||||||
import { EchoEvents, EncryptedNotesEvents } from './events';
|
import { EchoEvents, EncryptedNotesEvents } from './events';
|
||||||
import type { NetIdType } from './networkConfig';
|
|
||||||
export interface NoteToEncrypt {
|
export interface NoteToEncrypt {
|
||||||
address: string;
|
address: string;
|
||||||
noteHex: string;
|
noteHex: string;
|
||||||
@@ -11,22 +10,20 @@ export interface DecryptedNotes {
|
|||||||
address: string;
|
address: string;
|
||||||
noteHex: string;
|
noteHex: string;
|
||||||
}
|
}
|
||||||
export declare function packEncryptedMessage({ nonce, ephemPublicKey, ciphertext }: EthEncryptedData): string;
|
export declare function packEncryptedMessage({ nonce, ephemPublicKey, ciphertext, }: EthEncryptedData): string;
|
||||||
export declare function unpackEncryptedMessage(encryptedMessage: string): EthEncryptedData & {
|
export declare function unpackEncryptedMessage(encryptedMessage: string): EthEncryptedData & {
|
||||||
messageBuff: string;
|
messageBuff: string;
|
||||||
};
|
};
|
||||||
export interface NoteAccountConstructor {
|
export interface NoteAccountConstructor {
|
||||||
netId: NetIdType;
|
|
||||||
blockNumber?: number;
|
blockNumber?: number;
|
||||||
recoveryKey?: string;
|
recoveryKey?: string;
|
||||||
}
|
}
|
||||||
export declare class NoteAccount {
|
export declare class NoteAccount {
|
||||||
netId: NetIdType;
|
|
||||||
blockNumber?: number;
|
blockNumber?: number;
|
||||||
recoveryKey: string;
|
recoveryKey: string;
|
||||||
recoveryAddress: string;
|
recoveryAddress: string;
|
||||||
recoveryPublicKey: string;
|
recoveryPublicKey: string;
|
||||||
constructor({ netId, blockNumber, recoveryKey }: NoteAccountConstructor);
|
constructor({ blockNumber, recoveryKey }: NoteAccountConstructor);
|
||||||
/**
|
/**
|
||||||
* Intends to mock eth_getEncryptionPublicKey behavior from MetaMask
|
* Intends to mock eth_getEncryptionPublicKey behavior from MetaMask
|
||||||
* In order to make the recoveryKey retrival from Echoer possible from the bare private key
|
* In order to make the recoveryKey retrival from Echoer possible from the bare private key
|
||||||
@@ -39,7 +36,7 @@ export declare class NoteAccount {
|
|||||||
/**
|
/**
|
||||||
* Decrypt Echoer backuped note encryption account with private keys
|
* Decrypt Echoer backuped note encryption account with private keys
|
||||||
*/
|
*/
|
||||||
decryptSignerNoteAccounts(signer: Signer | Wallet, events: EchoEvents[]): Promise<NoteAccount[]>;
|
static decryptSignerNoteAccounts(signer: Signer | Wallet, events: EchoEvents[]): Promise<NoteAccount[]>;
|
||||||
decryptNotes(events: EncryptedNotesEvents[]): DecryptedNotes[];
|
decryptNotes(events: EncryptedNotesEvents[]): DecryptedNotes[];
|
||||||
encryptNote({ address, noteHex }: NoteToEncrypt): string;
|
encryptNote({ address, noteHex }: NoteToEncrypt): string;
|
||||||
}
|
}
|
||||||
|
|||||||
16
dist/events/base.d.ts
vendored
16
dist/events/base.d.ts
vendored
@@ -50,10 +50,10 @@ export declare class BaseEventsService<EventType extends MinimalEvents> {
|
|||||||
getTovarishType(): string;
|
getTovarishType(): string;
|
||||||
getGraphMethod(): string;
|
getGraphMethod(): string;
|
||||||
getGraphParams(): BaseGraphParams;
|
getGraphParams(): BaseGraphParams;
|
||||||
updateEventProgress({ percentage, type, fromBlock, toBlock, count }: Parameters<BatchEventOnProgress>[0]): void;
|
updateEventProgress({ percentage, type, fromBlock, toBlock, count, }: Parameters<BatchEventOnProgress>[0]): void;
|
||||||
updateBlockProgress({ percentage, currentIndex, totalIndex }: Parameters<BatchBlockOnProgress>[0]): void;
|
updateBlockProgress({ percentage, currentIndex, totalIndex, }: Parameters<BatchBlockOnProgress>[0]): void;
|
||||||
updateTransactionProgress({ percentage, currentIndex, totalIndex }: Parameters<BatchBlockOnProgress>[0]): void;
|
updateTransactionProgress({ percentage, currentIndex, totalIndex, }: Parameters<BatchBlockOnProgress>[0]): void;
|
||||||
updateGraphProgress({ type, fromBlock, toBlock, count }: Parameters<BatchGraphOnProgress>[0]): void;
|
updateGraphProgress({ type, fromBlock, toBlock, count, }: Parameters<BatchGraphOnProgress>[0]): void;
|
||||||
formatEvents(events: EventLog[]): Promise<EventType[]>;
|
formatEvents(events: EventLog[]): Promise<EventType[]>;
|
||||||
/**
|
/**
|
||||||
* Get saved or cached events
|
* Get saved or cached events
|
||||||
@@ -75,7 +75,7 @@ export declare class BaseEventsService<EventType extends MinimalEvents> {
|
|||||||
fromBlock: number;
|
fromBlock: number;
|
||||||
toBlock?: number;
|
toBlock?: number;
|
||||||
}): Promise<BaseEvents<EventType>>;
|
}): Promise<BaseEvents<EventType>>;
|
||||||
getLatestEvents({ fromBlock }: {
|
getLatestEvents({ fromBlock, }: {
|
||||||
fromBlock: number;
|
fromBlock: number;
|
||||||
}): Promise<BaseEvents<EventType>>;
|
}): Promise<BaseEvents<EventType>>;
|
||||||
validateEvents<S>({ events, lastBlock, hasNewEvents, }: BaseEvents<EventType> & {
|
validateEvents<S>({ events, lastBlock, hasNewEvents, }: BaseEvents<EventType> & {
|
||||||
@@ -120,7 +120,7 @@ export declare class BaseTornadoService extends BaseEventsService<DepositsEvents
|
|||||||
validateEvents<S>({ events, hasNewEvents, }: BaseEvents<DepositsEvents | WithdrawalsEvents> & {
|
validateEvents<S>({ events, hasNewEvents, }: BaseEvents<DepositsEvents | WithdrawalsEvents> & {
|
||||||
hasNewEvents?: boolean;
|
hasNewEvents?: boolean;
|
||||||
}): Promise<S>;
|
}): Promise<S>;
|
||||||
getLatestEvents({ fromBlock }: {
|
getLatestEvents({ fromBlock, }: {
|
||||||
fromBlock: number;
|
fromBlock: number;
|
||||||
}): Promise<BaseEvents<DepositsEvents | WithdrawalsEvents>>;
|
}): Promise<BaseEvents<DepositsEvents | WithdrawalsEvents>>;
|
||||||
}
|
}
|
||||||
@@ -132,7 +132,7 @@ export declare class BaseEchoService extends BaseEventsService<EchoEvents> {
|
|||||||
getInstanceName(): string;
|
getInstanceName(): string;
|
||||||
getGraphMethod(): string;
|
getGraphMethod(): string;
|
||||||
formatEvents(events: EventLog[]): Promise<EchoEvents[]>;
|
formatEvents(events: EventLog[]): Promise<EchoEvents[]>;
|
||||||
getEventsFromGraph({ fromBlock }: {
|
getEventsFromGraph({ fromBlock, }: {
|
||||||
fromBlock: number;
|
fromBlock: number;
|
||||||
}): Promise<BaseEvents<EchoEvents>>;
|
}): Promise<BaseEvents<EchoEvents>>;
|
||||||
}
|
}
|
||||||
@@ -180,7 +180,7 @@ export declare class BaseGovernanceService extends BaseEventsService<AllGovernan
|
|||||||
getTovarishType(): string;
|
getTovarishType(): string;
|
||||||
getGraphMethod(): string;
|
getGraphMethod(): string;
|
||||||
formatEvents(events: EventLog[]): Promise<AllGovernanceEvents[]>;
|
formatEvents(events: EventLog[]): Promise<AllGovernanceEvents[]>;
|
||||||
getEventsFromGraph({ fromBlock }: {
|
getEventsFromGraph({ fromBlock, }: {
|
||||||
fromBlock: number;
|
fromBlock: number;
|
||||||
}): Promise<BaseEvents<AllGovernanceEvents>>;
|
}): Promise<BaseEvents<AllGovernanceEvents>>;
|
||||||
getAllProposals(): Promise<GovernanceProposals[]>;
|
getAllProposals(): Promise<GovernanceProposals[]>;
|
||||||
|
|||||||
2
dist/events/db.d.ts
vendored
2
dist/events/db.d.ts
vendored
@@ -28,7 +28,7 @@ export declare class DBTornadoService extends BaseTornadoService {
|
|||||||
constructor(params: DBTornadoServiceConstructor);
|
constructor(params: DBTornadoServiceConstructor);
|
||||||
getEventsFromDB(): Promise<BaseEvents<DepositsEvents | WithdrawalsEvents>>;
|
getEventsFromDB(): Promise<BaseEvents<DepositsEvents | WithdrawalsEvents>>;
|
||||||
getEventsFromCache(): Promise<CachedEvents<DepositsEvents | WithdrawalsEvents>>;
|
getEventsFromCache(): Promise<CachedEvents<DepositsEvents | WithdrawalsEvents>>;
|
||||||
saveEvents({ events, lastBlock }: BaseEvents<DepositsEvents | WithdrawalsEvents>): Promise<void>;
|
saveEvents({ events, lastBlock, }: BaseEvents<DepositsEvents | WithdrawalsEvents>): Promise<void>;
|
||||||
}
|
}
|
||||||
export interface DBEchoServiceConstructor extends BaseEchoServiceConstructor {
|
export interface DBEchoServiceConstructor extends BaseEchoServiceConstructor {
|
||||||
staticUrl: string;
|
staticUrl: string;
|
||||||
|
|||||||
2
dist/graphql/index.d.ts
vendored
2
dist/graphql/index.d.ts
vendored
@@ -57,7 +57,7 @@ export interface getMetaReturns {
|
|||||||
lastSyncBlock: null | number;
|
lastSyncBlock: null | number;
|
||||||
hasIndexingErrors: null | boolean;
|
hasIndexingErrors: null | boolean;
|
||||||
}
|
}
|
||||||
export declare function getMeta({ graphApi, subgraphName, fetchDataOptions }: getMetaParams): Promise<getMetaReturns>;
|
export declare function getMeta({ graphApi, subgraphName, fetchDataOptions, }: getMetaParams): Promise<getMetaReturns>;
|
||||||
export interface GraphRegisters {
|
export interface GraphRegisters {
|
||||||
relayers: {
|
relayers: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
8
dist/idb.d.ts
vendored
8
dist/idb.d.ts
vendored
@@ -35,16 +35,16 @@ export declare class IndexedDB {
|
|||||||
key?: string;
|
key?: string;
|
||||||
count?: number;
|
count?: number;
|
||||||
}): Promise<T>;
|
}): Promise<T>;
|
||||||
getItem<T>({ storeName, key }: {
|
getItem<T>({ storeName, key, }: {
|
||||||
storeName: string;
|
storeName: string;
|
||||||
key: string;
|
key: string;
|
||||||
}): Promise<T | undefined>;
|
}): Promise<T | undefined>;
|
||||||
addItem({ storeName, data, key }: {
|
addItem({ storeName, data, key, }: {
|
||||||
storeName: string;
|
storeName: string;
|
||||||
data: any;
|
data: any;
|
||||||
key: string;
|
key: string;
|
||||||
}): Promise<void>;
|
}): Promise<void>;
|
||||||
putItem({ storeName, data, key }: {
|
putItem({ storeName, data, key, }: {
|
||||||
storeName: string;
|
storeName: string;
|
||||||
data: any;
|
data: any;
|
||||||
key?: string;
|
key?: string;
|
||||||
@@ -62,7 +62,7 @@ export declare class IndexedDB {
|
|||||||
getValue<T>(key: string): Promise<T | undefined>;
|
getValue<T>(key: string): Promise<T | undefined>;
|
||||||
setValue(key: string, data: any): Promise<void>;
|
setValue(key: string, data: any): Promise<void>;
|
||||||
delValue(key: string): Promise<void>;
|
delValue(key: string): Promise<void>;
|
||||||
clearStore({ storeName, mode }: {
|
clearStore({ storeName, mode, }: {
|
||||||
storeName: string;
|
storeName: string;
|
||||||
mode: IDBTransactionMode;
|
mode: IDBTransactionMode;
|
||||||
}): Promise<void>;
|
}): Promise<void>;
|
||||||
|
|||||||
1484
dist/index.js
vendored
1484
dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
1458
dist/index.mjs
vendored
1458
dist/index.mjs
vendored
File diff suppressed because it is too large
Load Diff
2
dist/merkleTree.d.ts
vendored
2
dist/merkleTree.d.ts
vendored
@@ -22,7 +22,7 @@ export declare class MerkleTreeService {
|
|||||||
merkleWorkerPath?: string;
|
merkleWorkerPath?: string;
|
||||||
constructor({ netId, amount, currency, Tornado, commitmentHex, merkleTreeHeight, emptyElement, merkleWorkerPath, }: MerkleTreeConstructor);
|
constructor({ netId, amount, currency, Tornado, commitmentHex, merkleTreeHeight, emptyElement, merkleWorkerPath, }: MerkleTreeConstructor);
|
||||||
createTree(events: Element[]): Promise<MerkleTree>;
|
createTree(events: Element[]): Promise<MerkleTree>;
|
||||||
createPartialTree({ edge, elements }: {
|
createPartialTree({ edge, elements, }: {
|
||||||
edge: TreeEdge;
|
edge: TreeEdge;
|
||||||
elements: Element[];
|
elements: Element[];
|
||||||
}): Promise<PartialMerkleTree>;
|
}): Promise<PartialMerkleTree>;
|
||||||
|
|||||||
38
dist/merkleTreeWorker.js
vendored
38
dist/merkleTreeWorker.js
vendored
@@ -1814,7 +1814,9 @@ class Mimc {
|
|||||||
}
|
}
|
||||||
async initMimc() {
|
async initMimc() {
|
||||||
this.sponge = await buildMimcSponge();
|
this.sponge = await buildMimcSponge();
|
||||||
this.hash = (left, right) => this.sponge?.F.toString(this.sponge?.multiHash([BigInt(left), BigInt(right)]));
|
this.hash = (left, right) => this.sponge?.F.toString(
|
||||||
|
this.sponge?.multiHash([BigInt(left), BigInt(right)])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
async getHash() {
|
async getHash() {
|
||||||
await this.mimcPromise;
|
await this.mimcPromise;
|
||||||
@@ -1835,18 +1837,27 @@ async function nodePostWork() {
|
|||||||
const { hash: hashFunction } = await mimc.getHash();
|
const { hash: hashFunction } = await mimc.getHash();
|
||||||
const { merkleTreeHeight, edge, elements, zeroElement } = workerThreads.workerData;
|
const { merkleTreeHeight, edge, elements, zeroElement } = workerThreads.workerData;
|
||||||
if (edge) {
|
if (edge) {
|
||||||
const merkleTree2 = new libExports.PartialMerkleTree(merkleTreeHeight, edge, elements, {
|
const merkleTree2 = new libExports.PartialMerkleTree(
|
||||||
zeroElement,
|
merkleTreeHeight,
|
||||||
hashFunction
|
edge,
|
||||||
});
|
elements,
|
||||||
workerThreads.parentPort.postMessage(merkleTree2.toString());
|
{
|
||||||
|
zeroElement,
|
||||||
|
hashFunction
|
||||||
|
}
|
||||||
|
);
|
||||||
|
workerThreads.parentPort.postMessage(
|
||||||
|
merkleTree2.toString()
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const merkleTree = new libExports.MerkleTree(merkleTreeHeight, elements, {
|
const merkleTree = new libExports.MerkleTree(merkleTreeHeight, elements, {
|
||||||
zeroElement,
|
zeroElement,
|
||||||
hashFunction
|
hashFunction
|
||||||
});
|
});
|
||||||
workerThreads.parentPort.postMessage(merkleTree.toString());
|
workerThreads.parentPort.postMessage(
|
||||||
|
merkleTree.toString()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (isNode && workerThreads) {
|
if (isNode && workerThreads) {
|
||||||
nodePostWork();
|
nodePostWork();
|
||||||
@@ -1861,10 +1872,15 @@ if (isNode && workerThreads) {
|
|||||||
const { hash: hashFunction } = await mimc.getHash();
|
const { hash: hashFunction } = await mimc.getHash();
|
||||||
const { merkleTreeHeight, edge, elements, zeroElement } = data;
|
const { merkleTreeHeight, edge, elements, zeroElement } = data;
|
||||||
if (edge) {
|
if (edge) {
|
||||||
const merkleTree2 = new libExports.PartialMerkleTree(merkleTreeHeight, edge, elements, {
|
const merkleTree2 = new libExports.PartialMerkleTree(
|
||||||
zeroElement,
|
merkleTreeHeight,
|
||||||
hashFunction
|
edge,
|
||||||
});
|
elements,
|
||||||
|
{
|
||||||
|
zeroElement,
|
||||||
|
hashFunction
|
||||||
|
}
|
||||||
|
);
|
||||||
postMessage(merkleTree2.toString());
|
postMessage(merkleTree2.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
58
dist/merkleTreeWorker.umd.js
vendored
58
dist/merkleTreeWorker.umd.js
vendored
@@ -101986,7 +101986,9 @@ class Mimc {
|
|||||||
}
|
}
|
||||||
async initMimc() {
|
async initMimc() {
|
||||||
this.sponge = await mimcsponge_buildMimcSponge();
|
this.sponge = await mimcsponge_buildMimcSponge();
|
||||||
this.hash = (left, right) => this.sponge?.F.toString(this.sponge?.multiHash([BigInt(left), BigInt(right)]));
|
this.hash = (left, right) => this.sponge?.F.toString(
|
||||||
|
this.sponge?.multiHash([BigInt(left), BigInt(right)])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
async getHash() {
|
async getHash() {
|
||||||
await this.mimcPromise;
|
await this.mimcPromise;
|
||||||
@@ -102011,7 +102013,9 @@ BigInt.prototype.toJSON = function() {
|
|||||||
};
|
};
|
||||||
const isNode = !process.browser && typeof globalThis.window === "undefined";
|
const isNode = !process.browser && typeof globalThis.window === "undefined";
|
||||||
const utils_crypto = isNode ? crypto_browserify.webcrypto : globalThis.crypto;
|
const utils_crypto = isNode ? crypto_browserify.webcrypto : globalThis.crypto;
|
||||||
const chunk = (arr, size) => [...Array(Math.ceil(arr.length / size))].map((_, i) => arr.slice(size * i, size + size * i));
|
const chunk = (arr, size) => [...Array(Math.ceil(arr.length / size))].map(
|
||||||
|
(_, i) => arr.slice(size * i, size + size * i)
|
||||||
|
);
|
||||||
function utils_sleep(ms) {
|
function utils_sleep(ms) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
@@ -102039,7 +102043,9 @@ function bufferToBytes(b) {
|
|||||||
return new Uint8Array(b.buffer);
|
return new Uint8Array(b.buffer);
|
||||||
}
|
}
|
||||||
function bytesToBase64(bytes) {
|
function bytesToBase64(bytes) {
|
||||||
return btoa(bytes.reduce((data, byte) => data + String.fromCharCode(byte), ""));
|
return btoa(
|
||||||
|
bytes.reduce((data, byte) => data + String.fromCharCode(byte), "")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
function base64ToBytes(base64) {
|
function base64ToBytes(base64) {
|
||||||
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
||||||
@@ -102054,7 +102060,11 @@ function hexToBytes(hexString) {
|
|||||||
if (hexString.length % 2 !== 0) {
|
if (hexString.length % 2 !== 0) {
|
||||||
hexString = "0" + hexString;
|
hexString = "0" + hexString;
|
||||||
}
|
}
|
||||||
return Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
|
return Uint8Array.from(
|
||||||
|
hexString.match(/.{1,2}/g).map(
|
||||||
|
(byte) => parseInt(byte, 16)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
function bytesToBN(bytes) {
|
function bytesToBN(bytes) {
|
||||||
return BigInt(bytesToHex(bytes));
|
return BigInt(bytesToHex(bytes));
|
||||||
@@ -102067,7 +102077,11 @@ function bnToBytes(bigint) {
|
|||||||
if (hexString.length % 2 !== 0) {
|
if (hexString.length % 2 !== 0) {
|
||||||
hexString = "0" + hexString;
|
hexString = "0" + hexString;
|
||||||
}
|
}
|
||||||
return Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
|
return Uint8Array.from(
|
||||||
|
hexString.match(/.{1,2}/g).map(
|
||||||
|
(byte) => parseInt(byte, 16)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
function leBuff2Int(bytes) {
|
function leBuff2Int(bytes) {
|
||||||
return new BN(bytes, 16, "le");
|
return new BN(bytes, 16, "le");
|
||||||
@@ -102128,18 +102142,27 @@ async function nodePostWork() {
|
|||||||
const { hash: hashFunction } = await mimc.getHash();
|
const { hash: hashFunction } = await mimc.getHash();
|
||||||
const { merkleTreeHeight, edge, elements, zeroElement } = (worker_threads_ignored_default()).workerData;
|
const { merkleTreeHeight, edge, elements, zeroElement } = (worker_threads_ignored_default()).workerData;
|
||||||
if (edge) {
|
if (edge) {
|
||||||
const merkleTree2 = new lib.PartialMerkleTree(merkleTreeHeight, edge, elements, {
|
const merkleTree2 = new lib.PartialMerkleTree(
|
||||||
zeroElement,
|
merkleTreeHeight,
|
||||||
hashFunction
|
edge,
|
||||||
});
|
elements,
|
||||||
worker_threads_ignored_default().parentPort.postMessage(merkleTree2.toString());
|
{
|
||||||
|
zeroElement,
|
||||||
|
hashFunction
|
||||||
|
}
|
||||||
|
);
|
||||||
|
worker_threads_ignored_default().parentPort.postMessage(
|
||||||
|
merkleTree2.toString()
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const merkleTree = new lib.MerkleTree(merkleTreeHeight, elements, {
|
const merkleTree = new lib.MerkleTree(merkleTreeHeight, elements, {
|
||||||
zeroElement,
|
zeroElement,
|
||||||
hashFunction
|
hashFunction
|
||||||
});
|
});
|
||||||
worker_threads_ignored_default().parentPort.postMessage(merkleTree.toString());
|
worker_threads_ignored_default().parentPort.postMessage(
|
||||||
|
merkleTree.toString()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (isNode && (worker_threads_ignored_default())) {
|
if (isNode && (worker_threads_ignored_default())) {
|
||||||
nodePostWork();
|
nodePostWork();
|
||||||
@@ -102154,10 +102177,15 @@ if (isNode && (worker_threads_ignored_default())) {
|
|||||||
const { hash: hashFunction } = await mimc.getHash();
|
const { hash: hashFunction } = await mimc.getHash();
|
||||||
const { merkleTreeHeight, edge, elements, zeroElement } = data;
|
const { merkleTreeHeight, edge, elements, zeroElement } = data;
|
||||||
if (edge) {
|
if (edge) {
|
||||||
const merkleTree2 = new lib.PartialMerkleTree(merkleTreeHeight, edge, elements, {
|
const merkleTree2 = new lib.PartialMerkleTree(
|
||||||
zeroElement,
|
merkleTreeHeight,
|
||||||
hashFunction
|
edge,
|
||||||
});
|
elements,
|
||||||
|
{
|
||||||
|
zeroElement,
|
||||||
|
hashFunction
|
||||||
|
}
|
||||||
|
);
|
||||||
postMessage(merkleTree2.toString());
|
postMessage(merkleTree2.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
6
dist/providers.d.ts
vendored
6
dist/providers.d.ts
vendored
@@ -50,7 +50,7 @@ export declare class TornadoWallet extends Wallet {
|
|||||||
gasLimitBump: number;
|
gasLimitBump: number;
|
||||||
gasFailover: boolean;
|
gasFailover: boolean;
|
||||||
bumpNonce: boolean;
|
bumpNonce: boolean;
|
||||||
constructor(key: string | SigningKey, provider?: Provider, { gasPriceBump, gasLimitBump, gasFailover, bumpNonce }?: TornadoWalletOptions);
|
constructor(key: string | SigningKey, provider?: Provider, { gasPriceBump, gasLimitBump, gasFailover, bumpNonce, }?: TornadoWalletOptions);
|
||||||
static fromMnemonic(mneomnic: string, provider: Provider, index?: number, options?: TornadoWalletOptions): TornadoWallet;
|
static fromMnemonic(mneomnic: string, provider: Provider, index?: number, options?: TornadoWalletOptions): TornadoWallet;
|
||||||
populateTransaction(tx: TransactionRequest): Promise<import("ethers").TransactionLike<string>>;
|
populateTransaction(tx: TransactionRequest): Promise<import("ethers").TransactionLike<string>>;
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ export declare class TornadoVoidSigner extends VoidSigner {
|
|||||||
gasLimitBump: number;
|
gasLimitBump: number;
|
||||||
gasFailover: boolean;
|
gasFailover: boolean;
|
||||||
bumpNonce: boolean;
|
bumpNonce: boolean;
|
||||||
constructor(address: string, provider?: Provider, { gasPriceBump, gasLimitBump, gasFailover, bumpNonce }?: TornadoWalletOptions);
|
constructor(address: string, provider?: Provider, { gasPriceBump, gasLimitBump, gasFailover, bumpNonce, }?: TornadoWalletOptions);
|
||||||
populateTransaction(tx: TransactionRequest): Promise<import("ethers").TransactionLike<string>>;
|
populateTransaction(tx: TransactionRequest): Promise<import("ethers").TransactionLike<string>>;
|
||||||
}
|
}
|
||||||
export declare class TornadoRpcSigner extends JsonRpcSigner {
|
export declare class TornadoRpcSigner extends JsonRpcSigner {
|
||||||
@@ -69,7 +69,7 @@ export declare class TornadoRpcSigner extends JsonRpcSigner {
|
|||||||
gasLimitBump: number;
|
gasLimitBump: number;
|
||||||
gasFailover: boolean;
|
gasFailover: boolean;
|
||||||
bumpNonce: boolean;
|
bumpNonce: boolean;
|
||||||
constructor(provider: JsonRpcApiProvider, address: string, { gasPriceBump, gasLimitBump, gasFailover, bumpNonce }?: TornadoWalletOptions);
|
constructor(provider: JsonRpcApiProvider, address: string, { gasPriceBump, gasLimitBump, gasFailover, bumpNonce, }?: TornadoWalletOptions);
|
||||||
sendUncheckedTransaction(tx: TransactionRequest): Promise<string>;
|
sendUncheckedTransaction(tx: TransactionRequest): Promise<string>;
|
||||||
}
|
}
|
||||||
export type connectWalletFunc = (...args: any[]) => Promise<void>;
|
export type connectWalletFunc = (...args: any[]) => Promise<void>;
|
||||||
|
|||||||
2
dist/relayerClient.d.ts
vendored
2
dist/relayerClient.d.ts
vendored
@@ -109,7 +109,7 @@ export function isRelayerUpdated(relayerVersion: string, netId: NetIdType) {
|
|||||||
return isUpdatedMajor && (Number(patch) >= 5 || netId !== NetId.MAINNET); // Patch checking - also backwards compatibility for Mainnet
|
return isUpdatedMajor && (Number(patch) >= 5 || netId !== NetId.MAINNET); // Patch checking - also backwards compatibility for Mainnet
|
||||||
}
|
}
|
||||||
**/
|
**/
|
||||||
export declare function calculateScore({ stakeBalance, tornadoServiceFee }: RelayerInfo): bigint;
|
export declare function calculateScore({ stakeBalance, tornadoServiceFee, }: RelayerInfo): bigint;
|
||||||
export declare function getWeightRandom(weightsScores: bigint[], random: bigint): number;
|
export declare function getWeightRandom(weightsScores: bigint[], random: bigint): number;
|
||||||
export interface RelayerInstanceList {
|
export interface RelayerInstanceList {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
|
|||||||
1384
dist/tornado.umd.js
vendored
1384
dist/tornado.umd.js
vendored
File diff suppressed because it is too large
Load Diff
2
dist/tornado.umd.min.js
vendored
2
dist/tornado.umd.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@ import '@nomicfoundation/hardhat-toolbox';
|
|||||||
import '@nomicfoundation/hardhat-ethers';
|
import '@nomicfoundation/hardhat-ethers';
|
||||||
|
|
||||||
const config: HardhatUserConfig = {
|
const config: HardhatUserConfig = {
|
||||||
solidity: '0.8.28',
|
solidity: '0.8.28',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
182
package.json
182
package.json
@@ -1,93 +1,93 @@
|
|||||||
{
|
{
|
||||||
"name": "@tornado/core",
|
"name": "@tornado/core",
|
||||||
"version": "1.0.19",
|
"version": "1.0.19",
|
||||||
"description": "An SDK for building applications on top of Privacy Pools",
|
"description": "An SDK for building applications on top of Privacy Pools",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.mjs",
|
"module": "./dist/index.mjs",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"unpkg": "./dist/tornado.umd.min.js",
|
"unpkg": "./dist/tornado.umd.min.js",
|
||||||
"jsdelivr": "./dist/tornado.umd.min.js",
|
"jsdelivr": "./dist/tornado.umd.min.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typechain": "typechain --target ethers-v6 --out-dir src/typechain src/abi/*.json",
|
"typechain": "typechain --target ethers-v6 --out-dir src/typechain src/abi/*.json",
|
||||||
"types": "tsc --declaration --emitDeclarationOnly -p tsconfig.build.json",
|
"types": "tsc --declaration --emitDeclarationOnly -p tsconfig.build.json",
|
||||||
"lint": "eslint src/**/*.ts test/**/*.ts --ext .ts --ignore-pattern src/typechain",
|
"lint": "eslint src/**/*.ts test/**/*.ts --ext .ts --ignore-pattern src/typechain",
|
||||||
"build:node": "rollup -c",
|
"build:node": "rollup -c",
|
||||||
"build:web": "webpack",
|
"build:web": "webpack",
|
||||||
"build": "yarn types && yarn build:node && yarn build:web",
|
"build": "yarn types && yarn build:node && yarn build:web",
|
||||||
"test": "nyc mocha --require ts-node/register --require source-map-support/register --recursive 'test/**/*.ts' --timeout '300000'"
|
"test": "nyc mocha --require ts-node/register --require source-map-support/register --recursive 'test/**/*.ts' --timeout '300000'"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
"src",
|
"src",
|
||||||
".eslintrc.js",
|
".eslintrc.js",
|
||||||
".gitattributes",
|
".gitattributes",
|
||||||
".gitignore",
|
".gitignore",
|
||||||
".npmrc",
|
".npmrc",
|
||||||
"logo.png",
|
"logo.png",
|
||||||
"logo2.png",
|
"logo2.png",
|
||||||
"rollup.config.mjs",
|
"rollup.config.mjs",
|
||||||
"tsconfig.json",
|
"tsconfig.json",
|
||||||
"yarn.lock"
|
"yarn.lock"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@metamask/eth-sig-util": "^8.0.0",
|
"@metamask/eth-sig-util": "^8.0.0",
|
||||||
"@tornado/contracts": "git+https://git.tornado.ws/tornadocontrib/tornado-contracts.git#1b1d707878c16a3dc60d295299d4f0e7ce6ba831",
|
"@tornado/contracts": "git+https://git.tornado.ws/tornadocontrib/tornado-contracts.git#1b1d707878c16a3dc60d295299d4f0e7ce6ba831",
|
||||||
"@tornado/fixed-merkle-tree": "^0.7.3",
|
"@tornado/fixed-merkle-tree": "^0.7.3",
|
||||||
"@tornado/snarkjs": "^0.1.20",
|
"@tornado/snarkjs": "^0.1.20",
|
||||||
"@tornado/websnark": "^0.0.4",
|
"@tornado/websnark": "^0.0.4",
|
||||||
"ajv": "^8.17.1",
|
"ajv": "^8.17.1",
|
||||||
"bn.js": "^5.2.1",
|
"bn.js": "^5.2.1",
|
||||||
"circomlibjs": "0.1.7",
|
"circomlibjs": "0.1.7",
|
||||||
"cross-fetch": "^4.0.0",
|
"cross-fetch": "^4.0.0",
|
||||||
"ethers": "^6.13.4",
|
"ethers": "^6.13.4",
|
||||||
"ffjavascript": "0.2.48",
|
"ffjavascript": "0.2.48",
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"idb": "^8.0.0"
|
"idb": "^8.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nomicfoundation/hardhat-chai-matchers": "^2.0.7",
|
"@nomicfoundation/hardhat-chai-matchers": "^2.0.7",
|
||||||
"@nomicfoundation/hardhat-ethers": "^3.0.8",
|
"@nomicfoundation/hardhat-ethers": "^3.0.8",
|
||||||
"@nomicfoundation/hardhat-ignition": "^0.15.5",
|
"@nomicfoundation/hardhat-ignition": "^0.15.5",
|
||||||
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.5",
|
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.5",
|
||||||
"@nomicfoundation/hardhat-network-helpers": "^1.0.11",
|
"@nomicfoundation/hardhat-network-helpers": "^1.0.11",
|
||||||
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
||||||
"@nomicfoundation/hardhat-verify": "^2.0.10",
|
"@nomicfoundation/hardhat-verify": "^2.0.10",
|
||||||
"@nomicfoundation/ignition-core": "^0.15.5",
|
"@nomicfoundation/ignition-core": "^0.15.5",
|
||||||
"@rollup/plugin-commonjs": "^28.0.1",
|
"@rollup/plugin-commonjs": "^28.0.1",
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||||
"@typechain/ethers-v6": "^0.5.1",
|
"@typechain/ethers-v6": "^0.5.1",
|
||||||
"@typechain/hardhat": "^9.1.0",
|
"@typechain/hardhat": "^9.1.0",
|
||||||
"@types/bn.js": "^5.1.6",
|
"@types/bn.js": "^5.1.6",
|
||||||
"@types/chai": "^4.2.0",
|
"@types/chai": "^4.2.0",
|
||||||
"@types/circomlibjs": "^0.1.6",
|
"@types/circomlibjs": "^0.1.6",
|
||||||
"@types/mocha": "^10.0.9",
|
"@types/mocha": "^10.0.9",
|
||||||
"@types/node": "^22.8.0",
|
"@types/node": "^22.8.0",
|
||||||
"@types/node-fetch": "^2.6.11",
|
"@types/node-fetch": "^2.6.11",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.11.0",
|
"@typescript-eslint/eslint-plugin": "^8.11.0",
|
||||||
"@typescript-eslint/parser": "^8.11.0",
|
"@typescript-eslint/parser": "^8.11.0",
|
||||||
"esbuild-loader": "^4.2.2",
|
"esbuild-loader": "^4.2.2",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.3",
|
"eslint-import-resolver-typescript": "^3.6.3",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"hardhat": "^2.22.10",
|
"hardhat": "^2.22.10",
|
||||||
"hardhat-gas-reporter": "^2.2.1",
|
"hardhat-gas-reporter": "^2.2.1",
|
||||||
"mocha": "^10.7.3",
|
"mocha": "^10.7.3",
|
||||||
"node-polyfill-webpack-plugin": "^4.0.0",
|
"node-polyfill-webpack-plugin": "^4.0.0",
|
||||||
"nyc": "^17.1.0",
|
"nyc": "^17.1.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"rollup": "^4.24.0",
|
"rollup": "^4.24.0",
|
||||||
"rollup-plugin-esbuild": "^6.1.1",
|
"rollup-plugin-esbuild": "^6.1.1",
|
||||||
"solidity-coverage": "^0.8.13",
|
"solidity-coverage": "^0.8.13",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsc": "^2.0.4",
|
"tsc": "^2.0.4",
|
||||||
"typechain": "^8.3.2",
|
"typechain": "^8.3.2",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"webpack": "^5.95.0",
|
"webpack": "^5.95.0",
|
||||||
"webpack-cli": "^5.1.4"
|
"webpack-cli": "^5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,81 +7,81 @@ import { readFileSync } from 'fs';
|
|||||||
const pkgJson = JSON.parse(readFileSync("./package.json"));
|
const pkgJson = JSON.parse(readFileSync("./package.json"));
|
||||||
|
|
||||||
const external = Object.keys(pkgJson.dependencies).concat(
|
const external = Object.keys(pkgJson.dependencies).concat(
|
||||||
Object.keys(pkgJson.optionalDependencies || {}),
|
Object.keys(pkgJson.optionalDependencies || {}),
|
||||||
[
|
[
|
||||||
'http-proxy-agent',
|
'http-proxy-agent',
|
||||||
'https-proxy-agent',
|
'https-proxy-agent',
|
||||||
'socks-proxy-agent',
|
'socks-proxy-agent',
|
||||||
'@tornado/websnark/src/utils',
|
'@tornado/websnark/src/utils',
|
||||||
'@tornado/websnark/src/groth16',
|
'@tornado/websnark/src/groth16',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const config = [
|
const config = [
|
||||||
{
|
{
|
||||||
input: 'src/index.ts',
|
input: 'src/index.ts',
|
||||||
output: [
|
output: [
|
||||||
{
|
{
|
||||||
file: pkgJson.main,
|
file: pkgJson.main,
|
||||||
format: "cjs",
|
format: "cjs",
|
||||||
esModule: false,
|
esModule: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
external,
|
external,
|
||||||
plugins: [
|
plugins: [
|
||||||
esbuild({
|
esbuild({
|
||||||
include: /\.[jt]sx?$/,
|
include: /\.[jt]sx?$/,
|
||||||
minify: false,
|
minify: false,
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
target: 'es2022',
|
target: 'es2022',
|
||||||
}),
|
}),
|
||||||
commonjs(),
|
commonjs(),
|
||||||
nodeResolve(),
|
nodeResolve(),
|
||||||
json()
|
json()
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: 'src/index.ts',
|
input: 'src/index.ts',
|
||||||
output: [
|
output: [
|
||||||
{
|
{
|
||||||
file: pkgJson.module,
|
file: pkgJson.module,
|
||||||
format: "esm",
|
format: "esm",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
external,
|
external,
|
||||||
plugins: [
|
plugins: [
|
||||||
esbuild({
|
esbuild({
|
||||||
include: /\.[jt]sx?$/,
|
include: /\.[jt]sx?$/,
|
||||||
minify: false,
|
minify: false,
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
target: 'es2022',
|
target: 'es2022',
|
||||||
}),
|
}),
|
||||||
nodeResolve(),
|
nodeResolve(),
|
||||||
json()
|
json()
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: 'src/merkleTreeWorker.ts',
|
input: 'src/merkleTreeWorker.ts',
|
||||||
output: [
|
output: [
|
||||||
{
|
{
|
||||||
file: 'dist/merkleTreeWorker.js',
|
file: 'dist/merkleTreeWorker.js',
|
||||||
format: "cjs",
|
format: "cjs",
|
||||||
esModule: false,
|
esModule: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
treeshake: 'smallest',
|
treeshake: 'smallest',
|
||||||
plugins: [
|
plugins: [
|
||||||
esbuild({
|
esbuild({
|
||||||
include: /\.[jt]sx?$/,
|
include: /\.[jt]sx?$/,
|
||||||
minify: false,
|
minify: false,
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
target: 'es2022',
|
target: 'es2022',
|
||||||
}),
|
}),
|
||||||
commonjs(),
|
commonjs(),
|
||||||
nodeResolve(),
|
nodeResolve(),
|
||||||
json()
|
json()
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
614
src/batch.ts
614
src/batch.ts
@@ -1,339 +1,399 @@
|
|||||||
import type { Provider, BlockTag, Block, TransactionResponse, BaseContract, ContractEventName, EventLog } from 'ethers';
|
import type {
|
||||||
|
Provider,
|
||||||
|
BlockTag,
|
||||||
|
Block,
|
||||||
|
TransactionResponse,
|
||||||
|
BaseContract,
|
||||||
|
ContractEventName,
|
||||||
|
EventLog,
|
||||||
|
} from 'ethers';
|
||||||
import { chunk, sleep } from './utils';
|
import { chunk, sleep } from './utils';
|
||||||
|
|
||||||
export interface BatchBlockServiceConstructor {
|
export interface BatchBlockServiceConstructor {
|
||||||
provider: Provider;
|
provider: Provider;
|
||||||
onProgress?: BatchBlockOnProgress;
|
onProgress?: BatchBlockOnProgress;
|
||||||
concurrencySize?: number;
|
concurrencySize?: number;
|
||||||
batchSize?: number;
|
batchSize?: number;
|
||||||
shouldRetry?: boolean;
|
shouldRetry?: boolean;
|
||||||
retryMax?: number;
|
retryMax?: number;
|
||||||
retryOn?: number;
|
retryOn?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BatchBlockOnProgress = ({
|
export type BatchBlockOnProgress = ({
|
||||||
percentage,
|
percentage,
|
||||||
currentIndex,
|
currentIndex,
|
||||||
totalIndex,
|
totalIndex,
|
||||||
}: {
|
}: {
|
||||||
percentage: number;
|
percentage: number;
|
||||||
currentIndex?: number;
|
currentIndex?: number;
|
||||||
totalIndex?: number;
|
totalIndex?: number;
|
||||||
}) => void;
|
}) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch blocks from web3 provider on batches
|
* Fetch blocks from web3 provider on batches
|
||||||
*/
|
*/
|
||||||
export class BatchBlockService {
|
export class BatchBlockService {
|
||||||
provider: Provider;
|
provider: Provider;
|
||||||
onProgress?: BatchBlockOnProgress;
|
onProgress?: BatchBlockOnProgress;
|
||||||
concurrencySize: number;
|
concurrencySize: number;
|
||||||
batchSize: number;
|
batchSize: number;
|
||||||
shouldRetry: boolean;
|
shouldRetry: boolean;
|
||||||
retryMax: number;
|
retryMax: number;
|
||||||
retryOn: number;
|
retryOn: number;
|
||||||
constructor({
|
constructor({
|
||||||
provider,
|
provider,
|
||||||
onProgress,
|
onProgress,
|
||||||
concurrencySize = 10,
|
concurrencySize = 10,
|
||||||
batchSize = 10,
|
batchSize = 10,
|
||||||
shouldRetry = true,
|
shouldRetry = true,
|
||||||
retryMax = 5,
|
retryMax = 5,
|
||||||
retryOn = 500,
|
retryOn = 500,
|
||||||
}: BatchBlockServiceConstructor) {
|
}: BatchBlockServiceConstructor) {
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.onProgress = onProgress;
|
this.onProgress = onProgress;
|
||||||
this.concurrencySize = concurrencySize;
|
this.concurrencySize = concurrencySize;
|
||||||
this.batchSize = batchSize;
|
this.batchSize = batchSize;
|
||||||
this.shouldRetry = shouldRetry;
|
this.shouldRetry = shouldRetry;
|
||||||
this.retryMax = retryMax;
|
this.retryMax = retryMax;
|
||||||
this.retryOn = retryOn;
|
this.retryOn = retryOn;
|
||||||
}
|
|
||||||
|
|
||||||
async getBlock(blockTag: BlockTag): Promise<Block> {
|
|
||||||
const blockObject = await this.provider.getBlock(blockTag);
|
|
||||||
|
|
||||||
// if the provider returns null (which they have corrupted block data for one of their nodes) throw and retry
|
|
||||||
if (!blockObject) {
|
|
||||||
const errMsg = `No block for ${blockTag}`;
|
|
||||||
throw new Error(errMsg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return blockObject;
|
async getBlock(blockTag: BlockTag): Promise<Block> {
|
||||||
}
|
const blockObject = await this.provider.getBlock(blockTag);
|
||||||
|
|
||||||
createBatchRequest(batchArray: BlockTag[][]): Promise<Block[]>[] {
|
// if the provider returns null (which they have corrupted block data for one of their nodes) throw and retry
|
||||||
return batchArray.map(async (blocks: BlockTag[], index: number) => {
|
if (!blockObject) {
|
||||||
// send batch requests on milliseconds to avoid including them on a single batch request
|
const errMsg = `No block for ${blockTag}`;
|
||||||
await sleep(20 * index);
|
throw new Error(errMsg);
|
||||||
|
|
||||||
return (async () => {
|
|
||||||
let retries = 0;
|
|
||||||
let err;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unmodified-loop-condition
|
|
||||||
while ((!this.shouldRetry && retries === 0) || (this.shouldRetry && retries < this.retryMax)) {
|
|
||||||
try {
|
|
||||||
return await Promise.all(blocks.map((b) => this.getBlock(b)));
|
|
||||||
} catch (e) {
|
|
||||||
retries++;
|
|
||||||
err = e;
|
|
||||||
|
|
||||||
// retry on 0.5 seconds
|
|
||||||
await sleep(this.retryOn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err;
|
return blockObject;
|
||||||
})();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getBatchBlocks(blocks: BlockTag[]): Promise<Block[]> {
|
|
||||||
let blockCount = 0;
|
|
||||||
const results: Block[] = [];
|
|
||||||
|
|
||||||
for (const chunks of chunk(blocks, this.concurrencySize * this.batchSize)) {
|
|
||||||
const chunksResult = (await Promise.all(this.createBatchRequest(chunk(chunks, this.batchSize)))).flat();
|
|
||||||
|
|
||||||
results.push(...chunksResult);
|
|
||||||
|
|
||||||
blockCount += chunks.length;
|
|
||||||
|
|
||||||
if (typeof this.onProgress === 'function') {
|
|
||||||
this.onProgress({
|
|
||||||
percentage: blockCount / blocks.length,
|
|
||||||
currentIndex: blockCount,
|
|
||||||
totalIndex: blocks.length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
createBatchRequest(batchArray: BlockTag[][]): Promise<Block[]>[] {
|
||||||
}
|
return batchArray.map(async (blocks: BlockTag[], index: number) => {
|
||||||
|
// send batch requests on milliseconds to avoid including them on a single batch request
|
||||||
|
await sleep(20 * index);
|
||||||
|
|
||||||
|
return (async () => {
|
||||||
|
let retries = 0;
|
||||||
|
let err;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unmodified-loop-condition
|
||||||
|
while (
|
||||||
|
(!this.shouldRetry && retries === 0) ||
|
||||||
|
(this.shouldRetry && retries < this.retryMax)
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return await Promise.all(
|
||||||
|
blocks.map((b) => this.getBlock(b)),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
retries++;
|
||||||
|
err = e;
|
||||||
|
|
||||||
|
// retry on 0.5 seconds
|
||||||
|
await sleep(this.retryOn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBatchBlocks(blocks: BlockTag[]): Promise<Block[]> {
|
||||||
|
let blockCount = 0;
|
||||||
|
const results: Block[] = [];
|
||||||
|
|
||||||
|
for (const chunks of chunk(
|
||||||
|
blocks,
|
||||||
|
this.concurrencySize * this.batchSize,
|
||||||
|
)) {
|
||||||
|
const chunksResult = (
|
||||||
|
await Promise.all(
|
||||||
|
this.createBatchRequest(chunk(chunks, this.batchSize)),
|
||||||
|
)
|
||||||
|
).flat();
|
||||||
|
|
||||||
|
results.push(...chunksResult);
|
||||||
|
|
||||||
|
blockCount += chunks.length;
|
||||||
|
|
||||||
|
if (typeof this.onProgress === 'function') {
|
||||||
|
this.onProgress({
|
||||||
|
percentage: blockCount / blocks.length,
|
||||||
|
currentIndex: blockCount,
|
||||||
|
totalIndex: blocks.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch transactions from web3 provider on batches
|
* Fetch transactions from web3 provider on batches
|
||||||
*/
|
*/
|
||||||
export class BatchTransactionService {
|
export class BatchTransactionService {
|
||||||
provider: Provider;
|
provider: Provider;
|
||||||
onProgress?: BatchBlockOnProgress;
|
onProgress?: BatchBlockOnProgress;
|
||||||
concurrencySize: number;
|
concurrencySize: number;
|
||||||
batchSize: number;
|
batchSize: number;
|
||||||
shouldRetry: boolean;
|
shouldRetry: boolean;
|
||||||
retryMax: number;
|
retryMax: number;
|
||||||
retryOn: number;
|
retryOn: number;
|
||||||
constructor({
|
constructor({
|
||||||
provider,
|
provider,
|
||||||
onProgress,
|
onProgress,
|
||||||
concurrencySize = 10,
|
concurrencySize = 10,
|
||||||
batchSize = 10,
|
batchSize = 10,
|
||||||
shouldRetry = true,
|
shouldRetry = true,
|
||||||
retryMax = 5,
|
retryMax = 5,
|
||||||
retryOn = 500,
|
retryOn = 500,
|
||||||
}: BatchBlockServiceConstructor) {
|
}: BatchBlockServiceConstructor) {
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.onProgress = onProgress;
|
this.onProgress = onProgress;
|
||||||
this.concurrencySize = concurrencySize;
|
this.concurrencySize = concurrencySize;
|
||||||
this.batchSize = batchSize;
|
this.batchSize = batchSize;
|
||||||
this.shouldRetry = shouldRetry;
|
this.shouldRetry = shouldRetry;
|
||||||
this.retryMax = retryMax;
|
this.retryMax = retryMax;
|
||||||
this.retryOn = retryOn;
|
this.retryOn = retryOn;
|
||||||
}
|
|
||||||
|
|
||||||
async getTransaction(txHash: string): Promise<TransactionResponse> {
|
|
||||||
const txObject = await this.provider.getTransaction(txHash);
|
|
||||||
|
|
||||||
if (!txObject) {
|
|
||||||
const errMsg = `No transaction for ${txHash}`;
|
|
||||||
throw new Error(errMsg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return txObject;
|
async getTransaction(txHash: string): Promise<TransactionResponse> {
|
||||||
}
|
const txObject = await this.provider.getTransaction(txHash);
|
||||||
|
|
||||||
createBatchRequest(batchArray: string[][]): Promise<TransactionResponse[]>[] {
|
if (!txObject) {
|
||||||
return batchArray.map(async (txs: string[], index: number) => {
|
const errMsg = `No transaction for ${txHash}`;
|
||||||
await sleep(20 * index);
|
throw new Error(errMsg);
|
||||||
|
|
||||||
return (async () => {
|
|
||||||
let retries = 0;
|
|
||||||
let err;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unmodified-loop-condition
|
|
||||||
while ((!this.shouldRetry && retries === 0) || (this.shouldRetry && retries < this.retryMax)) {
|
|
||||||
try {
|
|
||||||
return await Promise.all(txs.map((tx) => this.getTransaction(tx)));
|
|
||||||
} catch (e) {
|
|
||||||
retries++;
|
|
||||||
err = e;
|
|
||||||
|
|
||||||
// retry on 0.5 seconds
|
|
||||||
await sleep(this.retryOn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err;
|
return txObject;
|
||||||
})();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getBatchTransactions(txs: string[]): Promise<TransactionResponse[]> {
|
|
||||||
let txCount = 0;
|
|
||||||
const results = [];
|
|
||||||
|
|
||||||
for (const chunks of chunk(txs, this.concurrencySize * this.batchSize)) {
|
|
||||||
const chunksResult = (await Promise.all(this.createBatchRequest(chunk(chunks, this.batchSize)))).flat();
|
|
||||||
|
|
||||||
results.push(...chunksResult);
|
|
||||||
|
|
||||||
txCount += chunks.length;
|
|
||||||
|
|
||||||
if (typeof this.onProgress === 'function') {
|
|
||||||
this.onProgress({ percentage: txCount / txs.length, currentIndex: txCount, totalIndex: txs.length });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
createBatchRequest(
|
||||||
}
|
batchArray: string[][],
|
||||||
|
): Promise<TransactionResponse[]>[] {
|
||||||
|
return batchArray.map(async (txs: string[], index: number) => {
|
||||||
|
await sleep(20 * index);
|
||||||
|
|
||||||
|
return (async () => {
|
||||||
|
let retries = 0;
|
||||||
|
let err;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unmodified-loop-condition
|
||||||
|
while (
|
||||||
|
(!this.shouldRetry && retries === 0) ||
|
||||||
|
(this.shouldRetry && retries < this.retryMax)
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return await Promise.all(
|
||||||
|
txs.map((tx) => this.getTransaction(tx)),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
retries++;
|
||||||
|
err = e;
|
||||||
|
|
||||||
|
// retry on 0.5 seconds
|
||||||
|
await sleep(this.retryOn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBatchTransactions(txs: string[]): Promise<TransactionResponse[]> {
|
||||||
|
let txCount = 0;
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const chunks of chunk(
|
||||||
|
txs,
|
||||||
|
this.concurrencySize * this.batchSize,
|
||||||
|
)) {
|
||||||
|
const chunksResult = (
|
||||||
|
await Promise.all(
|
||||||
|
this.createBatchRequest(chunk(chunks, this.batchSize)),
|
||||||
|
)
|
||||||
|
).flat();
|
||||||
|
|
||||||
|
results.push(...chunksResult);
|
||||||
|
|
||||||
|
txCount += chunks.length;
|
||||||
|
|
||||||
|
if (typeof this.onProgress === 'function') {
|
||||||
|
this.onProgress({
|
||||||
|
percentage: txCount / txs.length,
|
||||||
|
currentIndex: txCount,
|
||||||
|
totalIndex: txs.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BatchEventServiceConstructor {
|
export interface BatchEventServiceConstructor {
|
||||||
provider: Provider;
|
provider: Provider;
|
||||||
contract: BaseContract;
|
contract: BaseContract;
|
||||||
onProgress?: BatchEventOnProgress;
|
onProgress?: BatchEventOnProgress;
|
||||||
concurrencySize?: number;
|
concurrencySize?: number;
|
||||||
blocksPerRequest?: number;
|
blocksPerRequest?: number;
|
||||||
shouldRetry?: boolean;
|
shouldRetry?: boolean;
|
||||||
retryMax?: number;
|
retryMax?: number;
|
||||||
retryOn?: number;
|
retryOn?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BatchEventOnProgress = ({
|
export type BatchEventOnProgress = ({
|
||||||
percentage,
|
percentage,
|
||||||
type,
|
type,
|
||||||
fromBlock,
|
fromBlock,
|
||||||
toBlock,
|
toBlock,
|
||||||
count,
|
count,
|
||||||
}: {
|
}: {
|
||||||
percentage: number;
|
percentage: number;
|
||||||
type?: ContractEventName;
|
type?: ContractEventName;
|
||||||
fromBlock?: number;
|
fromBlock?: number;
|
||||||
toBlock?: number;
|
toBlock?: number;
|
||||||
count?: number;
|
count?: number;
|
||||||
}) => void;
|
}) => void;
|
||||||
|
|
||||||
// To enable iteration only numbers are accepted for fromBlock input
|
// To enable iteration only numbers are accepted for fromBlock input
|
||||||
export interface EventInput {
|
export interface EventInput {
|
||||||
fromBlock: number;
|
fromBlock: number;
|
||||||
toBlock: number;
|
toBlock: number;
|
||||||
type: ContractEventName;
|
type: ContractEventName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch events from web3 provider on bulk
|
* Fetch events from web3 provider on bulk
|
||||||
*/
|
*/
|
||||||
export class BatchEventsService {
|
export class BatchEventsService {
|
||||||
provider: Provider;
|
provider: Provider;
|
||||||
contract: BaseContract;
|
contract: BaseContract;
|
||||||
onProgress?: BatchEventOnProgress;
|
onProgress?: BatchEventOnProgress;
|
||||||
concurrencySize: number;
|
concurrencySize: number;
|
||||||
blocksPerRequest: number;
|
blocksPerRequest: number;
|
||||||
shouldRetry: boolean;
|
shouldRetry: boolean;
|
||||||
retryMax: number;
|
retryMax: number;
|
||||||
retryOn: number;
|
retryOn: number;
|
||||||
constructor({
|
constructor({
|
||||||
provider,
|
provider,
|
||||||
contract,
|
contract,
|
||||||
onProgress,
|
onProgress,
|
||||||
concurrencySize = 10,
|
concurrencySize = 10,
|
||||||
blocksPerRequest = 2000,
|
blocksPerRequest = 2000,
|
||||||
shouldRetry = true,
|
shouldRetry = true,
|
||||||
retryMax = 5,
|
retryMax = 5,
|
||||||
retryOn = 500,
|
retryOn = 500,
|
||||||
}: BatchEventServiceConstructor) {
|
}: BatchEventServiceConstructor) {
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.contract = contract;
|
this.contract = contract;
|
||||||
this.onProgress = onProgress;
|
this.onProgress = onProgress;
|
||||||
this.concurrencySize = concurrencySize;
|
this.concurrencySize = concurrencySize;
|
||||||
this.blocksPerRequest = blocksPerRequest;
|
this.blocksPerRequest = blocksPerRequest;
|
||||||
this.shouldRetry = shouldRetry;
|
this.shouldRetry = shouldRetry;
|
||||||
this.retryMax = retryMax;
|
this.retryMax = retryMax;
|
||||||
this.retryOn = retryOn;
|
this.retryOn = retryOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPastEvents({ fromBlock, toBlock, type }: EventInput): Promise<EventLog[]> {
|
async getPastEvents({
|
||||||
let err;
|
fromBlock,
|
||||||
let retries = 0;
|
toBlock,
|
||||||
|
type,
|
||||||
|
}: EventInput): Promise<EventLog[]> {
|
||||||
|
let err;
|
||||||
|
let retries = 0;
|
||||||
|
|
||||||
// eslint-disable-next-line no-unmodified-loop-condition
|
// eslint-disable-next-line no-unmodified-loop-condition
|
||||||
while ((!this.shouldRetry && retries === 0) || (this.shouldRetry && retries < this.retryMax)) {
|
while (
|
||||||
try {
|
(!this.shouldRetry && retries === 0) ||
|
||||||
return (await this.contract.queryFilter(type, fromBlock, toBlock)) as EventLog[];
|
(this.shouldRetry && retries < this.retryMax)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
) {
|
||||||
} catch (e: any) {
|
try {
|
||||||
err = e;
|
return (await this.contract.queryFilter(
|
||||||
retries++;
|
type,
|
||||||
|
fromBlock,
|
||||||
|
toBlock,
|
||||||
|
)) as EventLog[];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} catch (e: any) {
|
||||||
|
err = e;
|
||||||
|
retries++;
|
||||||
|
|
||||||
// If provider.getBlockNumber returned last block that isn't accepted (happened on Avalanche/Gnosis),
|
// If provider.getBlockNumber returned last block that isn't accepted (happened on Avalanche/Gnosis),
|
||||||
// get events to last accepted block
|
// get events to last accepted block
|
||||||
if (e.message.includes('after last accepted block')) {
|
if (e.message.includes('after last accepted block')) {
|
||||||
const acceptedBlock = parseInt(e.message.split('after last accepted block ')[1]);
|
const acceptedBlock = parseInt(
|
||||||
toBlock = acceptedBlock;
|
e.message.split('after last accepted block ')[1],
|
||||||
|
);
|
||||||
|
toBlock = acceptedBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
// retry on 0.5 seconds
|
||||||
|
await sleep(this.retryOn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// retry on 0.5 seconds
|
throw err;
|
||||||
await sleep(this.retryOn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err;
|
createBatchRequest(batchArray: EventInput[]): Promise<EventLog[]>[] {
|
||||||
}
|
return batchArray.map(async (event: EventInput, index: number) => {
|
||||||
|
await sleep(20 * index);
|
||||||
|
|
||||||
createBatchRequest(batchArray: EventInput[]): Promise<EventLog[]>[] {
|
return this.getPastEvents(event);
|
||||||
return batchArray.map(async (event: EventInput, index: number) => {
|
|
||||||
await sleep(20 * index);
|
|
||||||
|
|
||||||
return this.getPastEvents(event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getBatchEvents({ fromBlock, toBlock, type = '*' }: EventInput): Promise<EventLog[]> {
|
|
||||||
if (!toBlock) {
|
|
||||||
toBlock = await this.provider.getBlockNumber();
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventsToSync = [];
|
|
||||||
|
|
||||||
for (let i = fromBlock; i < toBlock; i += this.blocksPerRequest) {
|
|
||||||
const j = i + this.blocksPerRequest - 1 > toBlock ? toBlock : i + this.blocksPerRequest - 1;
|
|
||||||
|
|
||||||
eventsToSync.push({ fromBlock: i, toBlock: j, type });
|
|
||||||
}
|
|
||||||
|
|
||||||
const events = [];
|
|
||||||
const eventChunk = chunk(eventsToSync, this.concurrencySize);
|
|
||||||
|
|
||||||
let chunkCount = 0;
|
|
||||||
|
|
||||||
for (const chunk of eventChunk) {
|
|
||||||
chunkCount++;
|
|
||||||
|
|
||||||
const fetchedEvents = (await Promise.all(this.createBatchRequest(chunk))).flat();
|
|
||||||
events.push(...fetchedEvents);
|
|
||||||
|
|
||||||
if (typeof this.onProgress === 'function') {
|
|
||||||
this.onProgress({
|
|
||||||
percentage: chunkCount / eventChunk.length,
|
|
||||||
type,
|
|
||||||
fromBlock: chunk[0].fromBlock,
|
|
||||||
toBlock: chunk[chunk.length - 1].toBlock,
|
|
||||||
count: fetchedEvents.length,
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return events;
|
async getBatchEvents({
|
||||||
}
|
fromBlock,
|
||||||
|
toBlock,
|
||||||
|
type = '*',
|
||||||
|
}: EventInput): Promise<EventLog[]> {
|
||||||
|
if (!toBlock) {
|
||||||
|
toBlock = await this.provider.getBlockNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventsToSync = [];
|
||||||
|
|
||||||
|
for (let i = fromBlock; i < toBlock; i += this.blocksPerRequest) {
|
||||||
|
const j =
|
||||||
|
i + this.blocksPerRequest - 1 > toBlock
|
||||||
|
? toBlock
|
||||||
|
: i + this.blocksPerRequest - 1;
|
||||||
|
|
||||||
|
eventsToSync.push({ fromBlock: i, toBlock: j, type });
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = [];
|
||||||
|
const eventChunk = chunk(eventsToSync, this.concurrencySize);
|
||||||
|
|
||||||
|
let chunkCount = 0;
|
||||||
|
|
||||||
|
for (const chunk of eventChunk) {
|
||||||
|
chunkCount++;
|
||||||
|
|
||||||
|
const fetchedEvents = (
|
||||||
|
await Promise.all(this.createBatchRequest(chunk))
|
||||||
|
).flat();
|
||||||
|
events.push(...fetchedEvents);
|
||||||
|
|
||||||
|
if (typeof this.onProgress === 'function') {
|
||||||
|
this.onProgress({
|
||||||
|
percentage: chunkCount / eventChunk.length,
|
||||||
|
type,
|
||||||
|
fromBlock: chunk[0].fromBlock,
|
||||||
|
toBlock: chunk[chunk.length - 1].toBlock,
|
||||||
|
count: fetchedEvents.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
export * from '@tornado/contracts';
|
export * from '@tornado/contracts';
|
||||||
export {
|
export {
|
||||||
Multicall,
|
Multicall,
|
||||||
Multicall__factory,
|
Multicall__factory,
|
||||||
OffchainOracle,
|
OffchainOracle,
|
||||||
OffchainOracle__factory,
|
OffchainOracle__factory,
|
||||||
OvmGasPriceOracle,
|
OvmGasPriceOracle,
|
||||||
OvmGasPriceOracle__factory,
|
OvmGasPriceOracle__factory,
|
||||||
ReverseRecords,
|
ReverseRecords,
|
||||||
ReverseRecords__factory,
|
ReverseRecords__factory,
|
||||||
ENSNameWrapper,
|
ENSNameWrapper,
|
||||||
ENSNameWrapper__factory,
|
ENSNameWrapper__factory,
|
||||||
ENSRegistry,
|
ENSRegistry,
|
||||||
ENSRegistry__factory,
|
ENSRegistry__factory,
|
||||||
ENSResolver,
|
ENSResolver,
|
||||||
ENSResolver__factory,
|
ENSResolver__factory,
|
||||||
} from './typechain';
|
} from './typechain';
|
||||||
|
|||||||
450
src/deposits.ts
450
src/deposits.ts
@@ -1,269 +1,303 @@
|
|||||||
import { bnToBytes, bytesToBN, leBuff2Int, leInt2Buff, rBigInt, toFixedHex } from './utils';
|
import {
|
||||||
|
bnToBytes,
|
||||||
|
bytesToBN,
|
||||||
|
leBuff2Int,
|
||||||
|
leInt2Buff,
|
||||||
|
rBigInt,
|
||||||
|
toFixedHex,
|
||||||
|
} from './utils';
|
||||||
import { buffPedersenHash } from './pedersen';
|
import { buffPedersenHash } from './pedersen';
|
||||||
import type { NetIdType } from './networkConfig';
|
import type { NetIdType } from './networkConfig';
|
||||||
|
|
||||||
export interface DepositType {
|
export interface DepositType {
|
||||||
currency: string;
|
currency: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
netId: NetIdType;
|
netId: NetIdType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface createDepositParams {
|
export interface createDepositParams {
|
||||||
nullifier: bigint;
|
nullifier: bigint;
|
||||||
secret: bigint;
|
secret: bigint;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface createDepositObject {
|
export interface createDepositObject {
|
||||||
preimage: Uint8Array;
|
preimage: Uint8Array;
|
||||||
noteHex: string;
|
noteHex: string;
|
||||||
commitment: bigint;
|
commitment: bigint;
|
||||||
commitmentHex: string;
|
commitmentHex: string;
|
||||||
nullifierHash: bigint;
|
nullifierHash: bigint;
|
||||||
nullifierHex: string;
|
nullifierHex: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface createNoteParams extends DepositType {
|
export interface createNoteParams extends DepositType {
|
||||||
nullifier?: bigint;
|
nullifier?: bigint;
|
||||||
secret?: bigint;
|
secret?: bigint;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface parsedNoteExec extends DepositType {
|
export interface parsedNoteExec extends DepositType {
|
||||||
note: string;
|
note: string;
|
||||||
noteHex: string;
|
noteHex: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface parsedInvoiceExec extends DepositType {
|
export interface parsedInvoiceExec extends DepositType {
|
||||||
invoice: string;
|
invoice: string;
|
||||||
commitmentHex: string;
|
commitmentHex: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseNote(noteString: string): parsedNoteExec | undefined {
|
export function parseNote(noteString: string): parsedNoteExec | undefined {
|
||||||
const noteRegex = /tornado-(?<currency>\w+)-(?<amount>[\d.]+)-(?<netId>\d+)-0x(?<noteHex>[0-9a-fA-F]{124})/g;
|
const noteRegex =
|
||||||
const match = noteRegex.exec(noteString);
|
/tornado-(?<currency>\w+)-(?<amount>[\d.]+)-(?<netId>\d+)-0x(?<noteHex>[0-9a-fA-F]{124})/g;
|
||||||
if (!match) {
|
const match = noteRegex.exec(noteString);
|
||||||
return;
|
if (!match) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { currency, amount, netId, noteHex } = match.groups as unknown as parsedNoteExec;
|
const { currency, amount, netId, noteHex } =
|
||||||
|
match.groups as unknown as parsedNoteExec;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currency: currency.toLowerCase(),
|
currency: currency.toLowerCase(),
|
||||||
amount,
|
amount,
|
||||||
netId: Number(netId),
|
netId: Number(netId),
|
||||||
noteHex: '0x' + noteHex,
|
noteHex: '0x' + noteHex,
|
||||||
note: noteString,
|
note: noteString,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseInvoice(invoiceString: string): parsedInvoiceExec | undefined {
|
export function parseInvoice(
|
||||||
const invoiceRegex =
|
invoiceString: string,
|
||||||
/tornadoInvoice-(?<currency>\w+)-(?<amount>[\d.]+)-(?<netId>\d+)-0x(?<commitmentHex>[0-9a-fA-F]{64})/g;
|
): parsedInvoiceExec | undefined {
|
||||||
const match = invoiceRegex.exec(invoiceString);
|
const invoiceRegex =
|
||||||
if (!match) {
|
/tornadoInvoice-(?<currency>\w+)-(?<amount>[\d.]+)-(?<netId>\d+)-0x(?<commitmentHex>[0-9a-fA-F]{64})/g;
|
||||||
return;
|
const match = invoiceRegex.exec(invoiceString);
|
||||||
}
|
if (!match) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { currency, amount, netId, commitmentHex } = match.groups as unknown as parsedInvoiceExec;
|
const { currency, amount, netId, commitmentHex } =
|
||||||
|
match.groups as unknown as parsedInvoiceExec;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currency: currency.toLowerCase(),
|
currency: currency.toLowerCase(),
|
||||||
amount,
|
amount,
|
||||||
netId: Number(netId),
|
netId: Number(netId),
|
||||||
commitmentHex: '0x' + commitmentHex,
|
commitmentHex: '0x' + commitmentHex,
|
||||||
invoice: invoiceString,
|
invoice: invoiceString,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createDeposit({ nullifier, secret }: createDepositParams): Promise<createDepositObject> {
|
export async function createDeposit({
|
||||||
const preimage = new Uint8Array([...leInt2Buff(nullifier), ...leInt2Buff(secret)]);
|
nullifier,
|
||||||
const noteHex = toFixedHex(bytesToBN(preimage), 62);
|
secret,
|
||||||
const commitment = BigInt(await buffPedersenHash(preimage));
|
}: createDepositParams): Promise<createDepositObject> {
|
||||||
const commitmentHex = toFixedHex(commitment);
|
const preimage = new Uint8Array([
|
||||||
const nullifierHash = BigInt(await buffPedersenHash(leInt2Buff(nullifier)));
|
...leInt2Buff(nullifier),
|
||||||
const nullifierHex = toFixedHex(nullifierHash);
|
...leInt2Buff(secret),
|
||||||
|
]);
|
||||||
|
const noteHex = toFixedHex(bytesToBN(preimage), 62);
|
||||||
|
const commitment = BigInt(await buffPedersenHash(preimage));
|
||||||
|
const commitmentHex = toFixedHex(commitment);
|
||||||
|
const nullifierHash = BigInt(await buffPedersenHash(leInt2Buff(nullifier)));
|
||||||
|
const nullifierHex = toFixedHex(nullifierHash);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
preimage,
|
preimage,
|
||||||
noteHex,
|
noteHex,
|
||||||
commitment,
|
commitment,
|
||||||
commitmentHex,
|
commitmentHex,
|
||||||
nullifierHash,
|
nullifierHash,
|
||||||
nullifierHex,
|
nullifierHex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DepositConstructor {
|
export interface DepositConstructor {
|
||||||
currency: string;
|
currency: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
netId: NetIdType;
|
netId: NetIdType;
|
||||||
nullifier: bigint;
|
nullifier: bigint;
|
||||||
secret: bigint;
|
secret: bigint;
|
||||||
note: string;
|
note: string;
|
||||||
noteHex: string;
|
noteHex: string;
|
||||||
invoice: string;
|
invoice: string;
|
||||||
commitmentHex: string;
|
commitmentHex: string;
|
||||||
nullifierHex: string;
|
nullifierHex: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Deposit {
|
export class Deposit {
|
||||||
currency: string;
|
currency: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
netId: NetIdType;
|
netId: NetIdType;
|
||||||
|
|
||||||
nullifier: bigint;
|
nullifier: bigint;
|
||||||
secret: bigint;
|
secret: bigint;
|
||||||
|
|
||||||
note: string;
|
note: string;
|
||||||
noteHex: string;
|
noteHex: string;
|
||||||
invoice: string;
|
invoice: string;
|
||||||
|
|
||||||
commitmentHex: string;
|
commitmentHex: string;
|
||||||
nullifierHex: string;
|
nullifierHex: string;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
currency,
|
currency,
|
||||||
amount,
|
amount,
|
||||||
netId,
|
netId,
|
||||||
nullifier,
|
nullifier,
|
||||||
secret,
|
secret,
|
||||||
note,
|
note,
|
||||||
noteHex,
|
noteHex,
|
||||||
invoice,
|
invoice,
|
||||||
commitmentHex,
|
commitmentHex,
|
||||||
nullifierHex,
|
nullifierHex,
|
||||||
}: DepositConstructor) {
|
}: DepositConstructor) {
|
||||||
this.currency = currency;
|
this.currency = currency;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.netId = netId;
|
this.netId = netId;
|
||||||
|
|
||||||
this.nullifier = nullifier;
|
this.nullifier = nullifier;
|
||||||
this.secret = secret;
|
this.secret = secret;
|
||||||
|
|
||||||
this.note = note;
|
this.note = note;
|
||||||
this.noteHex = noteHex;
|
this.noteHex = noteHex;
|
||||||
this.invoice = invoice;
|
this.invoice = invoice;
|
||||||
|
|
||||||
this.commitmentHex = commitmentHex;
|
this.commitmentHex = commitmentHex;
|
||||||
this.nullifierHex = nullifierHex;
|
this.nullifierHex = nullifierHex;
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
return JSON.stringify(
|
|
||||||
{
|
|
||||||
currency: this.currency,
|
|
||||||
amount: this.amount,
|
|
||||||
netId: this.netId,
|
|
||||||
nullifier: this.nullifier,
|
|
||||||
secret: this.secret,
|
|
||||||
note: this.note,
|
|
||||||
noteHex: this.noteHex,
|
|
||||||
invoice: this.invoice,
|
|
||||||
commitmentHex: this.commitmentHex,
|
|
||||||
nullifierHex: this.nullifierHex,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async createNote({ currency, amount, netId, nullifier, secret }: createNoteParams): Promise<Deposit> {
|
|
||||||
if (!nullifier) {
|
|
||||||
nullifier = rBigInt(31);
|
|
||||||
}
|
|
||||||
if (!secret) {
|
|
||||||
secret = rBigInt(31);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const depositObject = await createDeposit({
|
toString() {
|
||||||
nullifier,
|
return JSON.stringify(
|
||||||
secret,
|
{
|
||||||
});
|
currency: this.currency,
|
||||||
|
amount: this.amount,
|
||||||
const newDeposit = new Deposit({
|
netId: this.netId,
|
||||||
currency: currency.toLowerCase(),
|
nullifier: this.nullifier,
|
||||||
amount: amount,
|
secret: this.secret,
|
||||||
netId,
|
note: this.note,
|
||||||
note: `tornado-${currency.toLowerCase()}-${amount}-${netId}-${depositObject.noteHex}`,
|
noteHex: this.noteHex,
|
||||||
noteHex: depositObject.noteHex,
|
invoice: this.invoice,
|
||||||
invoice: `tornadoInvoice-${currency.toLowerCase()}-${amount}-${netId}-${depositObject.commitmentHex}`,
|
commitmentHex: this.commitmentHex,
|
||||||
nullifier: nullifier,
|
nullifierHex: this.nullifierHex,
|
||||||
secret: secret,
|
},
|
||||||
commitmentHex: depositObject.commitmentHex,
|
null,
|
||||||
nullifierHex: depositObject.nullifierHex,
|
2,
|
||||||
});
|
);
|
||||||
|
|
||||||
return newDeposit;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async parseNote(noteString: string): Promise<Deposit> {
|
|
||||||
const parsedNote = parseNote(noteString);
|
|
||||||
|
|
||||||
if (!parsedNote) {
|
|
||||||
throw new Error('The note has invalid format');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { currency, amount, netId, note, noteHex: parsedNoteHex } = parsedNote;
|
static async createNote({
|
||||||
|
currency,
|
||||||
|
amount,
|
||||||
|
netId,
|
||||||
|
nullifier,
|
||||||
|
secret,
|
||||||
|
}: createNoteParams): Promise<Deposit> {
|
||||||
|
if (!nullifier) {
|
||||||
|
nullifier = rBigInt(31);
|
||||||
|
}
|
||||||
|
if (!secret) {
|
||||||
|
secret = rBigInt(31);
|
||||||
|
}
|
||||||
|
|
||||||
const bytes = bnToBytes(parsedNoteHex);
|
const depositObject = await createDeposit({
|
||||||
const nullifier = BigInt(leBuff2Int(bytes.slice(0, 31)).toString());
|
nullifier,
|
||||||
const secret = BigInt(leBuff2Int(bytes.slice(31, 62)).toString());
|
secret,
|
||||||
|
});
|
||||||
|
|
||||||
const { noteHex, commitmentHex, nullifierHex } = await createDeposit({ nullifier, secret });
|
const newDeposit = new Deposit({
|
||||||
|
currency: currency.toLowerCase(),
|
||||||
|
amount: amount,
|
||||||
|
netId,
|
||||||
|
note: `tornado-${currency.toLowerCase()}-${amount}-${netId}-${depositObject.noteHex}`,
|
||||||
|
noteHex: depositObject.noteHex,
|
||||||
|
invoice: `tornadoInvoice-${currency.toLowerCase()}-${amount}-${netId}-${depositObject.commitmentHex}`,
|
||||||
|
nullifier: nullifier,
|
||||||
|
secret: secret,
|
||||||
|
commitmentHex: depositObject.commitmentHex,
|
||||||
|
nullifierHex: depositObject.nullifierHex,
|
||||||
|
});
|
||||||
|
|
||||||
const invoice = `tornadoInvoice-${currency}-${amount}-${netId}-${commitmentHex}`;
|
return newDeposit;
|
||||||
|
}
|
||||||
|
|
||||||
const newDeposit = new Deposit({
|
static async parseNote(noteString: string): Promise<Deposit> {
|
||||||
currency,
|
const parsedNote = parseNote(noteString);
|
||||||
amount,
|
|
||||||
netId,
|
|
||||||
note,
|
|
||||||
noteHex,
|
|
||||||
invoice,
|
|
||||||
nullifier,
|
|
||||||
secret,
|
|
||||||
commitmentHex,
|
|
||||||
nullifierHex,
|
|
||||||
});
|
|
||||||
|
|
||||||
return newDeposit;
|
if (!parsedNote) {
|
||||||
}
|
throw new Error('The note has invalid format');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
currency,
|
||||||
|
amount,
|
||||||
|
netId,
|
||||||
|
note,
|
||||||
|
noteHex: parsedNoteHex,
|
||||||
|
} = parsedNote;
|
||||||
|
|
||||||
|
const bytes = bnToBytes(parsedNoteHex);
|
||||||
|
const nullifier = BigInt(leBuff2Int(bytes.slice(0, 31)).toString());
|
||||||
|
const secret = BigInt(leBuff2Int(bytes.slice(31, 62)).toString());
|
||||||
|
|
||||||
|
const { noteHex, commitmentHex, nullifierHex } = await createDeposit({
|
||||||
|
nullifier,
|
||||||
|
secret,
|
||||||
|
});
|
||||||
|
|
||||||
|
const invoice = `tornadoInvoice-${currency}-${amount}-${netId}-${commitmentHex}`;
|
||||||
|
|
||||||
|
const newDeposit = new Deposit({
|
||||||
|
currency,
|
||||||
|
amount,
|
||||||
|
netId,
|
||||||
|
note,
|
||||||
|
noteHex,
|
||||||
|
invoice,
|
||||||
|
nullifier,
|
||||||
|
secret,
|
||||||
|
commitmentHex,
|
||||||
|
nullifierHex,
|
||||||
|
});
|
||||||
|
|
||||||
|
return newDeposit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Invoice {
|
export class Invoice {
|
||||||
currency: string;
|
currency: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
netId: NetIdType;
|
netId: NetIdType;
|
||||||
commitmentHex: string;
|
commitmentHex: string;
|
||||||
invoice: string;
|
invoice: string;
|
||||||
|
|
||||||
constructor(invoiceString: string) {
|
constructor(invoiceString: string) {
|
||||||
const parsedInvoice = parseInvoice(invoiceString);
|
const parsedInvoice = parseInvoice(invoiceString);
|
||||||
|
|
||||||
if (!parsedInvoice) {
|
if (!parsedInvoice) {
|
||||||
throw new Error('The invoice has invalid format');
|
throw new Error('The invoice has invalid format');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { currency, amount, netId, invoice, commitmentHex } =
|
||||||
|
parsedInvoice;
|
||||||
|
|
||||||
|
this.currency = currency;
|
||||||
|
this.amount = amount;
|
||||||
|
this.netId = netId;
|
||||||
|
|
||||||
|
this.commitmentHex = commitmentHex;
|
||||||
|
this.invoice = invoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { currency, amount, netId, invoice, commitmentHex } = parsedInvoice;
|
toString() {
|
||||||
|
return JSON.stringify(
|
||||||
this.currency = currency;
|
{
|
||||||
this.amount = amount;
|
currency: this.currency,
|
||||||
this.netId = netId;
|
amount: this.amount,
|
||||||
|
netId: this.netId,
|
||||||
this.commitmentHex = commitmentHex;
|
commitmentHex: this.commitmentHex,
|
||||||
this.invoice = invoice;
|
invoice: this.invoice,
|
||||||
}
|
},
|
||||||
|
null,
|
||||||
toString() {
|
2,
|
||||||
return JSON.stringify(
|
);
|
||||||
{
|
}
|
||||||
currency: this.currency,
|
|
||||||
amount: this.amount,
|
|
||||||
netId: this.netId,
|
|
||||||
commitmentHex: this.commitmentHex,
|
|
||||||
invoice: this.invoice,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,213 +1,257 @@
|
|||||||
import { getEncryptionPublicKey, encrypt, decrypt, EthEncryptedData } from '@metamask/eth-sig-util';
|
import {
|
||||||
import { JsonRpcApiProvider, Signer, Wallet, computeAddress, getAddress } from 'ethers';
|
getEncryptionPublicKey,
|
||||||
import { base64ToBytes, bytesToBase64, bytesToHex, hexToBytes, toFixedHex, concatBytes, rHex } from './utils';
|
encrypt,
|
||||||
|
decrypt,
|
||||||
|
EthEncryptedData,
|
||||||
|
} from '@metamask/eth-sig-util';
|
||||||
|
import {
|
||||||
|
JsonRpcApiProvider,
|
||||||
|
Signer,
|
||||||
|
Wallet,
|
||||||
|
computeAddress,
|
||||||
|
getAddress,
|
||||||
|
} from 'ethers';
|
||||||
|
import {
|
||||||
|
base64ToBytes,
|
||||||
|
bytesToBase64,
|
||||||
|
bytesToHex,
|
||||||
|
hexToBytes,
|
||||||
|
toFixedHex,
|
||||||
|
concatBytes,
|
||||||
|
rHex,
|
||||||
|
} from './utils';
|
||||||
import { EchoEvents, EncryptedNotesEvents } from './events';
|
import { EchoEvents, EncryptedNotesEvents } from './events';
|
||||||
import type { NetIdType } from './networkConfig';
|
|
||||||
|
|
||||||
export interface NoteToEncrypt {
|
export interface NoteToEncrypt {
|
||||||
address: string;
|
address: string;
|
||||||
noteHex: string;
|
noteHex: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DecryptedNotes {
|
export interface DecryptedNotes {
|
||||||
blockNumber: number;
|
blockNumber: number;
|
||||||
address: string;
|
address: string;
|
||||||
noteHex: string;
|
noteHex: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function packEncryptedMessage({ nonce, ephemPublicKey, ciphertext }: EthEncryptedData) {
|
export function packEncryptedMessage({
|
||||||
const nonceBuf = toFixedHex(bytesToHex(base64ToBytes(nonce)), 24);
|
nonce,
|
||||||
const ephemPublicKeyBuf = toFixedHex(bytesToHex(base64ToBytes(ephemPublicKey)), 32);
|
ephemPublicKey,
|
||||||
const ciphertextBuf = bytesToHex(base64ToBytes(ciphertext));
|
ciphertext,
|
||||||
|
}: EthEncryptedData) {
|
||||||
|
const nonceBuf = toFixedHex(bytesToHex(base64ToBytes(nonce)), 24);
|
||||||
|
const ephemPublicKeyBuf = toFixedHex(
|
||||||
|
bytesToHex(base64ToBytes(ephemPublicKey)),
|
||||||
|
32,
|
||||||
|
);
|
||||||
|
const ciphertextBuf = bytesToHex(base64ToBytes(ciphertext));
|
||||||
|
|
||||||
const messageBuff = concatBytes(hexToBytes(nonceBuf), hexToBytes(ephemPublicKeyBuf), hexToBytes(ciphertextBuf));
|
const messageBuff = concatBytes(
|
||||||
|
hexToBytes(nonceBuf),
|
||||||
|
hexToBytes(ephemPublicKeyBuf),
|
||||||
|
hexToBytes(ciphertextBuf),
|
||||||
|
);
|
||||||
|
|
||||||
return bytesToHex(messageBuff);
|
return bytesToHex(messageBuff);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unpackEncryptedMessage(encryptedMessage: string) {
|
export function unpackEncryptedMessage(encryptedMessage: string) {
|
||||||
const messageBuff = hexToBytes(encryptedMessage);
|
const messageBuff = hexToBytes(encryptedMessage);
|
||||||
const nonceBuf = bytesToBase64(messageBuff.slice(0, 24));
|
const nonceBuf = bytesToBase64(messageBuff.slice(0, 24));
|
||||||
const ephemPublicKeyBuf = bytesToBase64(messageBuff.slice(24, 56));
|
const ephemPublicKeyBuf = bytesToBase64(messageBuff.slice(24, 56));
|
||||||
const ciphertextBuf = bytesToBase64(messageBuff.slice(56));
|
const ciphertextBuf = bytesToBase64(messageBuff.slice(56));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messageBuff: bytesToHex(messageBuff),
|
messageBuff: bytesToHex(messageBuff),
|
||||||
version: 'x25519-xsalsa20-poly1305',
|
version: 'x25519-xsalsa20-poly1305',
|
||||||
nonce: nonceBuf,
|
nonce: nonceBuf,
|
||||||
ephemPublicKey: ephemPublicKeyBuf,
|
ephemPublicKey: ephemPublicKeyBuf,
|
||||||
ciphertext: ciphertextBuf,
|
ciphertext: ciphertextBuf,
|
||||||
} as EthEncryptedData & {
|
} as EthEncryptedData & {
|
||||||
messageBuff: string;
|
messageBuff: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NoteAccountConstructor {
|
export interface NoteAccountConstructor {
|
||||||
netId: NetIdType;
|
blockNumber?: number;
|
||||||
blockNumber?: number;
|
// hex
|
||||||
// hex
|
recoveryKey?: string;
|
||||||
recoveryKey?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NoteAccount {
|
export class NoteAccount {
|
||||||
netId: NetIdType;
|
blockNumber?: number;
|
||||||
blockNumber?: number;
|
// Dedicated 32 bytes private key only used for note encryption, backed up to an Echoer and local for future derivation
|
||||||
// Dedicated 32 bytes private key only used for note encryption, backed up to an Echoer and local for future derivation
|
// Note that unlike the private key it shouldn't have the 0x prefix
|
||||||
// Note that unlike the private key it shouldn't have the 0x prefix
|
recoveryKey: string;
|
||||||
recoveryKey: string;
|
// Address derived from recoveryKey, only used for frontend UI
|
||||||
// Address derived from recoveryKey, only used for frontend UI
|
recoveryAddress: string;
|
||||||
recoveryAddress: string;
|
// Note encryption public key derived from recoveryKey
|
||||||
// Note encryption public key derived from recoveryKey
|
recoveryPublicKey: string;
|
||||||
recoveryPublicKey: string;
|
|
||||||
|
|
||||||
constructor({ netId, blockNumber, recoveryKey }: NoteAccountConstructor) {
|
constructor({ blockNumber, recoveryKey }: NoteAccountConstructor) {
|
||||||
if (!recoveryKey) {
|
if (!recoveryKey) {
|
||||||
recoveryKey = rHex(32).slice(2);
|
recoveryKey = rHex(32).slice(2);
|
||||||
}
|
|
||||||
|
|
||||||
this.netId = Math.floor(Number(netId));
|
|
||||||
this.blockNumber = blockNumber;
|
|
||||||
this.recoveryKey = recoveryKey;
|
|
||||||
this.recoveryAddress = computeAddress('0x' + recoveryKey);
|
|
||||||
this.recoveryPublicKey = getEncryptionPublicKey(recoveryKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intends to mock eth_getEncryptionPublicKey behavior from MetaMask
|
|
||||||
* In order to make the recoveryKey retrival from Echoer possible from the bare private key
|
|
||||||
*/
|
|
||||||
static async getSignerPublicKey(signer: Signer | Wallet) {
|
|
||||||
if ((signer as Wallet).privateKey) {
|
|
||||||
const wallet = signer as Wallet;
|
|
||||||
const privateKey = wallet.privateKey.slice(0, 2) === '0x' ? wallet.privateKey.slice(2) : wallet.privateKey;
|
|
||||||
|
|
||||||
// Should return base64 encoded public key
|
|
||||||
return getEncryptionPublicKey(privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = signer.provider as JsonRpcApiProvider;
|
|
||||||
|
|
||||||
return (await provider.send('eth_getEncryptionPublicKey', [
|
|
||||||
(signer as Signer & { address: string }).address,
|
|
||||||
])) as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function intends to provide an encrypted value of recoveryKey for an on-chain Echoer backup purpose
|
|
||||||
// Thus, the pubKey should be derived by a Wallet instance or from Web3 wallets
|
|
||||||
// pubKey: base64 encoded 32 bytes key from https://docs.metamask.io/wallet/reference/eth_getencryptionpublickey/
|
|
||||||
getEncryptedAccount(walletPublicKey: string) {
|
|
||||||
const encryptedData = encrypt({
|
|
||||||
publicKey: walletPublicKey,
|
|
||||||
data: this.recoveryKey,
|
|
||||||
version: 'x25519-xsalsa20-poly1305',
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = packEncryptedMessage(encryptedData);
|
|
||||||
|
|
||||||
return {
|
|
||||||
// Use this later to save hexPrivateKey generated with
|
|
||||||
// Buffer.from(JSON.stringify(encryptedData)).toString('hex')
|
|
||||||
// As we don't use buffer with this library we should leave UI to do the rest
|
|
||||||
encryptedData,
|
|
||||||
// Data that could be used as an echo(data) params
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypt Echoer backuped note encryption account with private keys
|
|
||||||
*/
|
|
||||||
async decryptSignerNoteAccounts(signer: Signer | Wallet, events: EchoEvents[]): Promise<NoteAccount[]> {
|
|
||||||
const signerAddress = (signer as (Signer & { address: string }) | Wallet).address;
|
|
||||||
|
|
||||||
const decryptedEvents = [];
|
|
||||||
|
|
||||||
for (const event of events) {
|
|
||||||
if (event.address !== signerAddress) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const unpackedMessage = unpackEncryptedMessage(event.encryptedAccount);
|
|
||||||
|
|
||||||
let recoveryKey;
|
|
||||||
|
|
||||||
if ((signer as Wallet).privateKey) {
|
|
||||||
const wallet = signer as Wallet;
|
|
||||||
const privateKey = wallet.privateKey.slice(0, 2) === '0x' ? wallet.privateKey.slice(2) : wallet.privateKey;
|
|
||||||
|
|
||||||
recoveryKey = decrypt({
|
|
||||||
encryptedData: unpackedMessage,
|
|
||||||
privateKey,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const { version, nonce, ephemPublicKey, ciphertext } = unpackedMessage;
|
|
||||||
|
|
||||||
const unpackedBuffer = bytesToHex(
|
|
||||||
new TextEncoder().encode(
|
|
||||||
JSON.stringify({
|
|
||||||
version,
|
|
||||||
nonce,
|
|
||||||
ephemPublicKey,
|
|
||||||
ciphertext,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const provider = signer.provider as JsonRpcApiProvider;
|
|
||||||
|
|
||||||
recoveryKey = await provider.send('eth_decrypt', [unpackedBuffer, signerAddress]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptedEvents.push(
|
this.blockNumber = blockNumber;
|
||||||
new NoteAccount({
|
this.recoveryKey = recoveryKey;
|
||||||
netId: this.netId,
|
this.recoveryAddress = computeAddress('0x' + recoveryKey);
|
||||||
blockNumber: event.blockNumber,
|
this.recoveryPublicKey = getEncryptionPublicKey(recoveryKey);
|
||||||
recoveryKey,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
// decryption may fail for invalid accounts
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return decryptedEvents;
|
/**
|
||||||
}
|
* Intends to mock eth_getEncryptionPublicKey behavior from MetaMask
|
||||||
|
* In order to make the recoveryKey retrival from Echoer possible from the bare private key
|
||||||
|
*/
|
||||||
|
static async getSignerPublicKey(signer: Signer | Wallet) {
|
||||||
|
if ((signer as Wallet).privateKey) {
|
||||||
|
const wallet = signer as Wallet;
|
||||||
|
const privateKey =
|
||||||
|
wallet.privateKey.slice(0, 2) === '0x'
|
||||||
|
? wallet.privateKey.slice(2)
|
||||||
|
: wallet.privateKey;
|
||||||
|
|
||||||
decryptNotes(events: EncryptedNotesEvents[]): DecryptedNotes[] {
|
// Should return base64 encoded public key
|
||||||
const decryptedEvents = [];
|
return getEncryptionPublicKey(privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
for (const event of events) {
|
const provider = signer.provider as JsonRpcApiProvider;
|
||||||
try {
|
|
||||||
const unpackedMessage = unpackEncryptedMessage(event.encryptedNote);
|
|
||||||
|
|
||||||
const [address, noteHex] = decrypt({
|
return (await provider.send('eth_getEncryptionPublicKey', [
|
||||||
encryptedData: unpackedMessage,
|
(signer as Signer & { address: string }).address,
|
||||||
privateKey: this.recoveryKey,
|
])) as string;
|
||||||
}).split('-');
|
}
|
||||||
|
|
||||||
decryptedEvents.push({
|
// This function intends to provide an encrypted value of recoveryKey for an on-chain Echoer backup purpose
|
||||||
blockNumber: event.blockNumber,
|
// Thus, the pubKey should be derived by a Wallet instance or from Web3 wallets
|
||||||
address: getAddress(address),
|
// pubKey: base64 encoded 32 bytes key from https://docs.metamask.io/wallet/reference/eth_getencryptionpublickey/
|
||||||
noteHex,
|
getEncryptedAccount(walletPublicKey: string) {
|
||||||
|
const encryptedData = encrypt({
|
||||||
|
publicKey: walletPublicKey,
|
||||||
|
data: this.recoveryKey,
|
||||||
|
version: 'x25519-xsalsa20-poly1305',
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
// decryption may fail for foreign notes
|
const data = packEncryptedMessage(encryptedData);
|
||||||
continue;
|
|
||||||
}
|
return {
|
||||||
|
// Use this later to save hexPrivateKey generated with
|
||||||
|
// Buffer.from(JSON.stringify(encryptedData)).toString('hex')
|
||||||
|
// As we don't use buffer with this library we should leave UI to do the rest
|
||||||
|
encryptedData,
|
||||||
|
// Data that could be used as an echo(data) params
|
||||||
|
data,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return decryptedEvents;
|
/**
|
||||||
}
|
* Decrypt Echoer backuped note encryption account with private keys
|
||||||
|
*/
|
||||||
|
static async decryptSignerNoteAccounts(
|
||||||
|
signer: Signer | Wallet,
|
||||||
|
events: EchoEvents[],
|
||||||
|
): Promise<NoteAccount[]> {
|
||||||
|
const signerAddress = (
|
||||||
|
signer as (Signer & { address: string }) | Wallet
|
||||||
|
).address;
|
||||||
|
|
||||||
encryptNote({ address, noteHex }: NoteToEncrypt) {
|
const decryptedEvents = [];
|
||||||
const encryptedData = encrypt({
|
|
||||||
publicKey: this.recoveryPublicKey,
|
|
||||||
data: `${address}-${noteHex}`,
|
|
||||||
version: 'x25519-xsalsa20-poly1305',
|
|
||||||
});
|
|
||||||
|
|
||||||
return packEncryptedMessage(encryptedData);
|
for (const event of events) {
|
||||||
}
|
if (event.address !== signerAddress) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const unpackedMessage = unpackEncryptedMessage(
|
||||||
|
event.encryptedAccount,
|
||||||
|
);
|
||||||
|
|
||||||
|
let recoveryKey;
|
||||||
|
|
||||||
|
if ((signer as Wallet).privateKey) {
|
||||||
|
const wallet = signer as Wallet;
|
||||||
|
const privateKey =
|
||||||
|
wallet.privateKey.slice(0, 2) === '0x'
|
||||||
|
? wallet.privateKey.slice(2)
|
||||||
|
: wallet.privateKey;
|
||||||
|
|
||||||
|
recoveryKey = decrypt({
|
||||||
|
encryptedData: unpackedMessage,
|
||||||
|
privateKey,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const { version, nonce, ephemPublicKey, ciphertext } =
|
||||||
|
unpackedMessage;
|
||||||
|
|
||||||
|
const unpackedBuffer = bytesToHex(
|
||||||
|
new TextEncoder().encode(
|
||||||
|
JSON.stringify({
|
||||||
|
version,
|
||||||
|
nonce,
|
||||||
|
ephemPublicKey,
|
||||||
|
ciphertext,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const provider = signer.provider as JsonRpcApiProvider;
|
||||||
|
|
||||||
|
recoveryKey = await provider.send('eth_decrypt', [
|
||||||
|
unpackedBuffer,
|
||||||
|
signerAddress,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedEvents.push(
|
||||||
|
new NoteAccount({
|
||||||
|
blockNumber: event.blockNumber,
|
||||||
|
recoveryKey,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// decryption may fail for invalid accounts
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptedEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptNotes(events: EncryptedNotesEvents[]): DecryptedNotes[] {
|
||||||
|
const decryptedEvents = [];
|
||||||
|
|
||||||
|
for (const event of events) {
|
||||||
|
try {
|
||||||
|
const unpackedMessage = unpackEncryptedMessage(
|
||||||
|
event.encryptedNote,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [address, noteHex] = decrypt({
|
||||||
|
encryptedData: unpackedMessage,
|
||||||
|
privateKey: this.recoveryKey,
|
||||||
|
}).split('-');
|
||||||
|
|
||||||
|
decryptedEvents.push({
|
||||||
|
blockNumber: event.blockNumber,
|
||||||
|
address: getAddress(address),
|
||||||
|
noteHex,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// decryption may fail for foreign notes
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptedEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptNote({ address, noteHex }: NoteToEncrypt) {
|
||||||
|
const encryptedData = encrypt({
|
||||||
|
publicKey: this.recoveryPublicKey,
|
||||||
|
data: `${address}-${noteHex}`,
|
||||||
|
version: 'x25519-xsalsa20-poly1305',
|
||||||
|
});
|
||||||
|
|
||||||
|
return packEncryptedMessage(encryptedData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
238
src/ens.ts
238
src/ens.ts
@@ -1,145 +1,175 @@
|
|||||||
import { namehash, EnsResolver, AbstractProvider, keccak256, Signer } from 'ethers';
|
import {
|
||||||
|
namehash,
|
||||||
|
EnsResolver,
|
||||||
|
AbstractProvider,
|
||||||
|
keccak256,
|
||||||
|
Signer,
|
||||||
|
} from 'ethers';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ENSNameWrapper,
|
ENSNameWrapper,
|
||||||
ENSNameWrapper__factory,
|
ENSNameWrapper__factory,
|
||||||
ENSRegistry,
|
ENSRegistry,
|
||||||
ENSRegistry__factory,
|
ENSRegistry__factory,
|
||||||
ENSResolver,
|
ENSResolver,
|
||||||
ENSResolver__factory,
|
ENSResolver__factory,
|
||||||
} from './typechain';
|
} from './typechain';
|
||||||
import { bytesToHex, isHex } from './utils';
|
import { bytesToHex, isHex } from './utils';
|
||||||
import { NetId, NetIdType } from './networkConfig';
|
import { NetId, NetIdType } from './networkConfig';
|
||||||
|
|
||||||
export function encodedLabelToLabelhash(label: string): string | null {
|
export function encodedLabelToLabelhash(label: string): string | null {
|
||||||
if (label.length !== 66) return null;
|
if (label.length !== 66) return null;
|
||||||
if (label.indexOf('[') !== 0) return null;
|
if (label.indexOf('[') !== 0) return null;
|
||||||
if (label.indexOf(']') !== 65) return null;
|
if (label.indexOf(']') !== 65) return null;
|
||||||
const hash = `0x${label.slice(1, 65)}`;
|
const hash = `0x${label.slice(1, 65)}`;
|
||||||
if (!isHex(hash)) return null;
|
if (!isHex(hash)) return null;
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function labelhash(label: string) {
|
export function labelhash(label: string) {
|
||||||
if (!label) {
|
if (!label) {
|
||||||
return bytesToHex(new Uint8Array(32).fill(0));
|
return bytesToHex(new Uint8Array(32).fill(0));
|
||||||
}
|
}
|
||||||
return encodedLabelToLabelhash(label) || keccak256(new TextEncoder().encode(label));
|
return (
|
||||||
|
encodedLabelToLabelhash(label) ||
|
||||||
|
keccak256(new TextEncoder().encode(label))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeLabelNodeAndParent(name: string) {
|
export function makeLabelNodeAndParent(name: string) {
|
||||||
const labels = name.split('.');
|
const labels = name.split('.');
|
||||||
const label = labels.shift() as string;
|
const label = labels.shift() as string;
|
||||||
const parentNode = namehash(labels.join('.'));
|
const parentNode = namehash(labels.join('.'));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label,
|
label,
|
||||||
labelhash: labelhash(label),
|
labelhash: labelhash(label),
|
||||||
parentNode,
|
parentNode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/ensdomains/ensjs/blob/main/packages/ensjs/src/contracts/consts.ts
|
// https://github.com/ensdomains/ensjs/blob/main/packages/ensjs/src/contracts/consts.ts
|
||||||
export const EnsContracts: {
|
export const EnsContracts: {
|
||||||
[key: NetIdType]: {
|
[key: NetIdType]: {
|
||||||
ensRegistry: string;
|
ensRegistry: string;
|
||||||
ensPublicResolver: string;
|
ensPublicResolver: string;
|
||||||
ensNameWrapper: string;
|
ensNameWrapper: string;
|
||||||
};
|
};
|
||||||
} = {
|
} = {
|
||||||
[NetId.MAINNET]: {
|
[NetId.MAINNET]: {
|
||||||
ensRegistry: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
|
ensRegistry: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
|
||||||
ensPublicResolver: '0x231b0Ee14048e9dCcD1d247744d114a4EB5E8E63',
|
ensPublicResolver: '0x231b0Ee14048e9dCcD1d247744d114a4EB5E8E63',
|
||||||
ensNameWrapper: '0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401',
|
ensNameWrapper: '0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401',
|
||||||
},
|
},
|
||||||
[NetId.SEPOLIA]: {
|
[NetId.SEPOLIA]: {
|
||||||
ensRegistry: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
|
ensRegistry: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
|
||||||
ensPublicResolver: '0x8FADE66B79cC9f707aB26799354482EB93a5B7dD',
|
ensPublicResolver: '0x8FADE66B79cC9f707aB26799354482EB93a5B7dD',
|
||||||
ensNameWrapper: '0x0635513f179D50A207757E05759CbD106d7dFcE8',
|
ensNameWrapper: '0x0635513f179D50A207757E05759CbD106d7dFcE8',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ENSUtils to manage on-chain registered relayers
|
* ENSUtils to manage on-chain registered relayers
|
||||||
*/
|
*/
|
||||||
export class ENSUtils {
|
export class ENSUtils {
|
||||||
ENSRegistry?: ENSRegistry;
|
ENSRegistry?: ENSRegistry;
|
||||||
ENSResolver?: ENSResolver;
|
ENSResolver?: ENSResolver;
|
||||||
ENSNameWrapper?: ENSNameWrapper;
|
ENSNameWrapper?: ENSNameWrapper;
|
||||||
provider: AbstractProvider;
|
provider: AbstractProvider;
|
||||||
|
|
||||||
constructor(provider: AbstractProvider) {
|
constructor(provider: AbstractProvider) {
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
}
|
|
||||||
|
|
||||||
async getContracts() {
|
|
||||||
const { chainId } = await this.provider.getNetwork();
|
|
||||||
|
|
||||||
const { ensRegistry, ensPublicResolver, ensNameWrapper } = EnsContracts[Number(chainId)];
|
|
||||||
|
|
||||||
this.ENSRegistry = ENSRegistry__factory.connect(ensRegistry, this.provider);
|
|
||||||
this.ENSResolver = ENSResolver__factory.connect(ensPublicResolver, this.provider);
|
|
||||||
this.ENSNameWrapper = ENSNameWrapper__factory.connect(ensNameWrapper, this.provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOwner(name: string) {
|
|
||||||
if (!this.ENSRegistry) {
|
|
||||||
await this.getContracts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (this.ENSRegistry as ENSRegistry).owner(namehash(name));
|
async getContracts() {
|
||||||
}
|
const { chainId } = await this.provider.getNetwork();
|
||||||
|
|
||||||
// nameWrapper connected with wallet signer
|
const { ensRegistry, ensPublicResolver, ensNameWrapper } =
|
||||||
async unwrap(signer: Signer, name: string) {
|
EnsContracts[Number(chainId)];
|
||||||
if (!this.ENSNameWrapper) {
|
|
||||||
await this.getContracts();
|
this.ENSRegistry = ENSRegistry__factory.connect(
|
||||||
|
ensRegistry,
|
||||||
|
this.provider,
|
||||||
|
);
|
||||||
|
this.ENSResolver = ENSResolver__factory.connect(
|
||||||
|
ensPublicResolver,
|
||||||
|
this.provider,
|
||||||
|
);
|
||||||
|
this.ENSNameWrapper = ENSNameWrapper__factory.connect(
|
||||||
|
ensNameWrapper,
|
||||||
|
this.provider,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const owner = (signer as unknown as { address: string }).address;
|
async getOwner(name: string) {
|
||||||
|
if (!this.ENSRegistry) {
|
||||||
|
await this.getContracts();
|
||||||
|
}
|
||||||
|
|
||||||
const nameWrapper = (this.ENSNameWrapper as ENSNameWrapper).connect(signer);
|
return (this.ENSRegistry as ENSRegistry).owner(namehash(name));
|
||||||
|
|
||||||
const { labelhash } = makeLabelNodeAndParent(name);
|
|
||||||
|
|
||||||
return nameWrapper.unwrapETH2LD(labelhash, owner, owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/ensdomains/ensjs/blob/main/packages/ensjs/src/functions/wallet/createSubname.ts
|
|
||||||
async setSubnodeRecord(signer: Signer, name: string) {
|
|
||||||
if (!this.ENSResolver) {
|
|
||||||
await this.getContracts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolver = this.ENSResolver as ENSResolver;
|
// nameWrapper connected with wallet signer
|
||||||
const registry = (this.ENSRegistry as ENSRegistry).connect(signer);
|
async unwrap(signer: Signer, name: string) {
|
||||||
|
if (!this.ENSNameWrapper) {
|
||||||
|
await this.getContracts();
|
||||||
|
}
|
||||||
|
|
||||||
const owner = (signer as unknown as { address: string }).address;
|
const owner = (signer as unknown as { address: string }).address;
|
||||||
|
|
||||||
const { labelhash, parentNode } = makeLabelNodeAndParent(name);
|
const nameWrapper = (this.ENSNameWrapper as ENSNameWrapper).connect(
|
||||||
|
signer,
|
||||||
|
);
|
||||||
|
|
||||||
return registry.setSubnodeRecord(parentNode, labelhash, owner, resolver.target, BigInt(0));
|
const { labelhash } = makeLabelNodeAndParent(name);
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/ensdomains/ensjs/blob/main/packages/ensjs/src/functions/wallet/setTextRecord.ts
|
return nameWrapper.unwrapETH2LD(labelhash, owner, owner);
|
||||||
async setText(signer: Signer, name: string, key: string, value: string) {
|
|
||||||
const resolver = ENSResolver__factory.connect((await this.getResolver(name))?.address as string, signer);
|
|
||||||
|
|
||||||
return resolver.setText(namehash(name), key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
getResolver(name: string) {
|
|
||||||
return EnsResolver.fromName(this.provider, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getText(name: string, key: string) {
|
|
||||||
const resolver = await this.getResolver(name);
|
|
||||||
|
|
||||||
// Retuns null if the name doesn't exist
|
|
||||||
if (!resolver) {
|
|
||||||
return resolver;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await resolver.getText(key)) || '';
|
// https://github.com/ensdomains/ensjs/blob/main/packages/ensjs/src/functions/wallet/createSubname.ts
|
||||||
}
|
async setSubnodeRecord(signer: Signer, name: string) {
|
||||||
|
if (!this.ENSResolver) {
|
||||||
|
await this.getContracts();
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolver = this.ENSResolver as ENSResolver;
|
||||||
|
const registry = (this.ENSRegistry as ENSRegistry).connect(signer);
|
||||||
|
|
||||||
|
const owner = (signer as unknown as { address: string }).address;
|
||||||
|
|
||||||
|
const { labelhash, parentNode } = makeLabelNodeAndParent(name);
|
||||||
|
|
||||||
|
return registry.setSubnodeRecord(
|
||||||
|
parentNode,
|
||||||
|
labelhash,
|
||||||
|
owner,
|
||||||
|
resolver.target,
|
||||||
|
BigInt(0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/ensdomains/ensjs/blob/main/packages/ensjs/src/functions/wallet/setTextRecord.ts
|
||||||
|
async setText(signer: Signer, name: string, key: string, value: string) {
|
||||||
|
const resolver = ENSResolver__factory.connect(
|
||||||
|
(await this.getResolver(name))?.address as string,
|
||||||
|
signer,
|
||||||
|
);
|
||||||
|
|
||||||
|
return resolver.setText(namehash(name), key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
getResolver(name: string) {
|
||||||
|
return EnsResolver.fromName(this.provider, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getText(name: string, key: string) {
|
||||||
|
const resolver = await this.getResolver(name);
|
||||||
|
|
||||||
|
// Retuns null if the name doesn't exist
|
||||||
|
if (!resolver) {
|
||||||
|
return resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await resolver.getText(key)) || '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2333
src/events/base.ts
2333
src/events/base.ts
File diff suppressed because it is too large
Load Diff
574
src/events/db.ts
574
src/events/db.ts
@@ -2,365 +2,379 @@ import { downloadZip } from '../zip';
|
|||||||
import { IndexedDB } from '../idb';
|
import { IndexedDB } from '../idb';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BaseTornadoService,
|
BaseTornadoService,
|
||||||
BaseTornadoServiceConstructor,
|
BaseTornadoServiceConstructor,
|
||||||
BaseEchoService,
|
BaseEchoService,
|
||||||
BaseEchoServiceConstructor,
|
BaseEchoServiceConstructor,
|
||||||
BaseEncryptedNotesService,
|
BaseEncryptedNotesService,
|
||||||
BaseEncryptedNotesServiceConstructor,
|
BaseEncryptedNotesServiceConstructor,
|
||||||
BaseGovernanceService,
|
BaseGovernanceService,
|
||||||
BaseGovernanceServiceConstructor,
|
BaseGovernanceServiceConstructor,
|
||||||
BaseRegistryService,
|
BaseRegistryService,
|
||||||
BaseRegistryServiceConstructor,
|
BaseRegistryServiceConstructor,
|
||||||
} from './base';
|
} from './base';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BaseEvents,
|
BaseEvents,
|
||||||
MinimalEvents,
|
MinimalEvents,
|
||||||
DepositsEvents,
|
DepositsEvents,
|
||||||
WithdrawalsEvents,
|
WithdrawalsEvents,
|
||||||
CachedEvents,
|
CachedEvents,
|
||||||
EchoEvents,
|
EchoEvents,
|
||||||
EncryptedNotesEvents,
|
EncryptedNotesEvents,
|
||||||
AllGovernanceEvents,
|
AllGovernanceEvents,
|
||||||
RegistersEvents,
|
RegistersEvents,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export async function saveDBEvents<T extends MinimalEvents>({
|
export async function saveDBEvents<T extends MinimalEvents>({
|
||||||
idb,
|
idb,
|
||||||
instanceName,
|
instanceName,
|
||||||
events,
|
events,
|
||||||
lastBlock,
|
lastBlock,
|
||||||
}: {
|
}: {
|
||||||
idb: IndexedDB;
|
idb: IndexedDB;
|
||||||
instanceName: string;
|
instanceName: string;
|
||||||
events: T[];
|
events: T[];
|
||||||
lastBlock: number;
|
lastBlock: number;
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
const formattedEvents = events.map((e) => {
|
const formattedEvents = events.map((e) => {
|
||||||
return {
|
return {
|
||||||
eid: `${e.transactionHash}_${e.logIndex}`,
|
eid: `${e.transactionHash}_${e.logIndex}`,
|
||||||
...e,
|
...e,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await idb.createMultipleTransactions({
|
await idb.createMultipleTransactions({
|
||||||
data: formattedEvents,
|
data: formattedEvents,
|
||||||
storeName: instanceName,
|
storeName: instanceName,
|
||||||
});
|
});
|
||||||
|
|
||||||
await idb.putItem({
|
await idb.putItem({
|
||||||
data: {
|
data: {
|
||||||
blockNumber: lastBlock,
|
blockNumber: lastBlock,
|
||||||
name: instanceName,
|
name: instanceName,
|
||||||
},
|
},
|
||||||
storeName: 'lastEvents',
|
storeName: 'lastEvents',
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Method saveDBEvents has error');
|
console.log('Method saveDBEvents has error');
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadDBEvents<T extends MinimalEvents>({
|
export async function loadDBEvents<T extends MinimalEvents>({
|
||||||
idb,
|
idb,
|
||||||
instanceName,
|
instanceName,
|
||||||
}: {
|
}: {
|
||||||
idb: IndexedDB;
|
idb: IndexedDB;
|
||||||
instanceName: string;
|
instanceName: string;
|
||||||
}): Promise<BaseEvents<T>> {
|
}): Promise<BaseEvents<T>> {
|
||||||
try {
|
try {
|
||||||
const lastBlockStore = await idb.getItem<{ blockNumber: number; name: string }>({
|
const lastBlockStore = await idb.getItem<{
|
||||||
storeName: 'lastEvents',
|
blockNumber: number;
|
||||||
key: instanceName,
|
name: string;
|
||||||
});
|
}>({
|
||||||
|
storeName: 'lastEvents',
|
||||||
|
key: instanceName,
|
||||||
|
});
|
||||||
|
|
||||||
if (!lastBlockStore?.blockNumber) {
|
if (!lastBlockStore?.blockNumber) {
|
||||||
return {
|
return {
|
||||||
events: [],
|
events: [],
|
||||||
lastBlock: 0,
|
lastBlock: 0,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = (
|
||||||
|
await idb.getAll<(T & { eid?: string })[]>({
|
||||||
|
storeName: instanceName,
|
||||||
|
})
|
||||||
|
).map((e) => {
|
||||||
|
delete e.eid;
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
events,
|
||||||
|
lastBlock: lastBlockStore.blockNumber,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Method loadDBEvents has error');
|
||||||
|
console.log(err);
|
||||||
|
|
||||||
|
return {
|
||||||
|
events: [],
|
||||||
|
lastBlock: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = (await idb.getAll<(T & { eid?: string })[]>({ storeName: instanceName })).map((e) => {
|
|
||||||
delete e.eid;
|
|
||||||
return e;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
events,
|
|
||||||
lastBlock: lastBlockStore.blockNumber,
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
console.log('Method loadDBEvents has error');
|
|
||||||
console.log(err);
|
|
||||||
|
|
||||||
return {
|
|
||||||
events: [],
|
|
||||||
lastBlock: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadRemoteEvents<T extends MinimalEvents>({
|
export async function loadRemoteEvents<T extends MinimalEvents>({
|
||||||
staticUrl,
|
staticUrl,
|
||||||
instanceName,
|
instanceName,
|
||||||
deployedBlock,
|
deployedBlock,
|
||||||
zipDigest,
|
zipDigest,
|
||||||
}: {
|
}: {
|
||||||
staticUrl: string;
|
staticUrl: string;
|
||||||
instanceName: string;
|
instanceName: string;
|
||||||
deployedBlock: number;
|
deployedBlock: number;
|
||||||
zipDigest?: string;
|
zipDigest?: string;
|
||||||
}): Promise<CachedEvents<T>> {
|
}): Promise<CachedEvents<T>> {
|
||||||
try {
|
try {
|
||||||
const zipName = `${instanceName}.json`.toLowerCase();
|
const zipName = `${instanceName}.json`.toLowerCase();
|
||||||
|
|
||||||
const events = await downloadZip<T[]>({
|
const events = await downloadZip<T[]>({
|
||||||
staticUrl,
|
staticUrl,
|
||||||
zipName,
|
zipName,
|
||||||
zipDigest,
|
zipDigest,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!Array.isArray(events)) {
|
if (!Array.isArray(events)) {
|
||||||
const errStr = `Invalid events from ${staticUrl}/${zipName}`;
|
const errStr = `Invalid events from ${staticUrl}/${zipName}`;
|
||||||
throw new Error(errStr);
|
throw new Error(errStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
events,
|
||||||
|
lastBlock: events[events.length - 1]?.blockNumber || deployedBlock,
|
||||||
|
fromCache: true,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Method loadRemoteEvents has error');
|
||||||
|
console.log(err);
|
||||||
|
|
||||||
|
return {
|
||||||
|
events: [],
|
||||||
|
lastBlock: deployedBlock,
|
||||||
|
fromCache: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
events,
|
|
||||||
lastBlock: events[events.length - 1]?.blockNumber || deployedBlock,
|
|
||||||
fromCache: true,
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
console.log('Method loadRemoteEvents has error');
|
|
||||||
console.log(err);
|
|
||||||
|
|
||||||
return {
|
|
||||||
events: [],
|
|
||||||
lastBlock: deployedBlock,
|
|
||||||
fromCache: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DBTornadoServiceConstructor extends BaseTornadoServiceConstructor {
|
export interface DBTornadoServiceConstructor
|
||||||
staticUrl: string;
|
extends BaseTornadoServiceConstructor {
|
||||||
idb: IndexedDB;
|
staticUrl: string;
|
||||||
|
idb: IndexedDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DBTornadoService extends BaseTornadoService {
|
export class DBTornadoService extends BaseTornadoService {
|
||||||
staticUrl: string;
|
staticUrl: string;
|
||||||
idb: IndexedDB;
|
idb: IndexedDB;
|
||||||
|
|
||||||
zipDigest?: string;
|
zipDigest?: string;
|
||||||
|
|
||||||
constructor(params: DBTornadoServiceConstructor) {
|
constructor(params: DBTornadoServiceConstructor) {
|
||||||
super(params);
|
super(params);
|
||||||
|
|
||||||
this.staticUrl = params.staticUrl;
|
this.staticUrl = params.staticUrl;
|
||||||
this.idb = params.idb;
|
this.idb = params.idb;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsFromDB() {
|
async getEventsFromDB() {
|
||||||
return await loadDBEvents<DepositsEvents | WithdrawalsEvents>({
|
return await loadDBEvents<DepositsEvents | WithdrawalsEvents>({
|
||||||
idb: this.idb,
|
idb: this.idb,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsFromCache() {
|
async getEventsFromCache() {
|
||||||
return await loadRemoteEvents<DepositsEvents | WithdrawalsEvents>({
|
return await loadRemoteEvents<DepositsEvents | WithdrawalsEvents>({
|
||||||
staticUrl: this.staticUrl,
|
staticUrl: this.staticUrl,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
deployedBlock: this.deployedBlock,
|
deployedBlock: this.deployedBlock,
|
||||||
zipDigest: this.zipDigest,
|
zipDigest: this.zipDigest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveEvents({ events, lastBlock }: BaseEvents<DepositsEvents | WithdrawalsEvents>) {
|
async saveEvents({
|
||||||
await saveDBEvents<DepositsEvents | WithdrawalsEvents>({
|
events,
|
||||||
idb: this.idb,
|
lastBlock,
|
||||||
instanceName: this.getInstanceName(),
|
}: BaseEvents<DepositsEvents | WithdrawalsEvents>) {
|
||||||
events,
|
await saveDBEvents<DepositsEvents | WithdrawalsEvents>({
|
||||||
lastBlock,
|
idb: this.idb,
|
||||||
});
|
instanceName: this.getInstanceName(),
|
||||||
}
|
events,
|
||||||
|
lastBlock,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DBEchoServiceConstructor extends BaseEchoServiceConstructor {
|
export interface DBEchoServiceConstructor extends BaseEchoServiceConstructor {
|
||||||
staticUrl: string;
|
staticUrl: string;
|
||||||
idb: IndexedDB;
|
idb: IndexedDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DBEchoService extends BaseEchoService {
|
export class DBEchoService extends BaseEchoService {
|
||||||
staticUrl: string;
|
staticUrl: string;
|
||||||
idb: IndexedDB;
|
idb: IndexedDB;
|
||||||
|
|
||||||
zipDigest?: string;
|
zipDigest?: string;
|
||||||
|
|
||||||
constructor(params: DBEchoServiceConstructor) {
|
constructor(params: DBEchoServiceConstructor) {
|
||||||
super(params);
|
super(params);
|
||||||
|
|
||||||
this.staticUrl = params.staticUrl;
|
this.staticUrl = params.staticUrl;
|
||||||
this.idb = params.idb;
|
this.idb = params.idb;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsFromDB() {
|
async getEventsFromDB() {
|
||||||
return await loadDBEvents<EchoEvents>({
|
return await loadDBEvents<EchoEvents>({
|
||||||
idb: this.idb,
|
idb: this.idb,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsFromCache() {
|
async getEventsFromCache() {
|
||||||
return await loadRemoteEvents<EchoEvents>({
|
return await loadRemoteEvents<EchoEvents>({
|
||||||
staticUrl: this.staticUrl,
|
staticUrl: this.staticUrl,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
deployedBlock: this.deployedBlock,
|
deployedBlock: this.deployedBlock,
|
||||||
zipDigest: this.zipDigest,
|
zipDigest: this.zipDigest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveEvents({ events, lastBlock }: BaseEvents<EchoEvents>) {
|
async saveEvents({ events, lastBlock }: BaseEvents<EchoEvents>) {
|
||||||
await saveDBEvents<EchoEvents>({
|
await saveDBEvents<EchoEvents>({
|
||||||
idb: this.idb,
|
idb: this.idb,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
events,
|
events,
|
||||||
lastBlock,
|
lastBlock,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DBEncryptedNotesServiceConstructor extends BaseEncryptedNotesServiceConstructor {
|
export interface DBEncryptedNotesServiceConstructor
|
||||||
staticUrl: string;
|
extends BaseEncryptedNotesServiceConstructor {
|
||||||
idb: IndexedDB;
|
staticUrl: string;
|
||||||
|
idb: IndexedDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DBEncryptedNotesService extends BaseEncryptedNotesService {
|
export class DBEncryptedNotesService extends BaseEncryptedNotesService {
|
||||||
staticUrl: string;
|
staticUrl: string;
|
||||||
idb: IndexedDB;
|
idb: IndexedDB;
|
||||||
|
|
||||||
zipDigest?: string;
|
zipDigest?: string;
|
||||||
|
|
||||||
constructor(params: DBEncryptedNotesServiceConstructor) {
|
constructor(params: DBEncryptedNotesServiceConstructor) {
|
||||||
super(params);
|
super(params);
|
||||||
|
|
||||||
this.staticUrl = params.staticUrl;
|
this.staticUrl = params.staticUrl;
|
||||||
this.idb = params.idb;
|
this.idb = params.idb;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsFromDB() {
|
async getEventsFromDB() {
|
||||||
return await loadDBEvents<EncryptedNotesEvents>({
|
return await loadDBEvents<EncryptedNotesEvents>({
|
||||||
idb: this.idb,
|
idb: this.idb,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsFromCache() {
|
async getEventsFromCache() {
|
||||||
return await loadRemoteEvents<EncryptedNotesEvents>({
|
return await loadRemoteEvents<EncryptedNotesEvents>({
|
||||||
staticUrl: this.staticUrl,
|
staticUrl: this.staticUrl,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
deployedBlock: this.deployedBlock,
|
deployedBlock: this.deployedBlock,
|
||||||
zipDigest: this.zipDigest,
|
zipDigest: this.zipDigest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveEvents({ events, lastBlock }: BaseEvents<EncryptedNotesEvents>) {
|
async saveEvents({ events, lastBlock }: BaseEvents<EncryptedNotesEvents>) {
|
||||||
await saveDBEvents<EncryptedNotesEvents>({
|
await saveDBEvents<EncryptedNotesEvents>({
|
||||||
idb: this.idb,
|
idb: this.idb,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
events,
|
events,
|
||||||
lastBlock,
|
lastBlock,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DBGovernanceServiceConstructor extends BaseGovernanceServiceConstructor {
|
export interface DBGovernanceServiceConstructor
|
||||||
staticUrl: string;
|
extends BaseGovernanceServiceConstructor {
|
||||||
idb: IndexedDB;
|
staticUrl: string;
|
||||||
|
idb: IndexedDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DBGovernanceService extends BaseGovernanceService {
|
export class DBGovernanceService extends BaseGovernanceService {
|
||||||
staticUrl: string;
|
staticUrl: string;
|
||||||
idb: IndexedDB;
|
idb: IndexedDB;
|
||||||
|
|
||||||
zipDigest?: string;
|
zipDigest?: string;
|
||||||
|
|
||||||
constructor(params: DBGovernanceServiceConstructor) {
|
constructor(params: DBGovernanceServiceConstructor) {
|
||||||
super(params);
|
super(params);
|
||||||
|
|
||||||
this.staticUrl = params.staticUrl;
|
this.staticUrl = params.staticUrl;
|
||||||
this.idb = params.idb;
|
this.idb = params.idb;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsFromDB() {
|
async getEventsFromDB() {
|
||||||
return await loadDBEvents<AllGovernanceEvents>({
|
return await loadDBEvents<AllGovernanceEvents>({
|
||||||
idb: this.idb,
|
idb: this.idb,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsFromCache() {
|
async getEventsFromCache() {
|
||||||
return await loadRemoteEvents<AllGovernanceEvents>({
|
return await loadRemoteEvents<AllGovernanceEvents>({
|
||||||
staticUrl: this.staticUrl,
|
staticUrl: this.staticUrl,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
deployedBlock: this.deployedBlock,
|
deployedBlock: this.deployedBlock,
|
||||||
zipDigest: this.zipDigest,
|
zipDigest: this.zipDigest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveEvents({ events, lastBlock }: BaseEvents<AllGovernanceEvents>) {
|
async saveEvents({ events, lastBlock }: BaseEvents<AllGovernanceEvents>) {
|
||||||
await saveDBEvents<AllGovernanceEvents>({
|
await saveDBEvents<AllGovernanceEvents>({
|
||||||
idb: this.idb,
|
idb: this.idb,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
events,
|
events,
|
||||||
lastBlock,
|
lastBlock,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DBRegistryServiceConstructor extends BaseRegistryServiceConstructor {
|
export interface DBRegistryServiceConstructor
|
||||||
staticUrl: string;
|
extends BaseRegistryServiceConstructor {
|
||||||
idb: IndexedDB;
|
staticUrl: string;
|
||||||
|
idb: IndexedDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DBRegistryService extends BaseRegistryService {
|
export class DBRegistryService extends BaseRegistryService {
|
||||||
staticUrl: string;
|
staticUrl: string;
|
||||||
idb: IndexedDB;
|
idb: IndexedDB;
|
||||||
|
|
||||||
zipDigest?: string;
|
zipDigest?: string;
|
||||||
|
|
||||||
constructor(params: DBRegistryServiceConstructor) {
|
constructor(params: DBRegistryServiceConstructor) {
|
||||||
super(params);
|
super(params);
|
||||||
|
|
||||||
this.staticUrl = params.staticUrl;
|
this.staticUrl = params.staticUrl;
|
||||||
this.idb = params.idb;
|
this.idb = params.idb;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsFromDB() {
|
async getEventsFromDB() {
|
||||||
return await loadDBEvents<RegistersEvents>({
|
return await loadDBEvents<RegistersEvents>({
|
||||||
idb: this.idb,
|
idb: this.idb,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEventsFromCache() {
|
async getEventsFromCache() {
|
||||||
return await loadRemoteEvents<RegistersEvents>({
|
return await loadRemoteEvents<RegistersEvents>({
|
||||||
staticUrl: this.staticUrl,
|
staticUrl: this.staticUrl,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
deployedBlock: this.deployedBlock,
|
deployedBlock: this.deployedBlock,
|
||||||
zipDigest: this.zipDigest,
|
zipDigest: this.zipDigest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveEvents({ events, lastBlock }: BaseEvents<RegistersEvents>) {
|
async saveEvents({ events, lastBlock }: BaseEvents<RegistersEvents>) {
|
||||||
await saveDBEvents<RegistersEvents>({
|
await saveDBEvents<RegistersEvents>({
|
||||||
idb: this.idb,
|
idb: this.idb,
|
||||||
instanceName: this.getInstanceName(),
|
instanceName: this.getInstanceName(),
|
||||||
events,
|
events,
|
||||||
lastBlock,
|
lastBlock,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +1,84 @@
|
|||||||
import { RelayerParams } from '../relayerClient';
|
import { RelayerParams } from '../relayerClient';
|
||||||
|
|
||||||
export interface BaseEvents<T> {
|
export interface BaseEvents<T> {
|
||||||
events: T[];
|
events: T[];
|
||||||
lastBlock: number;
|
lastBlock: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CachedEvents<T> extends BaseEvents<T> {
|
export interface CachedEvents<T> extends BaseEvents<T> {
|
||||||
fromCache: boolean;
|
fromCache: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseGraphEvents<T> {
|
export interface BaseGraphEvents<T> {
|
||||||
events: T[];
|
events: T[];
|
||||||
lastSyncBlock: number;
|
lastSyncBlock: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MinimalEvents {
|
export interface MinimalEvents {
|
||||||
blockNumber: number;
|
blockNumber: number;
|
||||||
logIndex: number;
|
logIndex: number;
|
||||||
transactionHash: string;
|
transactionHash: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GovernanceEvents extends MinimalEvents {
|
export interface GovernanceEvents extends MinimalEvents {
|
||||||
event: string;
|
event: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GovernanceProposalCreatedEvents extends GovernanceEvents {
|
export interface GovernanceProposalCreatedEvents extends GovernanceEvents {
|
||||||
id: number;
|
id: number;
|
||||||
proposer: string;
|
proposer: string;
|
||||||
target: string;
|
target: string;
|
||||||
startTime: number;
|
startTime: number;
|
||||||
endTime: number;
|
endTime: number;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GovernanceVotedEvents extends GovernanceEvents {
|
export interface GovernanceVotedEvents extends GovernanceEvents {
|
||||||
proposalId: number;
|
proposalId: number;
|
||||||
voter: string;
|
voter: string;
|
||||||
support: boolean;
|
support: boolean;
|
||||||
votes: string;
|
votes: string;
|
||||||
from: string;
|
from: string;
|
||||||
input: string;
|
input: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GovernanceDelegatedEvents extends GovernanceEvents {
|
export interface GovernanceDelegatedEvents extends GovernanceEvents {
|
||||||
account: string;
|
account: string;
|
||||||
delegateTo: string;
|
delegateTo: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GovernanceUndelegatedEvents extends GovernanceEvents {
|
export interface GovernanceUndelegatedEvents extends GovernanceEvents {
|
||||||
account: string;
|
account: string;
|
||||||
delegateFrom: string;
|
delegateFrom: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AllGovernanceEvents =
|
export type AllGovernanceEvents =
|
||||||
| GovernanceProposalCreatedEvents
|
| GovernanceProposalCreatedEvents
|
||||||
| GovernanceVotedEvents
|
| GovernanceVotedEvents
|
||||||
| GovernanceDelegatedEvents
|
| GovernanceDelegatedEvents
|
||||||
| GovernanceUndelegatedEvents;
|
| GovernanceUndelegatedEvents;
|
||||||
|
|
||||||
export type RegistersEvents = MinimalEvents & RelayerParams;
|
export type RegistersEvents = MinimalEvents & RelayerParams;
|
||||||
|
|
||||||
export interface DepositsEvents extends MinimalEvents {
|
export interface DepositsEvents extends MinimalEvents {
|
||||||
commitment: string;
|
commitment: string;
|
||||||
leafIndex: number;
|
leafIndex: number;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
from: string;
|
from: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WithdrawalsEvents extends MinimalEvents {
|
export interface WithdrawalsEvents extends MinimalEvents {
|
||||||
nullifierHash: string;
|
nullifierHash: string;
|
||||||
to: string;
|
to: string;
|
||||||
fee: string;
|
fee: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EchoEvents extends MinimalEvents {
|
export interface EchoEvents extends MinimalEvents {
|
||||||
address: string;
|
address: string;
|
||||||
encryptedAccount: string;
|
encryptedAccount: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EncryptedNotesEvents extends MinimalEvents {
|
export interface EncryptedNotesEvents extends MinimalEvents {
|
||||||
encryptedNote: string;
|
encryptedNote: string;
|
||||||
}
|
}
|
||||||
|
|||||||
274
src/fees.ts
274
src/fees.ts
@@ -7,7 +7,7 @@ const DUMMY_ADDRESS = '0x1111111111111111111111111111111111111111';
|
|||||||
const DUMMY_NONCE = 1024;
|
const DUMMY_NONCE = 1024;
|
||||||
|
|
||||||
const DUMMY_WITHDRAW_DATA =
|
const DUMMY_WITHDRAW_DATA =
|
||||||
'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111';
|
'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example:
|
* Example:
|
||||||
@@ -15,141 +15,179 @@ const DUMMY_WITHDRAW_DATA =
|
|||||||
* amountInWei (0.1 ETH) * tokenDecimals (18) * tokenPriceInWei (0.0008) = 125 TOKEN
|
* amountInWei (0.1 ETH) * tokenDecimals (18) * tokenPriceInWei (0.0008) = 125 TOKEN
|
||||||
*/
|
*/
|
||||||
export function convertETHToTokenAmount(
|
export function convertETHToTokenAmount(
|
||||||
amountInWei: BigNumberish,
|
amountInWei: BigNumberish,
|
||||||
tokenPriceInWei: BigNumberish,
|
tokenPriceInWei: BigNumberish,
|
||||||
tokenDecimals: number = 18,
|
tokenDecimals: number = 18,
|
||||||
): bigint {
|
): bigint {
|
||||||
const tokenDecimalsMultiplier = BigInt(10 ** Number(tokenDecimals));
|
const tokenDecimalsMultiplier = BigInt(10 ** Number(tokenDecimals));
|
||||||
return (BigInt(amountInWei) * tokenDecimalsMultiplier) / BigInt(tokenPriceInWei);
|
return (
|
||||||
|
(BigInt(amountInWei) * tokenDecimalsMultiplier) /
|
||||||
|
BigInt(tokenPriceInWei)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RelayerFeeParams {
|
export interface RelayerFeeParams {
|
||||||
gasPrice: BigNumberish;
|
gasPrice: BigNumberish;
|
||||||
gasLimit?: BigNumberish;
|
gasLimit?: BigNumberish;
|
||||||
l1Fee?: BigNumberish;
|
l1Fee?: BigNumberish;
|
||||||
denomination: BigNumberish;
|
denomination: BigNumberish;
|
||||||
ethRefund: BigNumberish;
|
ethRefund: BigNumberish;
|
||||||
tokenPriceInWei: BigNumberish;
|
tokenPriceInWei: BigNumberish;
|
||||||
tokenDecimals: number;
|
tokenDecimals: number;
|
||||||
relayerFeePercent?: number;
|
relayerFeePercent?: number;
|
||||||
isEth?: boolean;
|
isEth?: boolean;
|
||||||
premiumPercent?: number;
|
premiumPercent?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TornadoFeeOracle {
|
export class TornadoFeeOracle {
|
||||||
provider: JsonRpcApiProvider;
|
provider: JsonRpcApiProvider;
|
||||||
ovmGasPriceOracle?: OvmGasPriceOracle;
|
ovmGasPriceOracle?: OvmGasPriceOracle;
|
||||||
|
|
||||||
constructor(provider: JsonRpcApiProvider, ovmGasPriceOracle?: OvmGasPriceOracle) {
|
constructor(
|
||||||
this.provider = provider;
|
provider: JsonRpcApiProvider,
|
||||||
|
ovmGasPriceOracle?: OvmGasPriceOracle,
|
||||||
|
) {
|
||||||
|
this.provider = provider;
|
||||||
|
|
||||||
if (ovmGasPriceOracle) {
|
if (ovmGasPriceOracle) {
|
||||||
this.ovmGasPriceOracle = ovmGasPriceOracle;
|
this.ovmGasPriceOracle = ovmGasPriceOracle;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates Gas Price
|
|
||||||
* We apply 50% premium of EIP-1559 network fees instead of 100% from ethers.js
|
|
||||||
* (This should cover up to 4 full blocks which is equivalent of minute)
|
|
||||||
* (A single block can bump 12.5% of fees, see the methodology https://hackmd.io/@tvanepps/1559-wallets)
|
|
||||||
* (Still it is recommended to use 100% premium for sending transactions to prevent stucking it)
|
|
||||||
*/
|
|
||||||
async gasPrice() {
|
|
||||||
const [block, getGasPrice, getPriorityFee] = await Promise.all([
|
|
||||||
this.provider.getBlock('latest'),
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
return BigInt(await this.provider.send('eth_gasPrice', []));
|
|
||||||
} catch {
|
|
||||||
return parseUnits('1', 'gwei');
|
|
||||||
}
|
}
|
||||||
})(),
|
}
|
||||||
(async () => {
|
|
||||||
try {
|
/**
|
||||||
return BigInt(await this.provider.send('eth_maxPriorityFeePerGas', []));
|
* Calculates Gas Price
|
||||||
} catch {
|
* We apply 50% premium of EIP-1559 network fees instead of 100% from ethers.js
|
||||||
return BigInt(0);
|
* (This should cover up to 4 full blocks which is equivalent of minute)
|
||||||
|
* (A single block can bump 12.5% of fees, see the methodology https://hackmd.io/@tvanepps/1559-wallets)
|
||||||
|
* (Still it is recommended to use 100% premium for sending transactions to prevent stucking it)
|
||||||
|
*/
|
||||||
|
async gasPrice() {
|
||||||
|
const [block, getGasPrice, getPriorityFee] = await Promise.all([
|
||||||
|
this.provider.getBlock('latest'),
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
return BigInt(await this.provider.send('eth_gasPrice', []));
|
||||||
|
} catch {
|
||||||
|
return parseUnits('1', 'gwei');
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
return BigInt(
|
||||||
|
await this.provider.send(
|
||||||
|
'eth_maxPriorityFeePerGas',
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return BigInt(0);
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return block?.baseFeePerGas
|
||||||
|
? (block.baseFeePerGas * BigInt(15)) / BigInt(10) + getPriorityFee
|
||||||
|
: getGasPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate L1 fee for op-stack chains
|
||||||
|
*
|
||||||
|
* This is required since relayers would pay the full transaction fees for users
|
||||||
|
*/
|
||||||
|
fetchL1OptimismFee(tx?: TransactionLike): Promise<bigint> {
|
||||||
|
if (!this.ovmGasPriceOracle) {
|
||||||
|
return new Promise((resolve) => resolve(BigInt(0)));
|
||||||
}
|
}
|
||||||
})(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return block?.baseFeePerGas ? (block.baseFeePerGas * BigInt(15)) / BigInt(10) + getPriorityFee : getGasPrice;
|
if (!tx) {
|
||||||
}
|
// this tx is only used to simulate bytes size of the encoded tx so has nothing to with the accuracy
|
||||||
|
// inspired by the old style classic-ui calculation
|
||||||
|
tx = {
|
||||||
|
type: 0,
|
||||||
|
gasLimit: 1_000_000,
|
||||||
|
nonce: DUMMY_NONCE,
|
||||||
|
data: DUMMY_WITHDRAW_DATA,
|
||||||
|
gasPrice: parseUnits('1', 'gwei'),
|
||||||
|
to: DUMMY_ADDRESS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return this.ovmGasPriceOracle.getL1Fee.staticCall(
|
||||||
* Calculate L1 fee for op-stack chains
|
Transaction.from(tx).unsignedSerialized,
|
||||||
*
|
);
|
||||||
* This is required since relayers would pay the full transaction fees for users
|
|
||||||
*/
|
|
||||||
fetchL1OptimismFee(tx?: TransactionLike): Promise<bigint> {
|
|
||||||
if (!this.ovmGasPriceOracle) {
|
|
||||||
return new Promise((resolve) => resolve(BigInt(0)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tx) {
|
/**
|
||||||
// this tx is only used to simulate bytes size of the encoded tx so has nothing to with the accuracy
|
* We don't need to distinguish default refunds by tokens since most users interact with other defi protocols after withdrawal
|
||||||
// inspired by the old style classic-ui calculation
|
* So we default with 1M gas which is enough for two or three swaps
|
||||||
tx = {
|
* Using 30 gwei for default but it is recommended to supply cached gasPrice value from the UI
|
||||||
type: 0,
|
*/
|
||||||
gasLimit: 1_000_000,
|
defaultEthRefund(gasPrice?: BigNumberish, gasLimit?: BigNumberish): bigint {
|
||||||
nonce: DUMMY_NONCE,
|
return (
|
||||||
data: DUMMY_WITHDRAW_DATA,
|
(gasPrice ? BigInt(gasPrice) : parseUnits('30', 'gwei')) *
|
||||||
gasPrice: parseUnits('1', 'gwei'),
|
BigInt(gasLimit || 1_000_000)
|
||||||
to: DUMMY_ADDRESS,
|
);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.ovmGasPriceOracle.getL1Fee.staticCall(Transaction.from(tx).unsignedSerialized);
|
/**
|
||||||
}
|
* Calculates token amount for required ethRefund purchases required to calculate fees
|
||||||
|
*/
|
||||||
/**
|
calculateTokenAmount(
|
||||||
* We don't need to distinguish default refunds by tokens since most users interact with other defi protocols after withdrawal
|
ethRefund: BigNumberish,
|
||||||
* So we default with 1M gas which is enough for two or three swaps
|
tokenPriceInEth: BigNumberish,
|
||||||
* Using 30 gwei for default but it is recommended to supply cached gasPrice value from the UI
|
tokenDecimals?: number,
|
||||||
*/
|
): bigint {
|
||||||
defaultEthRefund(gasPrice?: BigNumberish, gasLimit?: BigNumberish): bigint {
|
return convertETHToTokenAmount(
|
||||||
return (gasPrice ? BigInt(gasPrice) : parseUnits('30', 'gwei')) * BigInt(gasLimit || 1_000_000);
|
ethRefund,
|
||||||
}
|
tokenPriceInEth,
|
||||||
|
tokenDecimals,
|
||||||
/**
|
);
|
||||||
* Calculates token amount for required ethRefund purchases required to calculate fees
|
|
||||||
*/
|
|
||||||
calculateTokenAmount(ethRefund: BigNumberish, tokenPriceInEth: BigNumberish, tokenDecimals?: number): bigint {
|
|
||||||
return convertETHToTokenAmount(ethRefund, tokenPriceInEth, tokenDecimals);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Warning: For tokens you need to check if the fees are above denomination
|
|
||||||
* (Usually happens for small denomination pool or if the gas price is high)
|
|
||||||
*/
|
|
||||||
calculateRelayerFee({
|
|
||||||
gasPrice,
|
|
||||||
gasLimit = 600_000,
|
|
||||||
l1Fee = 0,
|
|
||||||
denomination,
|
|
||||||
ethRefund = BigInt(0),
|
|
||||||
tokenPriceInWei,
|
|
||||||
tokenDecimals = 18,
|
|
||||||
relayerFeePercent = 0.33,
|
|
||||||
isEth = true,
|
|
||||||
premiumPercent = 20,
|
|
||||||
}: RelayerFeeParams): bigint {
|
|
||||||
const gasCosts = BigInt(gasPrice) * BigInt(gasLimit) + BigInt(l1Fee);
|
|
||||||
|
|
||||||
const relayerFee = (BigInt(denomination) * BigInt(Math.floor(10000 * relayerFeePercent))) / BigInt(10000 * 100);
|
|
||||||
|
|
||||||
if (isEth) {
|
|
||||||
// Add 20% premium
|
|
||||||
return ((gasCosts + relayerFee) * BigInt(premiumPercent ? 100 + premiumPercent : 100)) / BigInt(100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const feeInEth = gasCosts + BigInt(ethRefund);
|
/**
|
||||||
|
* Warning: For tokens you need to check if the fees are above denomination
|
||||||
|
* (Usually happens for small denomination pool or if the gas price is high)
|
||||||
|
*/
|
||||||
|
calculateRelayerFee({
|
||||||
|
gasPrice,
|
||||||
|
gasLimit = 600_000,
|
||||||
|
l1Fee = 0,
|
||||||
|
denomination,
|
||||||
|
ethRefund = BigInt(0),
|
||||||
|
tokenPriceInWei,
|
||||||
|
tokenDecimals = 18,
|
||||||
|
relayerFeePercent = 0.33,
|
||||||
|
isEth = true,
|
||||||
|
premiumPercent = 20,
|
||||||
|
}: RelayerFeeParams): bigint {
|
||||||
|
const gasCosts = BigInt(gasPrice) * BigInt(gasLimit) + BigInt(l1Fee);
|
||||||
|
|
||||||
return (
|
const relayerFee =
|
||||||
((convertETHToTokenAmount(feeInEth, tokenPriceInWei, tokenDecimals) + relayerFee) *
|
(BigInt(denomination) *
|
||||||
BigInt(premiumPercent ? 100 + premiumPercent : 100)) /
|
BigInt(Math.floor(10000 * relayerFeePercent))) /
|
||||||
BigInt(100)
|
BigInt(10000 * 100);
|
||||||
);
|
|
||||||
}
|
if (isEth) {
|
||||||
|
// Add 20% premium
|
||||||
|
return (
|
||||||
|
((gasCosts + relayerFee) *
|
||||||
|
BigInt(premiumPercent ? 100 + premiumPercent : 100)) /
|
||||||
|
BigInt(100)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const feeInEth = gasCosts + BigInt(ethRefund);
|
||||||
|
|
||||||
|
return (
|
||||||
|
((convertETHToTokenAmount(
|
||||||
|
feeInEth,
|
||||||
|
tokenPriceInWei,
|
||||||
|
tokenDecimals,
|
||||||
|
) +
|
||||||
|
relayerFee) *
|
||||||
|
BigInt(premiumPercent ? 100 + premiumPercent : 100)) /
|
||||||
|
BigInt(100)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,52 +3,52 @@ import { NetId, NetIdType } from './networkConfig';
|
|||||||
|
|
||||||
// https://dev.gas.zip/gas/chain-support/inbound
|
// https://dev.gas.zip/gas/chain-support/inbound
|
||||||
export const gasZipInbounds: { [key in NetIdType]: string } = {
|
export const gasZipInbounds: { [key in NetIdType]: string } = {
|
||||||
[NetId.MAINNET]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
[NetId.MAINNET]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
||||||
[NetId.BSC]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
[NetId.BSC]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
||||||
[NetId.POLYGON]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
[NetId.POLYGON]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
||||||
[NetId.OPTIMISM]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
[NetId.OPTIMISM]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
||||||
[NetId.ARBITRUM]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
[NetId.ARBITRUM]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
||||||
[NetId.GNOSIS]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
[NetId.GNOSIS]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
||||||
[NetId.AVALANCHE]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
[NetId.AVALANCHE]: '0x391E7C679d29bD940d63be94AD22A25d25b5A604',
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://dev.gas.zip/gas/chain-support/outbound
|
// https://dev.gas.zip/gas/chain-support/outbound
|
||||||
export const gasZipID: { [key in NetIdType]: number } = {
|
export const gasZipID: { [key in NetIdType]: number } = {
|
||||||
[NetId.MAINNET]: 255,
|
[NetId.MAINNET]: 255,
|
||||||
[NetId.BSC]: 14,
|
[NetId.BSC]: 14,
|
||||||
[NetId.POLYGON]: 17,
|
[NetId.POLYGON]: 17,
|
||||||
[NetId.OPTIMISM]: 55,
|
[NetId.OPTIMISM]: 55,
|
||||||
[NetId.ARBITRUM]: 57,
|
[NetId.ARBITRUM]: 57,
|
||||||
[NetId.GNOSIS]: 16,
|
[NetId.GNOSIS]: 16,
|
||||||
[NetId.AVALANCHE]: 15,
|
[NetId.AVALANCHE]: 15,
|
||||||
[NetId.SEPOLIA]: 102,
|
[NetId.SEPOLIA]: 102,
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://dev.gas.zip/gas/code-examples/eoaDeposit
|
// https://dev.gas.zip/gas/code-examples/eoaDeposit
|
||||||
export function gasZipInput(to: string, shorts: number[]): string | null {
|
export function gasZipInput(to: string, shorts: number[]): string | null {
|
||||||
let data = '0x';
|
let data = '0x';
|
||||||
if (isAddress(to)) {
|
if (isAddress(to)) {
|
||||||
if (to.length === 42) {
|
if (to.length === 42) {
|
||||||
data += '02';
|
data += '02';
|
||||||
data += to.slice(2);
|
data += to.slice(2);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return null;
|
data += '01'; // to == sender
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
data += '01'; // to == sender
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const i in shorts) {
|
for (const i in shorts) {
|
||||||
data += Number(shorts[i]).toString(16).padStart(4, '0');
|
data += Number(shorts[i]).toString(16).padStart(4, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function gasZipMinMax(ethUsd: number) {
|
export function gasZipMinMax(ethUsd: number) {
|
||||||
return {
|
return {
|
||||||
min: 1 / ethUsd,
|
min: 1 / ethUsd,
|
||||||
max: 50 / ethUsd,
|
max: 50 / ethUsd,
|
||||||
ethUsd,
|
ethUsd,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
1928
src/graphql/index.ts
1928
src/graphql/index.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
882
src/idb.ts
882
src/idb.ts
@@ -2,450 +2,498 @@
|
|||||||
import { openDB, deleteDB, OpenDBCallbacks, IDBPDatabase } from 'idb';
|
import { openDB, deleteDB, OpenDBCallbacks, IDBPDatabase } from 'idb';
|
||||||
import { getConfig, NetIdType } from './networkConfig';
|
import { getConfig, NetIdType } from './networkConfig';
|
||||||
|
|
||||||
export const INDEX_DB_ERROR = 'A mutation operation was attempted on a database that did not allow mutations.';
|
export const INDEX_DB_ERROR =
|
||||||
|
'A mutation operation was attempted on a database that did not allow mutations.';
|
||||||
|
|
||||||
export interface IDBIndex {
|
export interface IDBIndex {
|
||||||
name: string;
|
name: string;
|
||||||
unique?: boolean;
|
unique?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDBStores {
|
export interface IDBStores {
|
||||||
name: string;
|
name: string;
|
||||||
keyPath?: string;
|
keyPath?: string;
|
||||||
indexes?: IDBIndex[];
|
indexes?: IDBIndex[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDBConstructor {
|
export interface IDBConstructor {
|
||||||
dbName: string;
|
dbName: string;
|
||||||
stores?: IDBStores[];
|
stores?: IDBStores[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IndexedDB {
|
export class IndexedDB {
|
||||||
dbExists: boolean;
|
dbExists: boolean;
|
||||||
isBlocked: boolean;
|
isBlocked: boolean;
|
||||||
// todo: TestDBSchema on any
|
// todo: TestDBSchema on any
|
||||||
options: OpenDBCallbacks<any>;
|
options: OpenDBCallbacks<any>;
|
||||||
dbName: string;
|
dbName: string;
|
||||||
dbVersion: number;
|
dbVersion: number;
|
||||||
db?: IDBPDatabase<any>;
|
db?: IDBPDatabase<any>;
|
||||||
|
|
||||||
constructor({ dbName, stores }: IDBConstructor) {
|
constructor({ dbName, stores }: IDBConstructor) {
|
||||||
this.dbExists = false;
|
this.dbExists = false;
|
||||||
this.isBlocked = false;
|
this.isBlocked = false;
|
||||||
|
|
||||||
this.options = {
|
this.options = {
|
||||||
upgrade(db) {
|
upgrade(db) {
|
||||||
Object.values(db.objectStoreNames).forEach((value) => {
|
Object.values(db.objectStoreNames).forEach((value) => {
|
||||||
db.deleteObjectStore(value);
|
db.deleteObjectStore(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
[{ name: 'keyval' }, ...(stores || [])].forEach(({ name, keyPath, indexes }) => {
|
[{ name: 'keyval' }, ...(stores || [])].forEach(
|
||||||
const store = db.createObjectStore(name, {
|
({ name, keyPath, indexes }) => {
|
||||||
keyPath,
|
const store = db.createObjectStore(name, {
|
||||||
autoIncrement: true,
|
keyPath,
|
||||||
});
|
autoIncrement: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (Array.isArray(indexes)) {
|
if (Array.isArray(indexes)) {
|
||||||
indexes.forEach(({ name, unique = false }) => {
|
indexes.forEach(({ name, unique = false }) => {
|
||||||
store.createIndex(name, name, { unique });
|
store.createIndex(name, name, { unique });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.dbName = dbName;
|
||||||
|
this.dbVersion = 35;
|
||||||
|
}
|
||||||
|
|
||||||
|
async initDB() {
|
||||||
|
try {
|
||||||
|
if (this.dbExists || this.isBlocked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.db = await openDB(this.dbName, this.dbVersion, this.options);
|
||||||
|
this.db.addEventListener('onupgradeneeded', async () => {
|
||||||
|
await this._removeExist();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.dbName = dbName;
|
this.dbExists = true;
|
||||||
this.dbVersion = 35;
|
} catch (err: any) {
|
||||||
}
|
// needed for private mode firefox browser
|
||||||
|
if (err.message.includes(INDEX_DB_ERROR)) {
|
||||||
|
console.log('This browser does not support IndexedDB!');
|
||||||
|
this.isBlocked = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
async initDB() {
|
if (err.message.includes('less than the existing version')) {
|
||||||
try {
|
console.log(`Upgrading DB ${this.dbName} to ${this.dbVersion}`);
|
||||||
if (this.dbExists || this.isBlocked) {
|
await this._removeExist();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.db = await openDB(this.dbName, this.dbVersion, this.options);
|
console.error(`Method initDB has error: ${err.message}`);
|
||||||
this.db.addEventListener('onupgradeneeded', async () => {
|
}
|
||||||
await this._removeExist();
|
}
|
||||||
});
|
|
||||||
|
async _removeExist() {
|
||||||
this.dbExists = true;
|
await deleteDB(this.dbName);
|
||||||
} catch (err: any) {
|
this.dbExists = false;
|
||||||
// needed for private mode firefox browser
|
|
||||||
if (err.message.includes(INDEX_DB_ERROR)) {
|
await this.initDB();
|
||||||
console.log('This browser does not support IndexedDB!');
|
}
|
||||||
this.isBlocked = true;
|
|
||||||
return;
|
async getFromIndex<T>({
|
||||||
}
|
storeName,
|
||||||
|
indexName,
|
||||||
if (err.message.includes('less than the existing version')) {
|
key,
|
||||||
console.log(`Upgrading DB ${this.dbName} to ${this.dbVersion}`);
|
}: {
|
||||||
await this._removeExist();
|
storeName: string;
|
||||||
return;
|
indexName: string;
|
||||||
}
|
key?: string;
|
||||||
|
}): Promise<T | undefined> {
|
||||||
console.error(`Method initDB has error: ${err.message}`);
|
await this.initDB();
|
||||||
}
|
|
||||||
}
|
if (!this.db) {
|
||||||
|
return;
|
||||||
async _removeExist() {
|
}
|
||||||
await deleteDB(this.dbName);
|
|
||||||
this.dbExists = false;
|
try {
|
||||||
|
return (await this.db.getFromIndex(storeName, indexName, key)) as T;
|
||||||
await this.initDB();
|
} catch (err: any) {
|
||||||
}
|
throw new Error(`Method getFromIndex has error: ${err.message}`);
|
||||||
|
}
|
||||||
async getFromIndex<T>({
|
}
|
||||||
storeName,
|
|
||||||
indexName,
|
async getAllFromIndex<T>({
|
||||||
key,
|
storeName,
|
||||||
}: {
|
indexName,
|
||||||
storeName: string;
|
key,
|
||||||
indexName: string;
|
count,
|
||||||
key?: string;
|
}: {
|
||||||
}): Promise<T | undefined> {
|
storeName: string;
|
||||||
await this.initDB();
|
indexName: string;
|
||||||
|
key?: string;
|
||||||
if (!this.db) {
|
count?: number;
|
||||||
return;
|
}): Promise<T> {
|
||||||
}
|
await this.initDB();
|
||||||
|
|
||||||
try {
|
if (!this.db) {
|
||||||
return (await this.db.getFromIndex(storeName, indexName, key)) as T;
|
return [] as T;
|
||||||
} catch (err: any) {
|
}
|
||||||
throw new Error(`Method getFromIndex has error: ${err.message}`);
|
|
||||||
}
|
try {
|
||||||
}
|
return (await this.db.getAllFromIndex(
|
||||||
|
storeName,
|
||||||
async getAllFromIndex<T>({
|
indexName,
|
||||||
storeName,
|
key,
|
||||||
indexName,
|
count,
|
||||||
key,
|
)) as T;
|
||||||
count,
|
} catch (err: any) {
|
||||||
}: {
|
throw new Error(`Method getAllFromIndex has error: ${err.message}`);
|
||||||
storeName: string;
|
}
|
||||||
indexName: string;
|
}
|
||||||
key?: string;
|
|
||||||
count?: number;
|
async getItem<T>({
|
||||||
}): Promise<T> {
|
storeName,
|
||||||
await this.initDB();
|
key,
|
||||||
|
}: {
|
||||||
if (!this.db) {
|
storeName: string;
|
||||||
return [] as T;
|
key: string;
|
||||||
}
|
}): Promise<T | undefined> {
|
||||||
|
await this.initDB();
|
||||||
try {
|
|
||||||
return (await this.db.getAllFromIndex(storeName, indexName, key, count)) as T;
|
if (!this.db) {
|
||||||
} catch (err: any) {
|
return;
|
||||||
throw new Error(`Method getAllFromIndex has error: ${err.message}`);
|
}
|
||||||
}
|
|
||||||
}
|
try {
|
||||||
|
const store = this.db.transaction(storeName).objectStore(storeName);
|
||||||
async getItem<T>({ storeName, key }: { storeName: string; key: string }): Promise<T | undefined> {
|
|
||||||
await this.initDB();
|
return (await store.get(key)) as T;
|
||||||
|
} catch (err: any) {
|
||||||
if (!this.db) {
|
throw new Error(`Method getItem has error: ${err.message}`);
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
async addItem({
|
||||||
const store = this.db.transaction(storeName).objectStore(storeName);
|
storeName,
|
||||||
|
data,
|
||||||
return (await store.get(key)) as T;
|
key = '',
|
||||||
} catch (err: any) {
|
}: {
|
||||||
throw new Error(`Method getItem has error: ${err.message}`);
|
storeName: string;
|
||||||
}
|
data: any;
|
||||||
}
|
key: string;
|
||||||
|
}) {
|
||||||
async addItem({ storeName, data, key = '' }: { storeName: string; data: any; key: string }) {
|
await this.initDB();
|
||||||
await this.initDB();
|
|
||||||
|
if (!this.db) {
|
||||||
if (!this.db) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
try {
|
||||||
try {
|
const tx = this.db.transaction(storeName, 'readwrite');
|
||||||
const tx = this.db.transaction(storeName, 'readwrite');
|
const isExist = await tx.objectStore(storeName).get(key);
|
||||||
const isExist = await tx.objectStore(storeName).get(key);
|
|
||||||
|
if (!isExist) {
|
||||||
if (!isExist) {
|
await tx.objectStore(storeName).add(data);
|
||||||
await tx.objectStore(storeName).add(data);
|
}
|
||||||
}
|
} catch (err: any) {
|
||||||
} catch (err: any) {
|
throw new Error(`Method addItem has error: ${err.message}`);
|
||||||
throw new Error(`Method addItem has error: ${err.message}`);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
async putItem({
|
||||||
async putItem({ storeName, data, key }: { storeName: string; data: any; key?: string }) {
|
storeName,
|
||||||
await this.initDB();
|
data,
|
||||||
|
key,
|
||||||
if (!this.db) {
|
}: {
|
||||||
return;
|
storeName: string;
|
||||||
}
|
data: any;
|
||||||
|
key?: string;
|
||||||
try {
|
}) {
|
||||||
const tx = this.db.transaction(storeName, 'readwrite');
|
await this.initDB();
|
||||||
|
|
||||||
await tx.objectStore(storeName).put(data, key);
|
if (!this.db) {
|
||||||
} catch (err: any) {
|
return;
|
||||||
throw new Error(`Method putItem has error: ${err.message}`);
|
}
|
||||||
}
|
|
||||||
}
|
try {
|
||||||
|
const tx = this.db.transaction(storeName, 'readwrite');
|
||||||
async deleteItem({ storeName, key }: { storeName: string; key: string }) {
|
|
||||||
await this.initDB();
|
await tx.objectStore(storeName).put(data, key);
|
||||||
|
} catch (err: any) {
|
||||||
if (!this.db) {
|
throw new Error(`Method putItem has error: ${err.message}`);
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
async deleteItem({ storeName, key }: { storeName: string; key: string }) {
|
||||||
const tx = this.db.transaction(storeName, 'readwrite');
|
await this.initDB();
|
||||||
|
|
||||||
await tx.objectStore(storeName).delete(key);
|
if (!this.db) {
|
||||||
} catch (err: any) {
|
return;
|
||||||
throw new Error(`Method deleteItem has error: ${err.message}`);
|
}
|
||||||
}
|
|
||||||
}
|
try {
|
||||||
|
const tx = this.db.transaction(storeName, 'readwrite');
|
||||||
async getAll<T>({ storeName }: { storeName: string }): Promise<T> {
|
|
||||||
await this.initDB();
|
await tx.objectStore(storeName).delete(key);
|
||||||
|
} catch (err: any) {
|
||||||
if (!this.db) {
|
throw new Error(`Method deleteItem has error: ${err.message}`);
|
||||||
return [] as T;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
async getAll<T>({ storeName }: { storeName: string }): Promise<T> {
|
||||||
const tx = this.db.transaction(storeName, 'readonly');
|
await this.initDB();
|
||||||
|
|
||||||
return (await tx.objectStore(storeName).getAll()) as T;
|
if (!this.db) {
|
||||||
} catch (err: any) {
|
return [] as T;
|
||||||
throw new Error(`Method getAll has error: ${err.message}`);
|
}
|
||||||
}
|
|
||||||
}
|
try {
|
||||||
|
const tx = this.db.transaction(storeName, 'readonly');
|
||||||
/**
|
|
||||||
* Simple key-value store inspired by idb-keyval package
|
return (await tx.objectStore(storeName).getAll()) as T;
|
||||||
*/
|
} catch (err: any) {
|
||||||
getValue<T>(key: string) {
|
throw new Error(`Method getAll has error: ${err.message}`);
|
||||||
return this.getItem<T>({ storeName: 'keyval', key });
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue(key: string, data: any) {
|
/**
|
||||||
return this.putItem({ storeName: 'keyval', key, data });
|
* Simple key-value store inspired by idb-keyval package
|
||||||
}
|
*/
|
||||||
|
getValue<T>(key: string) {
|
||||||
delValue(key: string) {
|
return this.getItem<T>({ storeName: 'keyval', key });
|
||||||
return this.deleteItem({ storeName: 'keyval', key });
|
}
|
||||||
}
|
|
||||||
|
setValue(key: string, data: any) {
|
||||||
async clearStore({ storeName, mode = 'readwrite' }: { storeName: string; mode: IDBTransactionMode }) {
|
return this.putItem({ storeName: 'keyval', key, data });
|
||||||
await this.initDB();
|
}
|
||||||
|
|
||||||
if (!this.db) {
|
delValue(key: string) {
|
||||||
return;
|
return this.deleteItem({ storeName: 'keyval', key });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
async clearStore({
|
||||||
const tx = this.db.transaction(storeName, mode);
|
storeName,
|
||||||
|
mode = 'readwrite',
|
||||||
await (tx.objectStore(storeName).clear as () => Promise<void>)();
|
}: {
|
||||||
} catch (err: any) {
|
storeName: string;
|
||||||
throw new Error(`Method clearStore has error: ${err.message}`);
|
mode: IDBTransactionMode;
|
||||||
}
|
}) {
|
||||||
}
|
await this.initDB();
|
||||||
|
|
||||||
async createTransactions({
|
if (!this.db) {
|
||||||
storeName,
|
return;
|
||||||
data,
|
}
|
||||||
mode = 'readwrite',
|
|
||||||
}: {
|
try {
|
||||||
storeName: string;
|
const tx = this.db.transaction(storeName, mode);
|
||||||
data: any;
|
|
||||||
mode: IDBTransactionMode;
|
await (tx.objectStore(storeName).clear as () => Promise<void>)();
|
||||||
}) {
|
} catch (err: any) {
|
||||||
await this.initDB();
|
throw new Error(`Method clearStore has error: ${err.message}`);
|
||||||
|
}
|
||||||
if (!this.db) {
|
}
|
||||||
return;
|
|
||||||
}
|
async createTransactions({
|
||||||
|
storeName,
|
||||||
try {
|
data,
|
||||||
const tx = this.db.transaction(storeName, mode);
|
mode = 'readwrite',
|
||||||
|
}: {
|
||||||
await (tx.objectStore(storeName).add as (value: any, key?: any) => Promise<any>)(data);
|
storeName: string;
|
||||||
await tx.done;
|
data: any;
|
||||||
} catch (err: any) {
|
mode: IDBTransactionMode;
|
||||||
throw new Error(`Method createTransactions has error: ${err.message}`);
|
}) {
|
||||||
}
|
await this.initDB();
|
||||||
}
|
|
||||||
|
if (!this.db) {
|
||||||
async createMultipleTransactions({
|
return;
|
||||||
storeName,
|
}
|
||||||
data,
|
|
||||||
index,
|
try {
|
||||||
mode = 'readwrite',
|
const tx = this.db.transaction(storeName, mode);
|
||||||
}: {
|
|
||||||
storeName: string;
|
await (
|
||||||
data: any[];
|
tx.objectStore(storeName).add as (
|
||||||
index?: any;
|
value: any,
|
||||||
mode?: IDBTransactionMode;
|
key?: any,
|
||||||
}) {
|
) => Promise<any>
|
||||||
await this.initDB();
|
)(data);
|
||||||
|
await tx.done;
|
||||||
if (!this.db) {
|
} catch (err: any) {
|
||||||
return;
|
throw new Error(
|
||||||
}
|
`Method createTransactions has error: ${err.message}`,
|
||||||
|
);
|
||||||
try {
|
}
|
||||||
const tx = this.db.transaction(storeName, mode);
|
}
|
||||||
|
|
||||||
for (const item of data) {
|
async createMultipleTransactions({
|
||||||
if (item) {
|
storeName,
|
||||||
await (tx.store.put as (value: any, key?: any) => Promise<any>)({ ...item, ...index });
|
data,
|
||||||
|
index,
|
||||||
|
mode = 'readwrite',
|
||||||
|
}: {
|
||||||
|
storeName: string;
|
||||||
|
data: any[];
|
||||||
|
index?: any;
|
||||||
|
mode?: IDBTransactionMode;
|
||||||
|
}) {
|
||||||
|
await this.initDB();
|
||||||
|
|
||||||
|
if (!this.db) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tx = this.db.transaction(storeName, mode);
|
||||||
|
|
||||||
|
for (const item of data) {
|
||||||
|
if (item) {
|
||||||
|
await (
|
||||||
|
tx.store.put as (value: any, key?: any) => Promise<any>
|
||||||
|
)({ ...item, ...index });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new Error(
|
||||||
|
`Method createMultipleTransactions has error: ${err.message}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
throw new Error(`Method createMultipleTransactions has error: ${err.message}`);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should check if DB is initialized well
|
* Should check if DB is initialized well
|
||||||
*/
|
*/
|
||||||
export async function getIndexedDB(netId?: NetIdType) {
|
export async function getIndexedDB(netId?: NetIdType) {
|
||||||
// key-value db for settings
|
// key-value db for settings
|
||||||
if (!netId) {
|
if (!netId) {
|
||||||
const idb = new IndexedDB({ dbName: 'tornado-core' });
|
const idb = new IndexedDB({ dbName: 'tornado-core' });
|
||||||
|
await idb.initDB();
|
||||||
|
return idb;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minimalIndexes = [
|
||||||
|
{
|
||||||
|
name: 'blockNumber',
|
||||||
|
unique: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'transactionHash',
|
||||||
|
unique: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultState = [
|
||||||
|
{
|
||||||
|
name: `echo_${netId}`,
|
||||||
|
keyPath: 'eid',
|
||||||
|
indexes: [
|
||||||
|
...minimalIndexes,
|
||||||
|
{
|
||||||
|
name: 'address',
|
||||||
|
unique: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `encrypted_notes_${netId}`,
|
||||||
|
keyPath: 'eid',
|
||||||
|
indexes: minimalIndexes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lastEvents',
|
||||||
|
keyPath: 'name',
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
unique: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const config = getConfig(netId);
|
||||||
|
|
||||||
|
const { tokens, nativeCurrency, registryContract, governanceContract } =
|
||||||
|
config;
|
||||||
|
|
||||||
|
const stores = [...defaultState];
|
||||||
|
|
||||||
|
if (registryContract) {
|
||||||
|
stores.push({
|
||||||
|
name: `registered_${netId}`,
|
||||||
|
keyPath: 'ensName',
|
||||||
|
indexes: [
|
||||||
|
...minimalIndexes,
|
||||||
|
{
|
||||||
|
name: 'relayerAddress',
|
||||||
|
unique: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (governanceContract) {
|
||||||
|
stores.push({
|
||||||
|
name: `governance_${netId}`,
|
||||||
|
keyPath: 'eid',
|
||||||
|
indexes: [
|
||||||
|
...minimalIndexes,
|
||||||
|
{
|
||||||
|
name: 'event',
|
||||||
|
unique: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(tokens).forEach(([token, { instanceAddress }]) => {
|
||||||
|
Object.keys(instanceAddress).forEach((amount) => {
|
||||||
|
if (nativeCurrency === token) {
|
||||||
|
stores.push(
|
||||||
|
{
|
||||||
|
name: `stringify_bloom_${netId}_${token}_${amount}`,
|
||||||
|
keyPath: 'hashBloom',
|
||||||
|
indexes: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `stringify_tree_${netId}_${token}_${amount}`,
|
||||||
|
keyPath: 'hashTree',
|
||||||
|
indexes: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
stores.push(
|
||||||
|
{
|
||||||
|
name: `deposits_${netId}_${token}_${amount}`,
|
||||||
|
keyPath: 'leafIndex', // the key by which it refers to the object must be in all instances of the storage
|
||||||
|
indexes: [
|
||||||
|
...minimalIndexes,
|
||||||
|
{
|
||||||
|
name: 'commitment',
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `withdrawals_${netId}_${token}_${amount}`,
|
||||||
|
keyPath: 'eid',
|
||||||
|
indexes: [
|
||||||
|
...minimalIndexes,
|
||||||
|
{
|
||||||
|
name: 'nullifierHash',
|
||||||
|
unique: true,
|
||||||
|
}, // keys on which the index is created
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const idb = new IndexedDB({
|
||||||
|
dbName: `tornado_core_${netId}`,
|
||||||
|
stores,
|
||||||
|
});
|
||||||
|
|
||||||
await idb.initDB();
|
await idb.initDB();
|
||||||
|
|
||||||
return idb;
|
return idb;
|
||||||
}
|
|
||||||
|
|
||||||
const minimalIndexes = [
|
|
||||||
{
|
|
||||||
name: 'blockNumber',
|
|
||||||
unique: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'transactionHash',
|
|
||||||
unique: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const defaultState = [
|
|
||||||
{
|
|
||||||
name: `echo_${netId}`,
|
|
||||||
keyPath: 'eid',
|
|
||||||
indexes: [
|
|
||||||
...minimalIndexes,
|
|
||||||
{
|
|
||||||
name: 'address',
|
|
||||||
unique: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `encrypted_notes_${netId}`,
|
|
||||||
keyPath: 'eid',
|
|
||||||
indexes: minimalIndexes,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'lastEvents',
|
|
||||||
keyPath: 'name',
|
|
||||||
indexes: [
|
|
||||||
{
|
|
||||||
name: 'name',
|
|
||||||
unique: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const config = getConfig(netId);
|
|
||||||
|
|
||||||
const { tokens, nativeCurrency, registryContract, governanceContract } = config;
|
|
||||||
|
|
||||||
const stores = [...defaultState];
|
|
||||||
|
|
||||||
if (registryContract) {
|
|
||||||
stores.push({
|
|
||||||
name: `registered_${netId}`,
|
|
||||||
keyPath: 'ensName',
|
|
||||||
indexes: [
|
|
||||||
...minimalIndexes,
|
|
||||||
{
|
|
||||||
name: 'relayerAddress',
|
|
||||||
unique: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (governanceContract) {
|
|
||||||
stores.push({
|
|
||||||
name: `governance_${netId}`,
|
|
||||||
keyPath: 'eid',
|
|
||||||
indexes: [
|
|
||||||
...minimalIndexes,
|
|
||||||
{
|
|
||||||
name: 'event',
|
|
||||||
unique: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.entries(tokens).forEach(([token, { instanceAddress }]) => {
|
|
||||||
Object.keys(instanceAddress).forEach((amount) => {
|
|
||||||
if (nativeCurrency === token) {
|
|
||||||
stores.push(
|
|
||||||
{
|
|
||||||
name: `stringify_bloom_${netId}_${token}_${amount}`,
|
|
||||||
keyPath: 'hashBloom',
|
|
||||||
indexes: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `stringify_tree_${netId}_${token}_${amount}`,
|
|
||||||
keyPath: 'hashTree',
|
|
||||||
indexes: [],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
stores.push(
|
|
||||||
{
|
|
||||||
name: `deposits_${netId}_${token}_${amount}`,
|
|
||||||
keyPath: 'leafIndex', // the key by which it refers to the object must be in all instances of the storage
|
|
||||||
indexes: [
|
|
||||||
...minimalIndexes,
|
|
||||||
{
|
|
||||||
name: 'commitment',
|
|
||||||
unique: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `withdrawals_${netId}_${token}_${amount}`,
|
|
||||||
keyPath: 'eid',
|
|
||||||
indexes: [
|
|
||||||
...minimalIndexes,
|
|
||||||
{
|
|
||||||
name: 'nullifierHash',
|
|
||||||
unique: true,
|
|
||||||
}, // keys on which the index is created
|
|
||||||
],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const idb = new IndexedDB({
|
|
||||||
dbName: `tornado_core_${netId}`,
|
|
||||||
stores,
|
|
||||||
});
|
|
||||||
|
|
||||||
await idb.initDB();
|
|
||||||
|
|
||||||
return idb;
|
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/ip.ts
14
src/ip.ts
@@ -1,14 +1,14 @@
|
|||||||
import { fetchData } from './providers';
|
import { fetchData } from './providers';
|
||||||
|
|
||||||
export interface IPResult {
|
export interface IPResult {
|
||||||
ip: string;
|
ip: string;
|
||||||
iso?: string;
|
iso?: string;
|
||||||
tor?: boolean;
|
tor?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchIp(ipEcho: string) {
|
export async function fetchIp(ipEcho: string) {
|
||||||
return (await fetchData(ipEcho, {
|
return (await fetchData(ipEcho, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
})) as IPResult;
|
})) as IPResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { Worker as NodeWorker } from 'worker_threads';
|
import { Worker as NodeWorker } from 'worker_threads';
|
||||||
import { MerkleTree, PartialMerkleTree, Element, TreeEdge } from '@tornado/fixed-merkle-tree';
|
import {
|
||||||
|
MerkleTree,
|
||||||
|
PartialMerkleTree,
|
||||||
|
Element,
|
||||||
|
TreeEdge,
|
||||||
|
} from '@tornado/fixed-merkle-tree';
|
||||||
import type { Tornado } from '@tornado/contracts';
|
import type { Tornado } from '@tornado/contracts';
|
||||||
import { isNode, toFixedHex } from './utils';
|
import { isNode, toFixedHex } from './utils';
|
||||||
import { mimc } from './mimc';
|
import { mimc } from './mimc';
|
||||||
@@ -8,195 +13,247 @@ import type { DepositsEvents } from './events';
|
|||||||
import type { NetIdType } from './networkConfig';
|
import type { NetIdType } from './networkConfig';
|
||||||
|
|
||||||
export interface MerkleTreeConstructor extends DepositType {
|
export interface MerkleTreeConstructor extends DepositType {
|
||||||
Tornado: Tornado;
|
Tornado: Tornado;
|
||||||
commitmentHex?: string;
|
commitmentHex?: string;
|
||||||
merkleTreeHeight?: number;
|
merkleTreeHeight?: number;
|
||||||
emptyElement?: string;
|
emptyElement?: string;
|
||||||
merkleWorkerPath?: string;
|
merkleWorkerPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MerkleTreeService {
|
export class MerkleTreeService {
|
||||||
currency: string;
|
currency: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
netId: NetIdType;
|
netId: NetIdType;
|
||||||
Tornado: Tornado;
|
Tornado: Tornado;
|
||||||
commitmentHex?: string;
|
commitmentHex?: string;
|
||||||
instanceName: string;
|
instanceName: string;
|
||||||
|
|
||||||
merkleTreeHeight: number;
|
merkleTreeHeight: number;
|
||||||
emptyElement: string;
|
emptyElement: string;
|
||||||
|
|
||||||
merkleWorkerPath?: string;
|
merkleWorkerPath?: string;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
netId,
|
netId,
|
||||||
amount,
|
amount,
|
||||||
currency,
|
currency,
|
||||||
Tornado,
|
Tornado,
|
||||||
commitmentHex,
|
commitmentHex,
|
||||||
merkleTreeHeight = 20,
|
merkleTreeHeight = 20,
|
||||||
emptyElement = '21663839004416932945382355908790599225266501822907911457504978515578255421292',
|
emptyElement = '21663839004416932945382355908790599225266501822907911457504978515578255421292',
|
||||||
merkleWorkerPath,
|
merkleWorkerPath,
|
||||||
}: MerkleTreeConstructor) {
|
}: MerkleTreeConstructor) {
|
||||||
const instanceName = `${netId}_${currency}_${amount}`;
|
const instanceName = `${netId}_${currency}_${amount}`;
|
||||||
|
|
||||||
this.currency = currency;
|
this.currency = currency;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.netId = Number(netId);
|
this.netId = Number(netId);
|
||||||
|
|
||||||
this.Tornado = Tornado;
|
this.Tornado = Tornado;
|
||||||
this.instanceName = instanceName;
|
this.instanceName = instanceName;
|
||||||
this.commitmentHex = commitmentHex;
|
this.commitmentHex = commitmentHex;
|
||||||
|
|
||||||
this.merkleTreeHeight = merkleTreeHeight;
|
this.merkleTreeHeight = merkleTreeHeight;
|
||||||
this.emptyElement = emptyElement;
|
this.emptyElement = emptyElement;
|
||||||
this.merkleWorkerPath = merkleWorkerPath;
|
this.merkleWorkerPath = merkleWorkerPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createTree(events: Element[]) {
|
async createTree(events: Element[]) {
|
||||||
const { hash: hashFunction } = await mimc.getHash();
|
const { hash: hashFunction } = await mimc.getHash();
|
||||||
|
|
||||||
if (this.merkleWorkerPath) {
|
if (this.merkleWorkerPath) {
|
||||||
console.log('Using merkleWorker\n');
|
console.log('Using merkleWorker\n');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
const merkleWorkerPromise = new Promise((resolve, reject) => {
|
const merkleWorkerPromise = new Promise(
|
||||||
const worker = new NodeWorker(this.merkleWorkerPath as string, {
|
(resolve, reject) => {
|
||||||
workerData: {
|
const worker = new NodeWorker(
|
||||||
merkleTreeHeight: this.merkleTreeHeight,
|
this.merkleWorkerPath as string,
|
||||||
elements: events,
|
{
|
||||||
zeroElement: this.emptyElement,
|
workerData: {
|
||||||
},
|
merkleTreeHeight: this.merkleTreeHeight,
|
||||||
});
|
elements: events,
|
||||||
worker.on('message', resolve);
|
zeroElement: this.emptyElement,
|
||||||
worker.on('error', reject);
|
},
|
||||||
worker.on('exit', (code) => {
|
},
|
||||||
if (code !== 0) {
|
);
|
||||||
reject(new Error(`Worker stopped with exit code ${code}`));
|
worker.on('message', resolve);
|
||||||
}
|
worker.on('error', reject);
|
||||||
});
|
worker.on('exit', (code) => {
|
||||||
}) as Promise<string>;
|
if (code !== 0) {
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
`Worker stopped with exit code ${code}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
) as Promise<string>;
|
||||||
|
|
||||||
return MerkleTree.deserialize(JSON.parse(await merkleWorkerPromise), hashFunction);
|
return MerkleTree.deserialize(
|
||||||
} else {
|
JSON.parse(await merkleWorkerPromise),
|
||||||
const merkleWorkerPromise = new Promise((resolve, reject) => {
|
hashFunction,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
);
|
||||||
const worker = new (Worker as any)(this.merkleWorkerPath);
|
} else {
|
||||||
|
const merkleWorkerPromise = new Promise(
|
||||||
|
(resolve, reject) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const worker = new (Worker as any)(
|
||||||
|
this.merkleWorkerPath,
|
||||||
|
);
|
||||||
|
|
||||||
worker.onmessage = (e: { data: string }) => {
|
worker.onmessage = (e: { data: string }) => {
|
||||||
resolve(e.data);
|
resolve(e.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
worker.onerror = (e: any) => {
|
worker.onerror = (e: any) => {
|
||||||
reject(e);
|
reject(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
worker.postMessage({
|
worker.postMessage({
|
||||||
merkleTreeHeight: this.merkleTreeHeight,
|
merkleTreeHeight: this.merkleTreeHeight,
|
||||||
elements: events,
|
elements: events,
|
||||||
zeroElement: this.emptyElement,
|
zeroElement: this.emptyElement,
|
||||||
});
|
});
|
||||||
}) as Promise<string>;
|
},
|
||||||
|
) as Promise<string>;
|
||||||
|
|
||||||
return MerkleTree.deserialize(JSON.parse(await merkleWorkerPromise), hashFunction);
|
return MerkleTree.deserialize(
|
||||||
|
JSON.parse(await merkleWorkerPromise),
|
||||||
|
hashFunction,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(
|
||||||
|
'merkleWorker failed, falling back to synchronous merkle tree',
|
||||||
|
);
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
console.log('merkleWorker failed, falling back to synchronous merkle tree');
|
return new MerkleTree(this.merkleTreeHeight, events, {
|
||||||
console.log(err);
|
zeroElement: this.emptyElement,
|
||||||
}
|
hashFunction,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MerkleTree(this.merkleTreeHeight, events, {
|
async createPartialTree({
|
||||||
zeroElement: this.emptyElement,
|
edge,
|
||||||
hashFunction,
|
elements,
|
||||||
});
|
}: {
|
||||||
}
|
edge: TreeEdge;
|
||||||
|
elements: Element[];
|
||||||
|
}) {
|
||||||
|
const { hash: hashFunction } = await mimc.getHash();
|
||||||
|
|
||||||
async createPartialTree({ edge, elements }: { edge: TreeEdge; elements: Element[] }) {
|
if (this.merkleWorkerPath) {
|
||||||
const { hash: hashFunction } = await mimc.getHash();
|
console.log('Using merkleWorker\n');
|
||||||
|
|
||||||
if (this.merkleWorkerPath) {
|
try {
|
||||||
console.log('Using merkleWorker\n');
|
if (isNode) {
|
||||||
|
const merkleWorkerPromise = new Promise(
|
||||||
|
(resolve, reject) => {
|
||||||
|
const worker = new NodeWorker(
|
||||||
|
this.merkleWorkerPath as string,
|
||||||
|
{
|
||||||
|
workerData: {
|
||||||
|
merkleTreeHeight: this.merkleTreeHeight,
|
||||||
|
edge,
|
||||||
|
elements,
|
||||||
|
zeroElement: this.emptyElement,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
worker.on('message', resolve);
|
||||||
|
worker.on('error', reject);
|
||||||
|
worker.on('exit', (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
`Worker stopped with exit code ${code}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
) as Promise<string>;
|
||||||
|
|
||||||
try {
|
return PartialMerkleTree.deserialize(
|
||||||
if (isNode) {
|
JSON.parse(await merkleWorkerPromise),
|
||||||
const merkleWorkerPromise = new Promise((resolve, reject) => {
|
hashFunction,
|
||||||
const worker = new NodeWorker(this.merkleWorkerPath as string, {
|
);
|
||||||
workerData: {
|
} else {
|
||||||
merkleTreeHeight: this.merkleTreeHeight,
|
const merkleWorkerPromise = new Promise(
|
||||||
edge,
|
(resolve, reject) => {
|
||||||
elements,
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
zeroElement: this.emptyElement,
|
const worker = new (Worker as any)(
|
||||||
},
|
this.merkleWorkerPath,
|
||||||
});
|
);
|
||||||
worker.on('message', resolve);
|
|
||||||
worker.on('error', reject);
|
|
||||||
worker.on('exit', (code) => {
|
|
||||||
if (code !== 0) {
|
|
||||||
reject(new Error(`Worker stopped with exit code ${code}`));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}) as Promise<string>;
|
|
||||||
|
|
||||||
return PartialMerkleTree.deserialize(JSON.parse(await merkleWorkerPromise), hashFunction);
|
worker.onmessage = (e: { data: string }) => {
|
||||||
} else {
|
resolve(e.data);
|
||||||
const merkleWorkerPromise = new Promise((resolve, reject) => {
|
};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const worker = new (Worker as any)(this.merkleWorkerPath);
|
|
||||||
|
|
||||||
worker.onmessage = (e: { data: string }) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
resolve(e.data);
|
worker.onerror = (e: any) => {
|
||||||
};
|
reject(e);
|
||||||
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
worker.postMessage({
|
||||||
worker.onerror = (e: any) => {
|
merkleTreeHeight: this.merkleTreeHeight,
|
||||||
reject(e);
|
edge,
|
||||||
};
|
elements,
|
||||||
|
zeroElement: this.emptyElement,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
) as Promise<string>;
|
||||||
|
|
||||||
worker.postMessage({
|
return PartialMerkleTree.deserialize(
|
||||||
merkleTreeHeight: this.merkleTreeHeight,
|
JSON.parse(await merkleWorkerPromise),
|
||||||
edge,
|
hashFunction,
|
||||||
elements,
|
);
|
||||||
zeroElement: this.emptyElement,
|
}
|
||||||
});
|
} catch (err) {
|
||||||
}) as Promise<string>;
|
console.log(
|
||||||
|
'merkleWorker failed, falling back to synchronous merkle tree',
|
||||||
return PartialMerkleTree.deserialize(JSON.parse(await merkleWorkerPromise), hashFunction);
|
);
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
console.log('merkleWorker failed, falling back to synchronous merkle tree');
|
return new PartialMerkleTree(this.merkleTreeHeight, edge, elements, {
|
||||||
console.log(err);
|
zeroElement: this.emptyElement,
|
||||||
}
|
hashFunction,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PartialMerkleTree(this.merkleTreeHeight, edge, elements, {
|
async verifyTree(events: DepositsEvents[]) {
|
||||||
zeroElement: this.emptyElement,
|
console.log(
|
||||||
hashFunction,
|
`\nCreating deposit tree for ${this.netId} ${this.amount} ${this.currency.toUpperCase()} would take a while\n`,
|
||||||
});
|
);
|
||||||
}
|
|
||||||
|
|
||||||
async verifyTree(events: DepositsEvents[]) {
|
const timeStart = Date.now();
|
||||||
console.log(
|
|
||||||
`\nCreating deposit tree for ${this.netId} ${this.amount} ${this.currency.toUpperCase()} would take a while\n`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const timeStart = Date.now();
|
const tree = await this.createTree(
|
||||||
|
events.map(({ commitment }) => commitment),
|
||||||
|
);
|
||||||
|
|
||||||
const tree = await this.createTree(events.map(({ commitment }) => commitment));
|
const isKnownRoot = await this.Tornado.isKnownRoot(
|
||||||
|
toFixedHex(BigInt(tree.root)),
|
||||||
|
);
|
||||||
|
|
||||||
const isKnownRoot = await this.Tornado.isKnownRoot(toFixedHex(BigInt(tree.root)));
|
if (!isKnownRoot) {
|
||||||
|
const errMsg = `Deposit Event ${this.netId} ${this.amount} ${this.currency} is invalid`;
|
||||||
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isKnownRoot) {
|
console.log(
|
||||||
const errMsg = `Deposit Event ${this.netId} ${this.amount} ${this.currency} is invalid`;
|
`\nCreated ${this.netId} ${this.amount} ${this.currency.toUpperCase()} tree in ${Date.now() - timeStart}ms\n`,
|
||||||
throw new Error(errMsg);
|
);
|
||||||
|
|
||||||
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
|
||||||
`\nCreated ${this.netId} ${this.amount} ${this.currency.toUpperCase()} tree in ${Date.now() - timeStart}ms\n`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,70 +1,95 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import workerThreads from 'worker_threads';
|
import workerThreads from 'worker_threads';
|
||||||
import { MerkleTree, Element, TreeEdge, PartialMerkleTree } from '@tornado/fixed-merkle-tree';
|
import {
|
||||||
|
MerkleTree,
|
||||||
|
Element,
|
||||||
|
TreeEdge,
|
||||||
|
PartialMerkleTree,
|
||||||
|
} from '@tornado/fixed-merkle-tree';
|
||||||
import { mimc } from './mimc';
|
import { mimc } from './mimc';
|
||||||
import { isNode } from './utils';
|
import { isNode } from './utils';
|
||||||
|
|
||||||
interface WorkData {
|
interface WorkData {
|
||||||
merkleTreeHeight: number;
|
merkleTreeHeight: number;
|
||||||
edge?: TreeEdge;
|
edge?: TreeEdge;
|
||||||
elements: Element[];
|
elements: Element[];
|
||||||
zeroElement: string;
|
zeroElement: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function nodePostWork() {
|
async function nodePostWork() {
|
||||||
const { hash: hashFunction } = await mimc.getHash();
|
|
||||||
const { merkleTreeHeight, edge, elements, zeroElement } = workerThreads.workerData as WorkData;
|
|
||||||
|
|
||||||
if (edge) {
|
|
||||||
const merkleTree = new PartialMerkleTree(merkleTreeHeight, edge, elements, {
|
|
||||||
zeroElement,
|
|
||||||
hashFunction,
|
|
||||||
});
|
|
||||||
|
|
||||||
(workerThreads.parentPort as workerThreads.MessagePort).postMessage(merkleTree.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const merkleTree = new MerkleTree(merkleTreeHeight, elements, {
|
|
||||||
zeroElement,
|
|
||||||
hashFunction,
|
|
||||||
});
|
|
||||||
|
|
||||||
(workerThreads.parentPort as workerThreads.MessagePort).postMessage(merkleTree.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNode && workerThreads) {
|
|
||||||
nodePostWork();
|
|
||||||
} else if (!isNode && typeof addEventListener === 'function' && typeof postMessage === 'function') {
|
|
||||||
addEventListener('message', async (e: any) => {
|
|
||||||
let data;
|
|
||||||
|
|
||||||
if (e.data) {
|
|
||||||
data = e.data;
|
|
||||||
} else {
|
|
||||||
data = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { hash: hashFunction } = await mimc.getHash();
|
const { hash: hashFunction } = await mimc.getHash();
|
||||||
const { merkleTreeHeight, edge, elements, zeroElement } = data as WorkData;
|
const { merkleTreeHeight, edge, elements, zeroElement } =
|
||||||
|
workerThreads.workerData as WorkData;
|
||||||
|
|
||||||
if (edge) {
|
if (edge) {
|
||||||
const merkleTree = new PartialMerkleTree(merkleTreeHeight, edge, elements, {
|
const merkleTree = new PartialMerkleTree(
|
||||||
zeroElement,
|
merkleTreeHeight,
|
||||||
hashFunction,
|
edge,
|
||||||
});
|
elements,
|
||||||
|
{
|
||||||
|
zeroElement,
|
||||||
|
hashFunction,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
postMessage(merkleTree.toString());
|
(workerThreads.parentPort as workerThreads.MessagePort).postMessage(
|
||||||
return;
|
merkleTree.toString(),
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const merkleTree = new MerkleTree(merkleTreeHeight, elements, {
|
const merkleTree = new MerkleTree(merkleTreeHeight, elements, {
|
||||||
zeroElement,
|
zeroElement,
|
||||||
hashFunction,
|
hashFunction,
|
||||||
});
|
});
|
||||||
|
|
||||||
postMessage(merkleTree.toString());
|
(workerThreads.parentPort as workerThreads.MessagePort).postMessage(
|
||||||
});
|
merkleTree.toString(),
|
||||||
} else {
|
);
|
||||||
throw new Error('This browser / environment does not support workers!');
|
}
|
||||||
|
|
||||||
|
if (isNode && workerThreads) {
|
||||||
|
nodePostWork();
|
||||||
|
} else if (
|
||||||
|
!isNode &&
|
||||||
|
typeof addEventListener === 'function' &&
|
||||||
|
typeof postMessage === 'function'
|
||||||
|
) {
|
||||||
|
addEventListener('message', async (e: any) => {
|
||||||
|
let data;
|
||||||
|
|
||||||
|
if (e.data) {
|
||||||
|
data = e.data;
|
||||||
|
} else {
|
||||||
|
data = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hash: hashFunction } = await mimc.getHash();
|
||||||
|
const { merkleTreeHeight, edge, elements, zeroElement } =
|
||||||
|
data as WorkData;
|
||||||
|
|
||||||
|
if (edge) {
|
||||||
|
const merkleTree = new PartialMerkleTree(
|
||||||
|
merkleTreeHeight,
|
||||||
|
edge,
|
||||||
|
elements,
|
||||||
|
{
|
||||||
|
zeroElement,
|
||||||
|
hashFunction,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
postMessage(merkleTree.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const merkleTree = new MerkleTree(merkleTreeHeight, elements, {
|
||||||
|
zeroElement,
|
||||||
|
hashFunction,
|
||||||
|
});
|
||||||
|
|
||||||
|
postMessage(merkleTree.toString());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error('This browser / environment does not support workers!');
|
||||||
}
|
}
|
||||||
|
|||||||
37
src/mimc.ts
37
src/mimc.ts
@@ -2,27 +2,30 @@ import { MimcSponge, buildMimcSponge } from 'circomlibjs';
|
|||||||
import type { Element, HashFunction } from '@tornado/fixed-merkle-tree';
|
import type { Element, HashFunction } from '@tornado/fixed-merkle-tree';
|
||||||
|
|
||||||
export class Mimc {
|
export class Mimc {
|
||||||
sponge?: MimcSponge;
|
sponge?: MimcSponge;
|
||||||
hash?: HashFunction<Element>;
|
hash?: HashFunction<Element>;
|
||||||
mimcPromise: Promise<void>;
|
mimcPromise: Promise<void>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.mimcPromise = this.initMimc();
|
this.mimcPromise = this.initMimc();
|
||||||
}
|
}
|
||||||
|
|
||||||
async initMimc() {
|
async initMimc() {
|
||||||
this.sponge = await buildMimcSponge();
|
this.sponge = await buildMimcSponge();
|
||||||
this.hash = (left, right) => this.sponge?.F.toString(this.sponge?.multiHash([BigInt(left), BigInt(right)]));
|
this.hash = (left, right) =>
|
||||||
}
|
this.sponge?.F.toString(
|
||||||
|
this.sponge?.multiHash([BigInt(left), BigInt(right)]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async getHash() {
|
async getHash() {
|
||||||
await this.mimcPromise;
|
await this.mimcPromise;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sponge: this.sponge,
|
sponge: this.sponge,
|
||||||
hash: this.hash,
|
hash: this.hash,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mimc = new Mimc();
|
export const mimc = new Mimc();
|
||||||
|
|||||||
@@ -2,36 +2,44 @@ import { BaseContract, Interface } from 'ethers';
|
|||||||
import { Multicall } from './typechain';
|
import { Multicall } from './typechain';
|
||||||
|
|
||||||
export interface Call3 {
|
export interface Call3 {
|
||||||
contract?: BaseContract;
|
contract?: BaseContract;
|
||||||
address?: string;
|
address?: string;
|
||||||
interface?: Interface;
|
interface?: Interface;
|
||||||
name: string;
|
name: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
params?: any[];
|
params?: any[];
|
||||||
allowFailure?: boolean;
|
allowFailure?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function multicall(Multicall: Multicall, calls: Call3[]) {
|
export async function multicall(Multicall: Multicall, calls: Call3[]) {
|
||||||
const calldata = calls.map((call) => {
|
const calldata = calls.map((call) => {
|
||||||
const target = (call.contract?.target || call.address) as string;
|
const target = (call.contract?.target || call.address) as string;
|
||||||
const callInterface = (call.contract?.interface || call.interface) as Interface;
|
const callInterface = (call.contract?.interface ||
|
||||||
|
call.interface) as Interface;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
target,
|
target,
|
||||||
callData: callInterface.encodeFunctionData(call.name, call.params),
|
callData: callInterface.encodeFunctionData(call.name, call.params),
|
||||||
allowFailure: call.allowFailure ?? false,
|
allowFailure: call.allowFailure ?? false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const returnData = await Multicall.aggregate3.staticCall(calldata);
|
const returnData = await Multicall.aggregate3.staticCall(calldata);
|
||||||
|
|
||||||
const res = returnData.map((call, i) => {
|
const res = returnData.map((call, i) => {
|
||||||
const callInterface = (calls[i].contract?.interface || calls[i].interface) as Interface;
|
const callInterface = (calls[i].contract?.interface ||
|
||||||
const [result, data] = call;
|
calls[i].interface) as Interface;
|
||||||
const decodeResult =
|
const [result, data] = call;
|
||||||
result && data && data !== '0x' ? callInterface.decodeFunctionResult(calls[i].name, data) : null;
|
const decodeResult =
|
||||||
return !decodeResult ? null : decodeResult.length === 1 ? decodeResult[0] : decodeResult;
|
result && data && data !== '0x'
|
||||||
});
|
? callInterface.decodeFunctionResult(calls[i].name, data)
|
||||||
|
: null;
|
||||||
|
return !decodeResult
|
||||||
|
? null
|
||||||
|
: decodeResult.length === 1
|
||||||
|
? decodeResult[0]
|
||||||
|
: decodeResult;
|
||||||
|
});
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
1306
src/networkConfig.ts
1306
src/networkConfig.ts
File diff suppressed because it is too large
Load Diff
@@ -1,32 +1,34 @@
|
|||||||
import { BabyJub, PedersenHash, Point, buildPedersenHash } from 'circomlibjs';
|
import { BabyJub, PedersenHash, Point, buildPedersenHash } from 'circomlibjs';
|
||||||
|
|
||||||
export class Pedersen {
|
export class Pedersen {
|
||||||
pedersenHash?: PedersenHash;
|
pedersenHash?: PedersenHash;
|
||||||
babyJub?: BabyJub;
|
babyJub?: BabyJub;
|
||||||
pedersenPromise: Promise<void>;
|
pedersenPromise: Promise<void>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.pedersenPromise = this.initPedersen();
|
this.pedersenPromise = this.initPedersen();
|
||||||
}
|
}
|
||||||
|
|
||||||
async initPedersen() {
|
async initPedersen() {
|
||||||
this.pedersenHash = await buildPedersenHash();
|
this.pedersenHash = await buildPedersenHash();
|
||||||
this.babyJub = this.pedersenHash.babyJub;
|
this.babyJub = this.pedersenHash.babyJub;
|
||||||
}
|
}
|
||||||
|
|
||||||
async unpackPoint(buffer: Uint8Array) {
|
async unpackPoint(buffer: Uint8Array) {
|
||||||
await this.pedersenPromise;
|
await this.pedersenPromise;
|
||||||
return this.babyJub?.unpackPoint(this.pedersenHash?.hash(buffer) as Uint8Array);
|
return this.babyJub?.unpackPoint(
|
||||||
}
|
this.pedersenHash?.hash(buffer) as Uint8Array,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
toStringBuffer(buffer: Uint8Array): string {
|
toStringBuffer(buffer: Uint8Array): string {
|
||||||
return this.babyJub?.F.toString(buffer);
|
return this.babyJub?.F.toString(buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const pedersen = new Pedersen();
|
export const pedersen = new Pedersen();
|
||||||
|
|
||||||
export async function buffPedersenHash(buffer: Uint8Array): Promise<string> {
|
export async function buffPedersenHash(buffer: Uint8Array): Promise<string> {
|
||||||
const [hash] = (await pedersen.unpackPoint(buffer)) as Point;
|
const [hash] = (await pedersen.unpackPoint(buffer)) as Point;
|
||||||
return pedersen.toStringBuffer(hash);
|
return pedersen.toStringBuffer(hash);
|
||||||
}
|
}
|
||||||
|
|||||||
443
src/permit.ts
443
src/permit.ts
@@ -1,28 +1,33 @@
|
|||||||
import { ERC20Permit, ERC20Mock, TORN, PermitTornado } from '@tornado/contracts';
|
|
||||||
import {
|
import {
|
||||||
BaseContract,
|
ERC20Permit,
|
||||||
MaxUint256,
|
ERC20Mock,
|
||||||
Provider,
|
TORN,
|
||||||
Signature,
|
PermitTornado,
|
||||||
Signer,
|
} from '@tornado/contracts';
|
||||||
solidityPackedKeccak256,
|
import {
|
||||||
TypedDataEncoder,
|
BaseContract,
|
||||||
TypedDataField,
|
MaxUint256,
|
||||||
|
Provider,
|
||||||
|
Signature,
|
||||||
|
Signer,
|
||||||
|
solidityPackedKeccak256,
|
||||||
|
TypedDataEncoder,
|
||||||
|
TypedDataField,
|
||||||
} from 'ethers';
|
} from 'ethers';
|
||||||
import { rBigInt } from './utils';
|
import { rBigInt } from './utils';
|
||||||
|
|
||||||
export interface PermitValue {
|
export interface PermitValue {
|
||||||
spender: string;
|
spender: string;
|
||||||
value: bigint;
|
value: bigint;
|
||||||
nonce?: bigint;
|
nonce?: bigint;
|
||||||
deadline?: bigint;
|
deadline?: bigint;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PermitCommitments {
|
export interface PermitCommitments {
|
||||||
denomination: bigint;
|
denomination: bigint;
|
||||||
commitments: string[];
|
commitments: string[];
|
||||||
nonce?: bigint;
|
nonce?: bigint;
|
||||||
deadline?: bigint;
|
deadline?: bigint;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const permit2Address = '0x000000000022D473030F116dDEE9F6B43aC78BA3';
|
export const permit2Address = '0x000000000022D473030F116dDEE9F6B43aC78BA3';
|
||||||
@@ -31,212 +36,220 @@ export const permit2Address = '0x000000000022D473030F116dDEE9F6B43aC78BA3';
|
|||||||
* From @uniswap/permit2-sdk ported for ethers.js v6
|
* From @uniswap/permit2-sdk ported for ethers.js v6
|
||||||
*/
|
*/
|
||||||
export interface Witness {
|
export interface Witness {
|
||||||
witnessTypeName: string;
|
witnessTypeName: string;
|
||||||
witnessType: {
|
witnessType: {
|
||||||
[key: string]: TypedDataField[];
|
[key: string]: TypedDataField[];
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
witness: any;
|
witness: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPermitSignature({
|
export async function getPermitSignature({
|
||||||
Token,
|
|
||||||
signer,
|
|
||||||
spender,
|
|
||||||
value,
|
|
||||||
nonce,
|
|
||||||
deadline,
|
|
||||||
}: PermitValue & {
|
|
||||||
Token: ERC20Permit | ERC20Mock | TORN;
|
|
||||||
signer?: Signer;
|
|
||||||
}) {
|
|
||||||
const sigSigner = (signer || Token.runner) as Signer & { address: string };
|
|
||||||
const provider = sigSigner.provider as Provider;
|
|
||||||
|
|
||||||
const [name, lastNonce, { chainId }] = await Promise.all([
|
|
||||||
Token.name(),
|
|
||||||
Token.nonces(sigSigner.address),
|
|
||||||
provider.getNetwork(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const DOMAIN_SEPARATOR = {
|
|
||||||
name,
|
|
||||||
version: '1',
|
|
||||||
chainId,
|
|
||||||
verifyingContract: Token.target as string,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PERMIT_TYPE = {
|
|
||||||
Permit: [
|
|
||||||
{ name: 'owner', type: 'address' },
|
|
||||||
{ name: 'spender', type: 'address' },
|
|
||||||
{ name: 'value', type: 'uint256' },
|
|
||||||
{ name: 'nonce', type: 'uint256' },
|
|
||||||
{ name: 'deadline', type: 'uint256' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
return Signature.from(
|
|
||||||
await sigSigner.signTypedData(DOMAIN_SEPARATOR, PERMIT_TYPE, {
|
|
||||||
owner: sigSigner.address,
|
|
||||||
spender,
|
|
||||||
value,
|
|
||||||
nonce: nonce || lastNonce,
|
|
||||||
deadline: deadline || MaxUint256,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPermitCommitmentsSignature({
|
|
||||||
PermitTornado,
|
|
||||||
Token,
|
|
||||||
signer,
|
|
||||||
denomination,
|
|
||||||
commitments,
|
|
||||||
nonce,
|
|
||||||
}: PermitCommitments & {
|
|
||||||
PermitTornado: PermitTornado;
|
|
||||||
Token: ERC20Permit | ERC20Mock | TORN;
|
|
||||||
signer?: Signer;
|
|
||||||
}) {
|
|
||||||
const value = BigInt(commitments.length) * denomination;
|
|
||||||
const commitmentsHash = solidityPackedKeccak256(['bytes32[]'], [commitments]);
|
|
||||||
|
|
||||||
return await getPermitSignature({
|
|
||||||
Token,
|
Token,
|
||||||
signer,
|
signer,
|
||||||
spender: PermitTornado.target as string,
|
|
||||||
value,
|
|
||||||
nonce,
|
|
||||||
deadline: BigInt(commitmentsHash),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPermit2Signature({
|
|
||||||
Token,
|
|
||||||
signer,
|
|
||||||
spender,
|
|
||||||
value: amount,
|
|
||||||
nonce,
|
|
||||||
deadline,
|
|
||||||
witness,
|
|
||||||
}: PermitValue & {
|
|
||||||
Token: BaseContract;
|
|
||||||
signer?: Signer;
|
|
||||||
witness?: Witness;
|
|
||||||
}) {
|
|
||||||
const sigSigner = (signer || Token.runner) as Signer & { address: string };
|
|
||||||
const provider = sigSigner.provider as Provider;
|
|
||||||
|
|
||||||
const domain = {
|
|
||||||
name: 'Permit2',
|
|
||||||
chainId: (await provider.getNetwork()).chainId,
|
|
||||||
verifyingContract: permit2Address,
|
|
||||||
};
|
|
||||||
|
|
||||||
const types: {
|
|
||||||
[key: string]: TypedDataField[];
|
|
||||||
} = !witness
|
|
||||||
? {
|
|
||||||
PermitTransferFrom: [
|
|
||||||
{ name: 'permitted', type: 'TokenPermissions' },
|
|
||||||
{ name: 'spender', type: 'address' },
|
|
||||||
{ name: 'nonce', type: 'uint256' },
|
|
||||||
{ name: 'deadline', type: 'uint256' },
|
|
||||||
],
|
|
||||||
TokenPermissions: [
|
|
||||||
{ name: 'token', type: 'address' },
|
|
||||||
{ name: 'amount', type: 'uint256' },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
PermitWitnessTransferFrom: [
|
|
||||||
{ name: 'permitted', type: 'TokenPermissions' },
|
|
||||||
{ name: 'spender', type: 'address' },
|
|
||||||
{ name: 'nonce', type: 'uint256' },
|
|
||||||
{ name: 'deadline', type: 'uint256' },
|
|
||||||
{ name: 'witness', type: witness.witnessTypeName },
|
|
||||||
],
|
|
||||||
TokenPermissions: [
|
|
||||||
{ name: 'token', type: 'address' },
|
|
||||||
{ name: 'amount', type: 'uint256' },
|
|
||||||
],
|
|
||||||
...witness.witnessType,
|
|
||||||
};
|
|
||||||
|
|
||||||
const values: {
|
|
||||||
permitted: {
|
|
||||||
token: string;
|
|
||||||
amount: bigint;
|
|
||||||
};
|
|
||||||
spender: string;
|
|
||||||
nonce: bigint;
|
|
||||||
deadline: bigint;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
witness?: any;
|
|
||||||
} = {
|
|
||||||
permitted: {
|
|
||||||
token: Token.target as string,
|
|
||||||
amount,
|
|
||||||
},
|
|
||||||
spender,
|
spender,
|
||||||
// Sorted nonce are not required for Permit2
|
|
||||||
nonce: nonce || rBigInt(16),
|
|
||||||
deadline: deadline || MaxUint256,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (witness) {
|
|
||||||
values.witness = witness.witness;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hash = new TypedDataEncoder(types).hash(values);
|
|
||||||
|
|
||||||
const signature = Signature.from(await sigSigner.signTypedData(domain, types, values));
|
|
||||||
|
|
||||||
return {
|
|
||||||
domain,
|
|
||||||
types,
|
|
||||||
values,
|
|
||||||
hash,
|
|
||||||
signature,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPermit2CommitmentsSignature({
|
|
||||||
PermitTornado,
|
|
||||||
Token,
|
|
||||||
signer,
|
|
||||||
denomination,
|
|
||||||
commitments,
|
|
||||||
nonce,
|
|
||||||
deadline,
|
|
||||||
}: PermitCommitments & {
|
|
||||||
PermitTornado: PermitTornado;
|
|
||||||
Token: BaseContract;
|
|
||||||
signer?: Signer;
|
|
||||||
}) {
|
|
||||||
const value = BigInt(commitments.length) * denomination;
|
|
||||||
const commitmentsHash = solidityPackedKeccak256(['bytes32[]'], [commitments]);
|
|
||||||
|
|
||||||
return await getPermit2Signature({
|
|
||||||
Token,
|
|
||||||
signer,
|
|
||||||
spender: PermitTornado.target as string,
|
|
||||||
value,
|
value,
|
||||||
nonce,
|
nonce,
|
||||||
deadline,
|
deadline,
|
||||||
witness: {
|
}: PermitValue & {
|
||||||
witnessTypeName: 'PermitCommitments',
|
Token: ERC20Permit | ERC20Mock | TORN;
|
||||||
witnessType: {
|
signer?: Signer;
|
||||||
PermitCommitments: [
|
}) {
|
||||||
{ name: 'instance', type: 'address' },
|
const sigSigner = (signer || Token.runner) as Signer & { address: string };
|
||||||
{ name: 'commitmentsHash', type: 'bytes32' },
|
const provider = sigSigner.provider as Provider;
|
||||||
|
|
||||||
|
const [name, lastNonce, { chainId }] = await Promise.all([
|
||||||
|
Token.name(),
|
||||||
|
Token.nonces(sigSigner.address),
|
||||||
|
provider.getNetwork(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const DOMAIN_SEPARATOR = {
|
||||||
|
name,
|
||||||
|
version: '1',
|
||||||
|
chainId,
|
||||||
|
verifyingContract: Token.target as string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PERMIT_TYPE = {
|
||||||
|
Permit: [
|
||||||
|
{ name: 'owner', type: 'address' },
|
||||||
|
{ name: 'spender', type: 'address' },
|
||||||
|
{ name: 'value', type: 'uint256' },
|
||||||
|
{ name: 'nonce', type: 'uint256' },
|
||||||
|
{ name: 'deadline', type: 'uint256' },
|
||||||
],
|
],
|
||||||
},
|
};
|
||||||
witness: {
|
|
||||||
instance: PermitTornado.target,
|
return Signature.from(
|
||||||
commitmentsHash,
|
await sigSigner.signTypedData(DOMAIN_SEPARATOR, PERMIT_TYPE, {
|
||||||
},
|
owner: sigSigner.address,
|
||||||
},
|
spender,
|
||||||
});
|
value,
|
||||||
|
nonce: nonce || lastNonce,
|
||||||
|
deadline: deadline || MaxUint256,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPermitCommitmentsSignature({
|
||||||
|
PermitTornado,
|
||||||
|
Token,
|
||||||
|
signer,
|
||||||
|
denomination,
|
||||||
|
commitments,
|
||||||
|
nonce,
|
||||||
|
}: PermitCommitments & {
|
||||||
|
PermitTornado: PermitTornado;
|
||||||
|
Token: ERC20Permit | ERC20Mock | TORN;
|
||||||
|
signer?: Signer;
|
||||||
|
}) {
|
||||||
|
const value = BigInt(commitments.length) * denomination;
|
||||||
|
const commitmentsHash = solidityPackedKeccak256(
|
||||||
|
['bytes32[]'],
|
||||||
|
[commitments],
|
||||||
|
);
|
||||||
|
|
||||||
|
return await getPermitSignature({
|
||||||
|
Token,
|
||||||
|
signer,
|
||||||
|
spender: PermitTornado.target as string,
|
||||||
|
value,
|
||||||
|
nonce,
|
||||||
|
deadline: BigInt(commitmentsHash),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPermit2Signature({
|
||||||
|
Token,
|
||||||
|
signer,
|
||||||
|
spender,
|
||||||
|
value: amount,
|
||||||
|
nonce,
|
||||||
|
deadline,
|
||||||
|
witness,
|
||||||
|
}: PermitValue & {
|
||||||
|
Token: BaseContract;
|
||||||
|
signer?: Signer;
|
||||||
|
witness?: Witness;
|
||||||
|
}) {
|
||||||
|
const sigSigner = (signer || Token.runner) as Signer & { address: string };
|
||||||
|
const provider = sigSigner.provider as Provider;
|
||||||
|
|
||||||
|
const domain = {
|
||||||
|
name: 'Permit2',
|
||||||
|
chainId: (await provider.getNetwork()).chainId,
|
||||||
|
verifyingContract: permit2Address,
|
||||||
|
};
|
||||||
|
|
||||||
|
const types: {
|
||||||
|
[key: string]: TypedDataField[];
|
||||||
|
} = !witness
|
||||||
|
? {
|
||||||
|
PermitTransferFrom: [
|
||||||
|
{ name: 'permitted', type: 'TokenPermissions' },
|
||||||
|
{ name: 'spender', type: 'address' },
|
||||||
|
{ name: 'nonce', type: 'uint256' },
|
||||||
|
{ name: 'deadline', type: 'uint256' },
|
||||||
|
],
|
||||||
|
TokenPermissions: [
|
||||||
|
{ name: 'token', type: 'address' },
|
||||||
|
{ name: 'amount', type: 'uint256' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
PermitWitnessTransferFrom: [
|
||||||
|
{ name: 'permitted', type: 'TokenPermissions' },
|
||||||
|
{ name: 'spender', type: 'address' },
|
||||||
|
{ name: 'nonce', type: 'uint256' },
|
||||||
|
{ name: 'deadline', type: 'uint256' },
|
||||||
|
{ name: 'witness', type: witness.witnessTypeName },
|
||||||
|
],
|
||||||
|
TokenPermissions: [
|
||||||
|
{ name: 'token', type: 'address' },
|
||||||
|
{ name: 'amount', type: 'uint256' },
|
||||||
|
],
|
||||||
|
...witness.witnessType,
|
||||||
|
};
|
||||||
|
|
||||||
|
const values: {
|
||||||
|
permitted: {
|
||||||
|
token: string;
|
||||||
|
amount: bigint;
|
||||||
|
};
|
||||||
|
spender: string;
|
||||||
|
nonce: bigint;
|
||||||
|
deadline: bigint;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
witness?: any;
|
||||||
|
} = {
|
||||||
|
permitted: {
|
||||||
|
token: Token.target as string,
|
||||||
|
amount,
|
||||||
|
},
|
||||||
|
spender,
|
||||||
|
// Sorted nonce are not required for Permit2
|
||||||
|
nonce: nonce || rBigInt(16),
|
||||||
|
deadline: deadline || MaxUint256,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (witness) {
|
||||||
|
values.witness = witness.witness;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash = new TypedDataEncoder(types).hash(values);
|
||||||
|
|
||||||
|
const signature = Signature.from(
|
||||||
|
await sigSigner.signTypedData(domain, types, values),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
domain,
|
||||||
|
types,
|
||||||
|
values,
|
||||||
|
hash,
|
||||||
|
signature,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPermit2CommitmentsSignature({
|
||||||
|
PermitTornado,
|
||||||
|
Token,
|
||||||
|
signer,
|
||||||
|
denomination,
|
||||||
|
commitments,
|
||||||
|
nonce,
|
||||||
|
deadline,
|
||||||
|
}: PermitCommitments & {
|
||||||
|
PermitTornado: PermitTornado;
|
||||||
|
Token: BaseContract;
|
||||||
|
signer?: Signer;
|
||||||
|
}) {
|
||||||
|
const value = BigInt(commitments.length) * denomination;
|
||||||
|
const commitmentsHash = solidityPackedKeccak256(
|
||||||
|
['bytes32[]'],
|
||||||
|
[commitments],
|
||||||
|
);
|
||||||
|
|
||||||
|
return await getPermit2Signature({
|
||||||
|
Token,
|
||||||
|
signer,
|
||||||
|
spender: PermitTornado.target as string,
|
||||||
|
value,
|
||||||
|
nonce,
|
||||||
|
deadline,
|
||||||
|
witness: {
|
||||||
|
witnessTypeName: 'PermitCommitments',
|
||||||
|
witnessType: {
|
||||||
|
PermitCommitments: [
|
||||||
|
{ name: 'instance', type: 'address' },
|
||||||
|
{ name: 'commitmentsHash', type: 'bytes32' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
witness: {
|
||||||
|
instance: PermitTornado.target,
|
||||||
|
commitmentsHash,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
192
src/prices.ts
192
src/prices.ts
@@ -3,99 +3,123 @@ import { ERC20__factory, OffchainOracle, Multicall } from './typechain';
|
|||||||
import { multicall, Call3 } from './multicall';
|
import { multicall, Call3 } from './multicall';
|
||||||
|
|
||||||
export class TokenPriceOracle {
|
export class TokenPriceOracle {
|
||||||
oracle?: OffchainOracle;
|
oracle?: OffchainOracle;
|
||||||
multicall: Multicall;
|
multicall: Multicall;
|
||||||
provider: Provider;
|
provider: Provider;
|
||||||
|
|
||||||
fallbackPrice: bigint;
|
fallbackPrice: bigint;
|
||||||
|
|
||||||
constructor(provider: Provider, multicall: Multicall, oracle?: OffchainOracle) {
|
constructor(
|
||||||
this.provider = provider;
|
provider: Provider,
|
||||||
this.multicall = multicall;
|
multicall: Multicall,
|
||||||
this.oracle = oracle;
|
oracle?: OffchainOracle,
|
||||||
this.fallbackPrice = parseEther('0.0001');
|
) {
|
||||||
}
|
this.provider = provider;
|
||||||
|
this.multicall = multicall;
|
||||||
buildCalls(
|
this.oracle = oracle;
|
||||||
tokens: {
|
this.fallbackPrice = parseEther('0.0001');
|
||||||
tokenAddress: string;
|
|
||||||
decimals: number;
|
|
||||||
}[],
|
|
||||||
): Call3[] {
|
|
||||||
return tokens.map(({ tokenAddress }) => ({
|
|
||||||
contract: this.oracle,
|
|
||||||
name: 'getRateToEth',
|
|
||||||
params: [tokenAddress, true],
|
|
||||||
allowFailure: true,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
buildStable(stablecoinAddress: string): Call3[] {
|
|
||||||
const stablecoin = ERC20__factory.connect(stablecoinAddress, this.provider);
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
contract: stablecoin,
|
|
||||||
name: 'decimals',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
contract: this.oracle,
|
|
||||||
name: 'getRateToEth',
|
|
||||||
params: [stablecoin.target, true],
|
|
||||||
allowFailure: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchPrice(tokenAddress: string, decimals: number): Promise<bigint> {
|
|
||||||
// setup mock price for testnets
|
|
||||||
if (!this.oracle) {
|
|
||||||
return new Promise((resolve) => resolve(this.fallbackPrice));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
buildCalls(
|
||||||
const price = await this.oracle.getRateToEth(tokenAddress, true);
|
tokens: {
|
||||||
|
tokenAddress: string;
|
||||||
return (price * BigInt(10 ** decimals)) / BigInt(10 ** 18);
|
decimals: number;
|
||||||
} catch (err) {
|
}[],
|
||||||
console.log(`Failed to fetch oracle price for ${tokenAddress}, will use fallback price ${this.fallbackPrice}`);
|
): Call3[] {
|
||||||
console.log(err);
|
return tokens.map(({ tokenAddress }) => ({
|
||||||
return this.fallbackPrice;
|
contract: this.oracle,
|
||||||
}
|
name: 'getRateToEth',
|
||||||
}
|
params: [tokenAddress, true],
|
||||||
|
allowFailure: true,
|
||||||
async fetchPrices(
|
}));
|
||||||
tokens: {
|
|
||||||
tokenAddress: string;
|
|
||||||
decimals: number;
|
|
||||||
}[],
|
|
||||||
): Promise<bigint[]> {
|
|
||||||
// setup mock price for testnets
|
|
||||||
if (!this.oracle) {
|
|
||||||
return new Promise((resolve) => resolve(tokens.map(() => this.fallbackPrice)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const prices = (await multicall(this.multicall, this.buildCalls(tokens))) as (bigint | null)[];
|
buildStable(stablecoinAddress: string): Call3[] {
|
||||||
|
const stablecoin = ERC20__factory.connect(
|
||||||
|
stablecoinAddress,
|
||||||
|
this.provider,
|
||||||
|
);
|
||||||
|
|
||||||
return prices.map((price, index) => {
|
return [
|
||||||
if (!price) {
|
{
|
||||||
price = this.fallbackPrice;
|
contract: stablecoin,
|
||||||
}
|
name: 'decimals',
|
||||||
return (price * BigInt(10 ** tokens[index].decimals)) / BigInt(10 ** 18);
|
},
|
||||||
});
|
{
|
||||||
}
|
contract: this.oracle,
|
||||||
|
name: 'getRateToEth',
|
||||||
async fetchEthUSD(stablecoinAddress: string): Promise<number> {
|
params: [stablecoin.target, true],
|
||||||
// setup mock price for testnets
|
allowFailure: true,
|
||||||
if (!this.oracle) {
|
},
|
||||||
return new Promise((resolve) => resolve(10 ** 18 / Number(this.fallbackPrice)));
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const [decimals, price] = await multicall(this.multicall, this.buildStable(stablecoinAddress));
|
async fetchPrice(tokenAddress: string, decimals: number): Promise<bigint> {
|
||||||
|
// setup mock price for testnets
|
||||||
|
if (!this.oracle) {
|
||||||
|
return new Promise((resolve) => resolve(this.fallbackPrice));
|
||||||
|
}
|
||||||
|
|
||||||
// eth wei price of usdc token
|
try {
|
||||||
const ethPrice = ((price || this.fallbackPrice) * BigInt(10n ** decimals)) / BigInt(10 ** 18);
|
const price = await this.oracle.getRateToEth(tokenAddress, true);
|
||||||
|
|
||||||
return 1 / Number(formatEther(ethPrice));
|
return (price * BigInt(10 ** decimals)) / BigInt(10 ** 18);
|
||||||
}
|
} catch (err) {
|
||||||
|
console.log(
|
||||||
|
`Failed to fetch oracle price for ${tokenAddress}, will use fallback price ${this.fallbackPrice}`,
|
||||||
|
);
|
||||||
|
console.log(err);
|
||||||
|
return this.fallbackPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchPrices(
|
||||||
|
tokens: {
|
||||||
|
tokenAddress: string;
|
||||||
|
decimals: number;
|
||||||
|
}[],
|
||||||
|
): Promise<bigint[]> {
|
||||||
|
// setup mock price for testnets
|
||||||
|
if (!this.oracle) {
|
||||||
|
return new Promise((resolve) =>
|
||||||
|
resolve(tokens.map(() => this.fallbackPrice)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const prices = (await multicall(
|
||||||
|
this.multicall,
|
||||||
|
this.buildCalls(tokens),
|
||||||
|
)) as (bigint | null)[];
|
||||||
|
|
||||||
|
return prices.map((price, index) => {
|
||||||
|
if (!price) {
|
||||||
|
price = this.fallbackPrice;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
(price * BigInt(10 ** tokens[index].decimals)) /
|
||||||
|
BigInt(10 ** 18)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchEthUSD(stablecoinAddress: string): Promise<number> {
|
||||||
|
// setup mock price for testnets
|
||||||
|
if (!this.oracle) {
|
||||||
|
return new Promise((resolve) =>
|
||||||
|
resolve(10 ** 18 / Number(this.fallbackPrice)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [decimals, price] = await multicall(
|
||||||
|
this.multicall,
|
||||||
|
this.buildStable(stablecoinAddress),
|
||||||
|
);
|
||||||
|
|
||||||
|
// eth wei price of usdc token
|
||||||
|
const ethPrice =
|
||||||
|
((price || this.fallbackPrice) * BigInt(10n ** decimals)) /
|
||||||
|
BigInt(10 ** 18);
|
||||||
|
|
||||||
|
return 1 / Number(formatEther(ethPrice));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
886
src/providers.ts
886
src/providers.ts
@@ -2,470 +2,531 @@ import type { EventEmitter } from 'stream';
|
|||||||
import type { RequestOptions } from 'http';
|
import type { RequestOptions } from 'http';
|
||||||
import crossFetch from 'cross-fetch';
|
import crossFetch from 'cross-fetch';
|
||||||
import {
|
import {
|
||||||
FetchRequest,
|
FetchRequest,
|
||||||
JsonRpcApiProvider,
|
JsonRpcApiProvider,
|
||||||
JsonRpcProvider,
|
JsonRpcProvider,
|
||||||
Wallet,
|
Wallet,
|
||||||
HDNodeWallet,
|
HDNodeWallet,
|
||||||
FetchGetUrlFunc,
|
FetchGetUrlFunc,
|
||||||
Provider,
|
Provider,
|
||||||
SigningKey,
|
SigningKey,
|
||||||
TransactionRequest,
|
TransactionRequest,
|
||||||
JsonRpcSigner,
|
JsonRpcSigner,
|
||||||
BrowserProvider,
|
BrowserProvider,
|
||||||
Networkish,
|
Networkish,
|
||||||
Eip1193Provider,
|
Eip1193Provider,
|
||||||
VoidSigner,
|
VoidSigner,
|
||||||
Network,
|
Network,
|
||||||
EnsPlugin,
|
EnsPlugin,
|
||||||
GasCostPlugin,
|
GasCostPlugin,
|
||||||
} from 'ethers';
|
} from 'ethers';
|
||||||
import type { RequestInfo, RequestInit, Response, HeadersInit } from 'node-fetch';
|
import type {
|
||||||
|
RequestInfo,
|
||||||
|
RequestInit,
|
||||||
|
Response,
|
||||||
|
HeadersInit,
|
||||||
|
} from 'node-fetch';
|
||||||
// Temporary workaround until @types/node-fetch is compatible with @types/node
|
// Temporary workaround until @types/node-fetch is compatible with @types/node
|
||||||
import type { AbortSignal as FetchAbortSignal } from 'node-fetch/externals';
|
import type { AbortSignal as FetchAbortSignal } from 'node-fetch/externals';
|
||||||
import { isNode, sleep } from './utils';
|
import { isNode, sleep } from './utils';
|
||||||
import type { Config, NetIdType } from './networkConfig';
|
import type { Config, NetIdType } from './networkConfig';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
ethereum?: Eip1193Provider & EventEmitter;
|
ethereum?: Eip1193Provider & EventEmitter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update this for every Tor Browser release
|
// Update this for every Tor Browser release
|
||||||
export const defaultUserAgent = 'Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/115.0';
|
export const defaultUserAgent =
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/115.0';
|
||||||
|
|
||||||
export const fetch = crossFetch as unknown as nodeFetch;
|
export const fetch = crossFetch as unknown as nodeFetch;
|
||||||
|
|
||||||
export type nodeFetch = (url: RequestInfo, init?: RequestInit) => Promise<Response>;
|
export type nodeFetch = (
|
||||||
|
url: RequestInfo,
|
||||||
|
init?: RequestInit,
|
||||||
|
) => Promise<Response>;
|
||||||
|
|
||||||
export type fetchDataOptions = RequestInit & {
|
export type fetchDataOptions = RequestInit & {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
headers?: HeadersInit | any;
|
headers?: HeadersInit | any;
|
||||||
maxRetry?: number;
|
maxRetry?: number;
|
||||||
retryOn?: number;
|
retryOn?: number;
|
||||||
userAgent?: string;
|
userAgent?: string;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
proxy?: string;
|
proxy?: string;
|
||||||
torPort?: number;
|
torPort?: number;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
debug?: Function;
|
debug?: Function;
|
||||||
returnResponse?: boolean;
|
returnResponse?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeAgent = RequestOptions['agent'] | ((parsedUrl: URL) => RequestOptions['agent']);
|
export type NodeAgent =
|
||||||
|
| RequestOptions['agent']
|
||||||
|
| ((parsedUrl: URL) => RequestOptions['agent']);
|
||||||
|
|
||||||
export function getHttpAgent({
|
export function getHttpAgent({
|
||||||
fetchUrl,
|
fetchUrl,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
torPort,
|
torPort,
|
||||||
retry,
|
retry,
|
||||||
}: {
|
}: {
|
||||||
fetchUrl: string;
|
fetchUrl: string;
|
||||||
proxyUrl?: string;
|
proxyUrl?: string;
|
||||||
torPort?: number;
|
torPort?: number;
|
||||||
retry: number;
|
retry: number;
|
||||||
}): NodeAgent | undefined {
|
}): NodeAgent | undefined {
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
const { HttpProxyAgent } = require('http-proxy-agent');
|
const { HttpProxyAgent } = require('http-proxy-agent');
|
||||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||||
const { SocksProxyAgent } = require('socks-proxy-agent');
|
const { SocksProxyAgent } = require('socks-proxy-agent');
|
||||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||||
|
|
||||||
if (torPort) {
|
if (torPort) {
|
||||||
return new SocksProxyAgent(`socks5h://tor${retry}@127.0.0.1:${torPort}`);
|
return new SocksProxyAgent(
|
||||||
}
|
`socks5h://tor${retry}@127.0.0.1:${torPort}`,
|
||||||
|
);
|
||||||
if (!proxyUrl) {
|
}
|
||||||
return;
|
|
||||||
}
|
if (!proxyUrl) {
|
||||||
|
return;
|
||||||
const isHttps = fetchUrl.includes('https://');
|
}
|
||||||
|
|
||||||
if (proxyUrl.includes('socks://') || proxyUrl.includes('socks4://') || proxyUrl.includes('socks5://')) {
|
const isHttps = fetchUrl.includes('https://');
|
||||||
return new SocksProxyAgent(proxyUrl);
|
|
||||||
}
|
if (
|
||||||
|
proxyUrl.includes('socks://') ||
|
||||||
if (proxyUrl.includes('http://') || proxyUrl.includes('https://')) {
|
proxyUrl.includes('socks4://') ||
|
||||||
if (isHttps) {
|
proxyUrl.includes('socks5://')
|
||||||
return new HttpsProxyAgent(proxyUrl);
|
) {
|
||||||
|
return new SocksProxyAgent(proxyUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proxyUrl.includes('http://') || proxyUrl.includes('https://')) {
|
||||||
|
if (isHttps) {
|
||||||
|
return new HttpsProxyAgent(proxyUrl);
|
||||||
|
}
|
||||||
|
return new HttpProxyAgent(proxyUrl);
|
||||||
}
|
}
|
||||||
return new HttpProxyAgent(proxyUrl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchData(url: string, options: fetchDataOptions = {}) {
|
export async function fetchData(url: string, options: fetchDataOptions = {}) {
|
||||||
const MAX_RETRY = options.maxRetry ?? 3;
|
const MAX_RETRY = options.maxRetry ?? 3;
|
||||||
const RETRY_ON = options.retryOn ?? 500;
|
const RETRY_ON = options.retryOn ?? 500;
|
||||||
const userAgent = options.userAgent ?? defaultUserAgent;
|
const userAgent = options.userAgent ?? defaultUserAgent;
|
||||||
|
|
||||||
let retry = 0;
|
let retry = 0;
|
||||||
let errorObject;
|
let errorObject;
|
||||||
|
|
||||||
if (!options.method) {
|
if (!options.method) {
|
||||||
if (!options.body) {
|
if (!options.body) {
|
||||||
options.method = 'GET';
|
options.method = 'GET';
|
||||||
} else {
|
} else {
|
||||||
options.method = 'POST';
|
options.method = 'POST';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.headers) {
|
|
||||||
options.headers = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNode && !options.headers['User-Agent']) {
|
|
||||||
options.headers['User-Agent'] = userAgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (retry < MAX_RETRY + 1) {
|
|
||||||
let timeout;
|
|
||||||
|
|
||||||
// Define promise timeout when the options.timeout is available
|
|
||||||
if (!options.signal && options.timeout) {
|
|
||||||
const controller = new AbortController();
|
|
||||||
|
|
||||||
// Temporary workaround until @types/node-fetch is compatible with @types/node
|
|
||||||
options.signal = controller.signal as FetchAbortSignal;
|
|
||||||
|
|
||||||
// Define timeout in seconds
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
controller.abort();
|
|
||||||
}, options.timeout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.agent && isNode && (options.proxy || options.torPort)) {
|
if (!options.headers) {
|
||||||
options.agent = getHttpAgent({
|
options.headers = {};
|
||||||
fetchUrl: url,
|
}
|
||||||
proxyUrl: options.proxy,
|
|
||||||
torPort: options.torPort,
|
if (isNode && !options.headers['User-Agent']) {
|
||||||
retry,
|
options.headers['User-Agent'] = userAgent;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
while (retry < MAX_RETRY + 1) {
|
||||||
|
let timeout;
|
||||||
|
|
||||||
|
// Define promise timeout when the options.timeout is available
|
||||||
|
if (!options.signal && options.timeout) {
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
|
// Temporary workaround until @types/node-fetch is compatible with @types/node
|
||||||
|
options.signal = controller.signal as FetchAbortSignal;
|
||||||
|
|
||||||
|
// Define timeout in seconds
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
controller.abort();
|
||||||
|
}, options.timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.agent && isNode && (options.proxy || options.torPort)) {
|
||||||
|
options.agent = getHttpAgent({
|
||||||
|
fetchUrl: url,
|
||||||
|
proxyUrl: options.proxy,
|
||||||
|
torPort: options.torPort,
|
||||||
|
retry,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.debug && typeof options.debug === 'function') {
|
||||||
|
options.debug('request', {
|
||||||
|
url,
|
||||||
|
retry,
|
||||||
|
errorObject,
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch(url, {
|
||||||
|
method: options.method,
|
||||||
|
headers: options.headers,
|
||||||
|
body: options.body,
|
||||||
|
redirect: options.redirect,
|
||||||
|
signal: options.signal,
|
||||||
|
agent: options.agent,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.debug && typeof options.debug === 'function') {
|
||||||
|
options.debug('response', resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resp.ok) {
|
||||||
|
const errMsg =
|
||||||
|
`Request to ${url} failed with error code ${resp.status}:\n` +
|
||||||
|
(await resp.text());
|
||||||
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.returnResponse) {
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = resp.headers.get('content-type');
|
||||||
|
|
||||||
|
// If server returns JSON object, parse it and return as an object
|
||||||
|
if (contentType?.includes('application/json')) {
|
||||||
|
return await resp.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else if the server returns text parse it as a string
|
||||||
|
if (contentType?.includes('text')) {
|
||||||
|
return await resp.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return as a response object https://developer.mozilla.org/en-US/docs/Web/API/Response
|
||||||
|
return resp;
|
||||||
|
} catch (error) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorObject = error;
|
||||||
|
|
||||||
|
retry++;
|
||||||
|
|
||||||
|
await sleep(RETRY_ON);
|
||||||
|
} finally {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.debug && typeof options.debug === 'function') {
|
if (options.debug && typeof options.debug === 'function') {
|
||||||
options.debug('request', {
|
options.debug('error', errorObject);
|
||||||
url,
|
|
||||||
retry,
|
|
||||||
errorObject,
|
|
||||||
options,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
throw errorObject;
|
||||||
const resp = await fetch(url, {
|
|
||||||
method: options.method,
|
|
||||||
headers: options.headers,
|
|
||||||
body: options.body,
|
|
||||||
redirect: options.redirect,
|
|
||||||
signal: options.signal,
|
|
||||||
agent: options.agent,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (options.debug && typeof options.debug === 'function') {
|
|
||||||
options.debug('response', resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!resp.ok) {
|
|
||||||
const errMsg = `Request to ${url} failed with error code ${resp.status}:\n` + (await resp.text());
|
|
||||||
throw new Error(errMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.returnResponse) {
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentType = resp.headers.get('content-type');
|
|
||||||
|
|
||||||
// If server returns JSON object, parse it and return as an object
|
|
||||||
if (contentType?.includes('application/json')) {
|
|
||||||
return await resp.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Else if the server returns text parse it as a string
|
|
||||||
if (contentType?.includes('text')) {
|
|
||||||
return await resp.text();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return as a response object https://developer.mozilla.org/en-US/docs/Web/API/Response
|
|
||||||
return resp;
|
|
||||||
} catch (error) {
|
|
||||||
if (timeout) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorObject = error;
|
|
||||||
|
|
||||||
retry++;
|
|
||||||
|
|
||||||
await sleep(RETRY_ON);
|
|
||||||
} finally {
|
|
||||||
if (timeout) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.debug && typeof options.debug === 'function') {
|
|
||||||
options.debug('error', errorObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw errorObject;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
export const fetchGetUrlFunc =
|
export const fetchGetUrlFunc =
|
||||||
(options: fetchDataOptions = {}): FetchGetUrlFunc =>
|
(options: fetchDataOptions = {}): FetchGetUrlFunc =>
|
||||||
async (req, _signal) => {
|
async (req, _signal) => {
|
||||||
let signal;
|
let signal;
|
||||||
|
|
||||||
if (_signal) {
|
if (_signal) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
// Temporary workaround until @types/node-fetch is compatible with @types/node
|
// Temporary workaround until @types/node-fetch is compatible with @types/node
|
||||||
signal = controller.signal as FetchAbortSignal;
|
signal = controller.signal as FetchAbortSignal;
|
||||||
_signal.addListener(() => {
|
_signal.addListener(() => {
|
||||||
controller.abort();
|
controller.abort();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const init = {
|
const init = {
|
||||||
...options,
|
...options,
|
||||||
method: req.method || 'POST',
|
method: req.method || 'POST',
|
||||||
headers: req.headers,
|
headers: req.headers,
|
||||||
body: req.body || undefined,
|
body: req.body || undefined,
|
||||||
signal,
|
signal,
|
||||||
returnResponse: true,
|
returnResponse: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const resp = await fetchData(req.url, init);
|
||||||
|
|
||||||
|
const headers = {} as { [key in string]: any };
|
||||||
|
resp.headers.forEach((value: any, key: string) => {
|
||||||
|
headers[key.toLowerCase()] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const respBody = await resp.arrayBuffer();
|
||||||
|
const body = respBody == null ? null : new Uint8Array(respBody);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: resp.status,
|
||||||
|
statusMessage: resp.statusText,
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const resp = await fetchData(req.url, init);
|
|
||||||
|
|
||||||
const headers = {} as { [key in string]: any };
|
|
||||||
resp.headers.forEach((value: any, key: string) => {
|
|
||||||
headers[key.toLowerCase()] = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
const respBody = await resp.arrayBuffer();
|
|
||||||
const body = respBody == null ? null : new Uint8Array(respBody);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: resp.status,
|
|
||||||
statusMessage: resp.statusText,
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
export type getProviderOptions = fetchDataOptions & {
|
export type getProviderOptions = fetchDataOptions & {
|
||||||
// NetId to check against rpc
|
// NetId to check against rpc
|
||||||
netId?: NetIdType;
|
netId?: NetIdType;
|
||||||
pollingInterval?: number;
|
pollingInterval?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getProvider(rpcUrl: string, fetchOptions?: getProviderOptions): Promise<JsonRpcProvider> {
|
export async function getProvider(
|
||||||
const fetchReq = new FetchRequest(rpcUrl);
|
rpcUrl: string,
|
||||||
|
fetchOptions?: getProviderOptions,
|
||||||
|
): Promise<JsonRpcProvider> {
|
||||||
|
const fetchReq = new FetchRequest(rpcUrl);
|
||||||
|
|
||||||
fetchReq.getUrlFunc = fetchGetUrlFunc(fetchOptions);
|
fetchReq.getUrlFunc = fetchGetUrlFunc(fetchOptions);
|
||||||
|
|
||||||
const staticNetwork = await new JsonRpcProvider(fetchReq).getNetwork();
|
const staticNetwork = await new JsonRpcProvider(fetchReq).getNetwork();
|
||||||
|
|
||||||
const chainId = Number(staticNetwork.chainId);
|
const chainId = Number(staticNetwork.chainId);
|
||||||
|
|
||||||
if (fetchOptions?.netId && fetchOptions.netId !== chainId) {
|
if (fetchOptions?.netId && fetchOptions.netId !== chainId) {
|
||||||
const errMsg = `Wrong network for ${rpcUrl}, wants ${fetchOptions.netId} got ${chainId}`;
|
const errMsg = `Wrong network for ${rpcUrl}, wants ${fetchOptions.netId} got ${chainId}`;
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JsonRpcProvider(fetchReq, staticNetwork, {
|
return new JsonRpcProvider(fetchReq, staticNetwork, {
|
||||||
staticNetwork,
|
staticNetwork,
|
||||||
pollingInterval: fetchOptions?.pollingInterval || 1000,
|
pollingInterval: fetchOptions?.pollingInterval || 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProviderWithNetId(
|
export function getProviderWithNetId(
|
||||||
netId: NetIdType,
|
netId: NetIdType,
|
||||||
rpcUrl: string,
|
rpcUrl: string,
|
||||||
config: Config,
|
config: Config,
|
||||||
fetchOptions?: getProviderOptions,
|
fetchOptions?: getProviderOptions,
|
||||||
): JsonRpcProvider {
|
): JsonRpcProvider {
|
||||||
const { networkName, reverseRecordsContract, pollInterval } = config;
|
const { networkName, reverseRecordsContract, pollInterval } = config;
|
||||||
const hasEns = Boolean(reverseRecordsContract);
|
const hasEns = Boolean(reverseRecordsContract);
|
||||||
|
|
||||||
const fetchReq = new FetchRequest(rpcUrl);
|
const fetchReq = new FetchRequest(rpcUrl);
|
||||||
fetchReq.getUrlFunc = fetchGetUrlFunc(fetchOptions);
|
fetchReq.getUrlFunc = fetchGetUrlFunc(fetchOptions);
|
||||||
const staticNetwork = new Network(networkName, netId);
|
const staticNetwork = new Network(networkName, netId);
|
||||||
if (hasEns) {
|
if (hasEns) {
|
||||||
staticNetwork.attachPlugin(new EnsPlugin(null, Number(netId)));
|
staticNetwork.attachPlugin(new EnsPlugin(null, Number(netId)));
|
||||||
}
|
}
|
||||||
staticNetwork.attachPlugin(new GasCostPlugin());
|
staticNetwork.attachPlugin(new GasCostPlugin());
|
||||||
|
|
||||||
const provider = new JsonRpcProvider(fetchReq, staticNetwork, {
|
const provider = new JsonRpcProvider(fetchReq, staticNetwork, {
|
||||||
staticNetwork,
|
staticNetwork,
|
||||||
pollingInterval: fetchOptions?.pollingInterval || pollInterval * 1000,
|
pollingInterval: fetchOptions?.pollingInterval || pollInterval * 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const populateTransaction = async (
|
export const populateTransaction = async (
|
||||||
signer: TornadoWallet | TornadoVoidSigner | TornadoRpcSigner,
|
signer: TornadoWallet | TornadoVoidSigner | TornadoRpcSigner,
|
||||||
tx: TransactionRequest,
|
tx: TransactionRequest,
|
||||||
) => {
|
) => {
|
||||||
const provider = signer.provider as Provider;
|
const provider = signer.provider as Provider;
|
||||||
|
|
||||||
if (!tx.from) {
|
if (!tx.from) {
|
||||||
tx.from = signer.address;
|
tx.from = signer.address;
|
||||||
} else if (tx.from !== signer.address) {
|
} else if (tx.from !== signer.address) {
|
||||||
const errMsg = `populateTransaction: signer mismatch for tx, wants ${tx.from} have ${signer.address}`;
|
const errMsg = `populateTransaction: signer mismatch for tx, wants ${tx.from} have ${signer.address}`;
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
|
||||||
|
|
||||||
const [feeData, nonce] = await Promise.all([
|
|
||||||
tx.maxFeePerGas || tx.gasPrice ? undefined : provider.getFeeData(),
|
|
||||||
tx.nonce ? undefined : provider.getTransactionCount(signer.address, 'pending'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (feeData) {
|
|
||||||
// EIP-1559
|
|
||||||
if (feeData.maxFeePerGas) {
|
|
||||||
if (!tx.type) {
|
|
||||||
tx.type = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.maxFeePerGas = (feeData.maxFeePerGas * (BigInt(10000) + BigInt(signer.gasPriceBump))) / BigInt(10000);
|
|
||||||
tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
|
|
||||||
delete tx.gasPrice;
|
|
||||||
} else if (feeData.gasPrice) {
|
|
||||||
if (!tx.type) {
|
|
||||||
tx.type = 0;
|
|
||||||
}
|
|
||||||
tx.gasPrice = feeData.gasPrice;
|
|
||||||
delete tx.maxFeePerGas;
|
|
||||||
delete tx.maxPriorityFeePerGas;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (nonce) {
|
const [feeData, nonce] = await Promise.all([
|
||||||
tx.nonce = nonce;
|
tx.maxFeePerGas || tx.gasPrice ? undefined : provider.getFeeData(),
|
||||||
}
|
tx.nonce
|
||||||
|
? undefined
|
||||||
|
: provider.getTransactionCount(signer.address, 'pending'),
|
||||||
|
]);
|
||||||
|
|
||||||
if (!tx.gasLimit) {
|
if (feeData) {
|
||||||
try {
|
// EIP-1559
|
||||||
const gasLimit = await provider.estimateGas(tx);
|
if (feeData.maxFeePerGas) {
|
||||||
|
if (!tx.type) {
|
||||||
|
tx.type = 2;
|
||||||
|
}
|
||||||
|
|
||||||
tx.gasLimit =
|
tx.maxFeePerGas =
|
||||||
gasLimit === BigInt(21000)
|
(feeData.maxFeePerGas *
|
||||||
? gasLimit
|
(BigInt(10000) + BigInt(signer.gasPriceBump))) /
|
||||||
: (gasLimit * (BigInt(10000) + BigInt(signer.gasLimitBump))) / BigInt(10000);
|
BigInt(10000);
|
||||||
} catch (error) {
|
tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
|
||||||
if (signer.gasFailover) {
|
delete tx.gasPrice;
|
||||||
console.log('populateTransaction: warning gas estimation failed falling back to 3M gas');
|
} else if (feeData.gasPrice) {
|
||||||
// Gas failover
|
if (!tx.type) {
|
||||||
tx.gasLimit = BigInt('3000000');
|
tx.type = 0;
|
||||||
} else {
|
}
|
||||||
throw error;
|
tx.gasPrice = feeData.gasPrice;
|
||||||
}
|
delete tx.maxFeePerGas;
|
||||||
|
delete tx.maxPriorityFeePerGas;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return tx;
|
if (nonce) {
|
||||||
|
tx.nonce = nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tx.gasLimit) {
|
||||||
|
try {
|
||||||
|
const gasLimit = await provider.estimateGas(tx);
|
||||||
|
|
||||||
|
tx.gasLimit =
|
||||||
|
gasLimit === BigInt(21000)
|
||||||
|
? gasLimit
|
||||||
|
: (gasLimit *
|
||||||
|
(BigInt(10000) + BigInt(signer.gasLimitBump))) /
|
||||||
|
BigInt(10000);
|
||||||
|
} catch (error) {
|
||||||
|
if (signer.gasFailover) {
|
||||||
|
console.log(
|
||||||
|
'populateTransaction: warning gas estimation failed falling back to 3M gas',
|
||||||
|
);
|
||||||
|
// Gas failover
|
||||||
|
tx.gasLimit = BigInt('3000000');
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface TornadoWalletOptions {
|
export interface TornadoWalletOptions {
|
||||||
gasPriceBump?: number;
|
gasPriceBump?: number;
|
||||||
gasLimitBump?: number;
|
gasLimitBump?: number;
|
||||||
gasFailover?: boolean;
|
gasFailover?: boolean;
|
||||||
bumpNonce?: boolean;
|
bumpNonce?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TornadoWallet extends Wallet {
|
export class TornadoWallet extends Wallet {
|
||||||
nonce?: number;
|
nonce?: number;
|
||||||
gasPriceBump: number;
|
gasPriceBump: number;
|
||||||
gasLimitBump: number;
|
gasLimitBump: number;
|
||||||
gasFailover: boolean;
|
gasFailover: boolean;
|
||||||
bumpNonce: boolean;
|
bumpNonce: boolean;
|
||||||
constructor(
|
constructor(
|
||||||
key: string | SigningKey,
|
key: string | SigningKey,
|
||||||
provider?: Provider,
|
provider?: Provider,
|
||||||
{ gasPriceBump, gasLimitBump, gasFailover, bumpNonce }: TornadoWalletOptions = {},
|
{
|
||||||
) {
|
gasPriceBump,
|
||||||
super(key, provider);
|
gasLimitBump,
|
||||||
// 10% bump from the recommended fee
|
gasFailover,
|
||||||
this.gasPriceBump = gasPriceBump ?? 0;
|
bumpNonce,
|
||||||
// 30% bump from the recommended gaslimit
|
}: TornadoWalletOptions = {},
|
||||||
this.gasLimitBump = gasLimitBump ?? 3000;
|
) {
|
||||||
this.gasFailover = gasFailover ?? false;
|
super(key, provider);
|
||||||
// Disable bump nonce feature unless being used by the server environment
|
// 10% bump from the recommended fee
|
||||||
this.bumpNonce = bumpNonce ?? false;
|
this.gasPriceBump = gasPriceBump ?? 0;
|
||||||
}
|
// 30% bump from the recommended gaslimit
|
||||||
|
this.gasLimitBump = gasLimitBump ?? 3000;
|
||||||
|
this.gasFailover = gasFailover ?? false;
|
||||||
|
// Disable bump nonce feature unless being used by the server environment
|
||||||
|
this.bumpNonce = bumpNonce ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
static fromMnemonic(mneomnic: string, provider: Provider, index = 0, options?: TornadoWalletOptions) {
|
static fromMnemonic(
|
||||||
const defaultPath = `m/44'/60'/0'/0/${index}`;
|
mneomnic: string,
|
||||||
const { privateKey } = HDNodeWallet.fromPhrase(mneomnic, undefined, defaultPath);
|
provider: Provider,
|
||||||
return new TornadoWallet(privateKey as unknown as SigningKey, provider, options);
|
index = 0,
|
||||||
}
|
options?: TornadoWalletOptions,
|
||||||
|
) {
|
||||||
|
const defaultPath = `m/44'/60'/0'/0/${index}`;
|
||||||
|
const { privateKey } = HDNodeWallet.fromPhrase(
|
||||||
|
mneomnic,
|
||||||
|
undefined,
|
||||||
|
defaultPath,
|
||||||
|
);
|
||||||
|
return new TornadoWallet(
|
||||||
|
privateKey as unknown as SigningKey,
|
||||||
|
provider,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async populateTransaction(tx: TransactionRequest) {
|
async populateTransaction(tx: TransactionRequest) {
|
||||||
const txObject = await populateTransaction(this, tx);
|
const txObject = await populateTransaction(this, tx);
|
||||||
this.nonce = Number(txObject.nonce);
|
this.nonce = Number(txObject.nonce);
|
||||||
|
|
||||||
return super.populateTransaction(txObject);
|
return super.populateTransaction(txObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TornadoVoidSigner extends VoidSigner {
|
export class TornadoVoidSigner extends VoidSigner {
|
||||||
nonce?: number;
|
nonce?: number;
|
||||||
gasPriceBump: number;
|
gasPriceBump: number;
|
||||||
gasLimitBump: number;
|
gasLimitBump: number;
|
||||||
gasFailover: boolean;
|
gasFailover: boolean;
|
||||||
bumpNonce: boolean;
|
bumpNonce: boolean;
|
||||||
constructor(
|
constructor(
|
||||||
address: string,
|
address: string,
|
||||||
provider?: Provider,
|
provider?: Provider,
|
||||||
{ gasPriceBump, gasLimitBump, gasFailover, bumpNonce }: TornadoWalletOptions = {},
|
{
|
||||||
) {
|
gasPriceBump,
|
||||||
super(address, provider);
|
gasLimitBump,
|
||||||
// 10% bump from the recommended fee
|
gasFailover,
|
||||||
this.gasPriceBump = gasPriceBump ?? 0;
|
bumpNonce,
|
||||||
// 30% bump from the recommended gaslimit
|
}: TornadoWalletOptions = {},
|
||||||
this.gasLimitBump = gasLimitBump ?? 3000;
|
) {
|
||||||
this.gasFailover = gasFailover ?? false;
|
super(address, provider);
|
||||||
// turn off bumpNonce feature for view only wallet
|
// 10% bump from the recommended fee
|
||||||
this.bumpNonce = bumpNonce ?? false;
|
this.gasPriceBump = gasPriceBump ?? 0;
|
||||||
}
|
// 30% bump from the recommended gaslimit
|
||||||
|
this.gasLimitBump = gasLimitBump ?? 3000;
|
||||||
|
this.gasFailover = gasFailover ?? false;
|
||||||
|
// turn off bumpNonce feature for view only wallet
|
||||||
|
this.bumpNonce = bumpNonce ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
async populateTransaction(tx: TransactionRequest) {
|
async populateTransaction(tx: TransactionRequest) {
|
||||||
const txObject = await populateTransaction(this, tx);
|
const txObject = await populateTransaction(this, tx);
|
||||||
this.nonce = Number(txObject.nonce);
|
this.nonce = Number(txObject.nonce);
|
||||||
|
|
||||||
return super.populateTransaction(txObject);
|
return super.populateTransaction(txObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TornadoRpcSigner extends JsonRpcSigner {
|
export class TornadoRpcSigner extends JsonRpcSigner {
|
||||||
nonce?: number;
|
nonce?: number;
|
||||||
gasPriceBump: number;
|
gasPriceBump: number;
|
||||||
gasLimitBump: number;
|
gasLimitBump: number;
|
||||||
gasFailover: boolean;
|
gasFailover: boolean;
|
||||||
bumpNonce: boolean;
|
bumpNonce: boolean;
|
||||||
constructor(
|
constructor(
|
||||||
provider: JsonRpcApiProvider,
|
provider: JsonRpcApiProvider,
|
||||||
address: string,
|
address: string,
|
||||||
{ gasPriceBump, gasLimitBump, gasFailover, bumpNonce }: TornadoWalletOptions = {},
|
{
|
||||||
) {
|
gasPriceBump,
|
||||||
super(provider, address);
|
gasLimitBump,
|
||||||
// 10% bump from the recommended fee
|
gasFailover,
|
||||||
this.gasPriceBump = gasPriceBump ?? 0;
|
bumpNonce,
|
||||||
// 30% bump from the recommended gaslimit
|
}: TornadoWalletOptions = {},
|
||||||
this.gasLimitBump = gasLimitBump ?? 3000;
|
) {
|
||||||
this.gasFailover = gasFailover ?? false;
|
super(provider, address);
|
||||||
// turn off bumpNonce feature for browser wallet
|
// 10% bump from the recommended fee
|
||||||
this.bumpNonce = bumpNonce ?? false;
|
this.gasPriceBump = gasPriceBump ?? 0;
|
||||||
}
|
// 30% bump from the recommended gaslimit
|
||||||
|
this.gasLimitBump = gasLimitBump ?? 3000;
|
||||||
|
this.gasFailover = gasFailover ?? false;
|
||||||
|
// turn off bumpNonce feature for browser wallet
|
||||||
|
this.bumpNonce = bumpNonce ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
async sendUncheckedTransaction(tx: TransactionRequest) {
|
async sendUncheckedTransaction(tx: TransactionRequest) {
|
||||||
return super.sendUncheckedTransaction(await populateTransaction(this, tx));
|
return super.sendUncheckedTransaction(
|
||||||
}
|
await populateTransaction(this, tx),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
@@ -475,43 +536,56 @@ export type handleWalletFunc = (...args: any[]) => void;
|
|||||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
export interface TornadoBrowserProviderOptions extends TornadoWalletOptions {
|
export interface TornadoBrowserProviderOptions extends TornadoWalletOptions {
|
||||||
netId?: NetIdType;
|
netId?: NetIdType;
|
||||||
connectWallet?: connectWalletFunc;
|
connectWallet?: connectWalletFunc;
|
||||||
handleNetworkChanges?: handleWalletFunc;
|
handleNetworkChanges?: handleWalletFunc;
|
||||||
handleAccountChanges?: handleWalletFunc;
|
handleAccountChanges?: handleWalletFunc;
|
||||||
handleAccountDisconnect?: handleWalletFunc;
|
handleAccountDisconnect?: handleWalletFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TornadoBrowserProvider extends BrowserProvider {
|
export class TornadoBrowserProvider extends BrowserProvider {
|
||||||
options?: TornadoBrowserProviderOptions;
|
options?: TornadoBrowserProviderOptions;
|
||||||
constructor(ethereum: Eip1193Provider, network?: Networkish, options?: TornadoBrowserProviderOptions) {
|
constructor(
|
||||||
super(ethereum, network);
|
ethereum: Eip1193Provider,
|
||||||
this.options = options;
|
network?: Networkish,
|
||||||
}
|
options?: TornadoBrowserProviderOptions,
|
||||||
|
|
||||||
async getSigner(address: string): Promise<TornadoRpcSigner> {
|
|
||||||
const signerAddress = (await super.getSigner(address)).address;
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.options?.netId &&
|
|
||||||
this.options?.connectWallet &&
|
|
||||||
Number(await super.send('net_version', [])) !== this.options?.netId
|
|
||||||
) {
|
) {
|
||||||
await this.options.connectWallet(this.options?.netId);
|
super(ethereum, network);
|
||||||
|
this.options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options?.handleNetworkChanges) {
|
async getSigner(address: string): Promise<TornadoRpcSigner> {
|
||||||
window?.ethereum?.on('chainChanged', this.options.handleNetworkChanges);
|
const signerAddress = (await super.getSigner(address)).address;
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options?.handleAccountChanges) {
|
if (
|
||||||
window?.ethereum?.on('accountsChanged', this.options.handleAccountChanges);
|
this.options?.netId &&
|
||||||
}
|
this.options?.connectWallet &&
|
||||||
|
Number(await super.send('net_version', [])) !== this.options?.netId
|
||||||
|
) {
|
||||||
|
await this.options.connectWallet(this.options?.netId);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.options?.handleAccountDisconnect) {
|
if (this.options?.handleNetworkChanges) {
|
||||||
window?.ethereum?.on('disconnect', this.options.handleAccountDisconnect);
|
window?.ethereum?.on(
|
||||||
}
|
'chainChanged',
|
||||||
|
this.options.handleNetworkChanges,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return new TornadoRpcSigner(this, signerAddress, this.options);
|
if (this.options?.handleAccountChanges) {
|
||||||
}
|
window?.ethereum?.on(
|
||||||
|
'accountsChanged',
|
||||||
|
this.options.handleAccountChanges,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options?.handleAccountDisconnect) {
|
||||||
|
window?.ethereum?.on(
|
||||||
|
'disconnect',
|
||||||
|
this.options.handleAccountDisconnect,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TornadoRpcSigner(this, signerAddress, this.options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,88 +13,88 @@ export const MAX_FEE = 0.9;
|
|||||||
export const MIN_STAKE_BALANCE = parseEther('500');
|
export const MIN_STAKE_BALANCE = parseEther('500');
|
||||||
|
|
||||||
export interface RelayerParams {
|
export interface RelayerParams {
|
||||||
ensName: string;
|
ensName: string;
|
||||||
relayerAddress: string;
|
relayerAddress: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Info from relayer status
|
* Info from relayer status
|
||||||
*/
|
*/
|
||||||
export interface RelayerInfo extends RelayerParams {
|
export interface RelayerInfo extends RelayerParams {
|
||||||
netId: NetIdType;
|
netId: NetIdType;
|
||||||
url: string;
|
url: string;
|
||||||
hostname: string;
|
hostname: string;
|
||||||
rewardAccount: string;
|
rewardAccount: string;
|
||||||
instances: string[];
|
instances: string[];
|
||||||
stakeBalance?: string;
|
stakeBalance?: string;
|
||||||
gasPrice?: number;
|
gasPrice?: number;
|
||||||
ethPrices?: {
|
ethPrices?: {
|
||||||
[key in string]: string;
|
[key in string]: string;
|
||||||
};
|
};
|
||||||
currentQueue: number;
|
currentQueue: number;
|
||||||
tornadoServiceFee: number;
|
tornadoServiceFee: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RelayerError {
|
export interface RelayerError {
|
||||||
hostname: string;
|
hostname: string;
|
||||||
relayerAddress?: string;
|
relayerAddress?: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RelayerStatus {
|
export interface RelayerStatus {
|
||||||
url: string;
|
url: string;
|
||||||
rewardAccount: string;
|
rewardAccount: string;
|
||||||
instances: {
|
instances: {
|
||||||
[key in string]: {
|
[key in string]: {
|
||||||
instanceAddress: {
|
instanceAddress: {
|
||||||
[key in string]: string;
|
[key in string]: string;
|
||||||
};
|
};
|
||||||
tokenAddress?: string;
|
tokenAddress?: string;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
decimals: number;
|
decimals: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
gasPrices?: {
|
||||||
gasPrices?: {
|
fast: number;
|
||||||
fast: number;
|
additionalProperties?: number;
|
||||||
additionalProperties?: number;
|
};
|
||||||
};
|
netId: NetIdType;
|
||||||
netId: NetIdType;
|
ethPrices?: {
|
||||||
ethPrices?: {
|
[key in string]: string;
|
||||||
[key in string]: string;
|
};
|
||||||
};
|
tornadoServiceFee: number;
|
||||||
tornadoServiceFee: number;
|
latestBlock?: number;
|
||||||
latestBlock?: number;
|
version: string;
|
||||||
version: string;
|
health: {
|
||||||
health: {
|
status: string;
|
||||||
status: string;
|
error: string;
|
||||||
error: string;
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
errorsLog: any[];
|
||||||
errorsLog: any[];
|
};
|
||||||
};
|
currentQueue: number;
|
||||||
currentQueue: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TornadoWithdrawParams extends snarkProofs {
|
export interface TornadoWithdrawParams extends snarkProofs {
|
||||||
contract: string;
|
contract: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RelayerTornadoWithdraw {
|
export interface RelayerTornadoWithdraw {
|
||||||
id?: string;
|
id?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RelayerTornadoJobs {
|
export interface RelayerTornadoJobs {
|
||||||
error?: string;
|
error?: string;
|
||||||
id: string;
|
id: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
status: string;
|
status: string;
|
||||||
contract?: string;
|
contract?: string;
|
||||||
proof?: string;
|
proof?: string;
|
||||||
args?: string[];
|
args?: string[];
|
||||||
txHash?: string;
|
txHash?: string;
|
||||||
confirmations?: number;
|
confirmations?: number;
|
||||||
failedReason?: string;
|
failedReason?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -125,291 +125,327 @@ export function isRelayerUpdated(relayerVersion: string, netId: NetIdType) {
|
|||||||
}
|
}
|
||||||
**/
|
**/
|
||||||
|
|
||||||
export function calculateScore({ stakeBalance, tornadoServiceFee }: RelayerInfo) {
|
export function calculateScore({
|
||||||
if (tornadoServiceFee < MIN_FEE) {
|
stakeBalance,
|
||||||
tornadoServiceFee = MIN_FEE;
|
tornadoServiceFee,
|
||||||
} else if (tornadoServiceFee >= MAX_FEE) {
|
}: RelayerInfo) {
|
||||||
return BigInt(0);
|
if (tornadoServiceFee < MIN_FEE) {
|
||||||
}
|
tornadoServiceFee = MIN_FEE;
|
||||||
|
} else if (tornadoServiceFee >= MAX_FEE) {
|
||||||
|
return BigInt(0);
|
||||||
|
}
|
||||||
|
|
||||||
const serviceFeeCoefficient = (tornadoServiceFee - MIN_FEE) ** 2;
|
const serviceFeeCoefficient = (tornadoServiceFee - MIN_FEE) ** 2;
|
||||||
const feeDiffCoefficient = 1 / (MAX_FEE - MIN_FEE) ** 2;
|
const feeDiffCoefficient = 1 / (MAX_FEE - MIN_FEE) ** 2;
|
||||||
const coefficientsMultiplier = 1 - feeDiffCoefficient * serviceFeeCoefficient;
|
const coefficientsMultiplier =
|
||||||
|
1 - feeDiffCoefficient * serviceFeeCoefficient;
|
||||||
|
|
||||||
return BigInt(Math.floor(Number(stakeBalance || '0') * coefficientsMultiplier));
|
return BigInt(
|
||||||
|
Math.floor(Number(stakeBalance || '0') * coefficientsMultiplier),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWeightRandom(weightsScores: bigint[], random: bigint) {
|
export function getWeightRandom(weightsScores: bigint[], random: bigint) {
|
||||||
for (let i = 0; i < weightsScores.length; i++) {
|
for (let i = 0; i < weightsScores.length; i++) {
|
||||||
if (random < weightsScores[i]) {
|
if (random < weightsScores[i]) {
|
||||||
return i;
|
return i;
|
||||||
|
}
|
||||||
|
random = random - weightsScores[i];
|
||||||
}
|
}
|
||||||
random = random - weightsScores[i];
|
return Math.floor(Math.random() * weightsScores.length);
|
||||||
}
|
|
||||||
return Math.floor(Math.random() * weightsScores.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RelayerInstanceList {
|
export interface RelayerInstanceList {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
instanceAddress: {
|
instanceAddress: {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSupportedInstances(instanceList: RelayerInstanceList) {
|
export function getSupportedInstances(instanceList: RelayerInstanceList) {
|
||||||
const rawList = Object.values(instanceList)
|
const rawList = Object.values(instanceList)
|
||||||
.map(({ instanceAddress }) => {
|
.map(({ instanceAddress }) => {
|
||||||
return Object.values(instanceAddress);
|
return Object.values(instanceAddress);
|
||||||
})
|
})
|
||||||
.flat();
|
.flat();
|
||||||
|
|
||||||
return rawList.map((l) => getAddress(l));
|
return rawList.map((l) => getAddress(l));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pickWeightedRandomRelayer(relayers: RelayerInfo[]) {
|
export function pickWeightedRandomRelayer(relayers: RelayerInfo[]) {
|
||||||
const weightsScores = relayers.map((el) => calculateScore(el));
|
const weightsScores = relayers.map((el) => calculateScore(el));
|
||||||
const totalWeight = weightsScores.reduce((acc, curr) => {
|
const totalWeight = weightsScores.reduce((acc, curr) => {
|
||||||
return (acc = acc + curr);
|
return (acc = acc + curr);
|
||||||
}, BigInt('0'));
|
}, BigInt('0'));
|
||||||
|
|
||||||
const random = BigInt(Math.floor(Number(totalWeight) * Math.random()));
|
const random = BigInt(Math.floor(Number(totalWeight) * Math.random()));
|
||||||
const weightRandomIndex = getWeightRandom(weightsScores, random);
|
const weightRandomIndex = getWeightRandom(weightsScores, random);
|
||||||
|
|
||||||
return relayers[weightRandomIndex];
|
return relayers[weightRandomIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RelayerClientConstructor {
|
export interface RelayerClientConstructor {
|
||||||
netId: NetIdType;
|
netId: NetIdType;
|
||||||
config: Config;
|
config: Config;
|
||||||
fetchDataOptions?: fetchDataOptions;
|
fetchDataOptions?: fetchDataOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RelayerClient {
|
export class RelayerClient {
|
||||||
netId: NetIdType;
|
netId: NetIdType;
|
||||||
config: Config;
|
config: Config;
|
||||||
selectedRelayer?: RelayerInfo;
|
selectedRelayer?: RelayerInfo;
|
||||||
fetchDataOptions?: fetchDataOptions;
|
fetchDataOptions?: fetchDataOptions;
|
||||||
tovarish: boolean;
|
tovarish: boolean;
|
||||||
|
|
||||||
constructor({ netId, config, fetchDataOptions }: RelayerClientConstructor) {
|
constructor({ netId, config, fetchDataOptions }: RelayerClientConstructor) {
|
||||||
this.netId = netId;
|
this.netId = netId;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.fetchDataOptions = fetchDataOptions;
|
this.fetchDataOptions = fetchDataOptions;
|
||||||
this.tovarish = false;
|
this.tovarish = false;
|
||||||
}
|
|
||||||
|
|
||||||
async askRelayerStatus({
|
|
||||||
hostname,
|
|
||||||
url,
|
|
||||||
relayerAddress,
|
|
||||||
}: {
|
|
||||||
hostname?: string;
|
|
||||||
// optional url if entered manually
|
|
||||||
url?: string;
|
|
||||||
// relayerAddress from registry contract to prevent cheating
|
|
||||||
relayerAddress?: string;
|
|
||||||
}): Promise<RelayerStatus> {
|
|
||||||
if (!url && hostname) {
|
|
||||||
url = `https://${!hostname.endsWith('/') ? hostname + '/' : hostname}`;
|
|
||||||
} else if (url && !url.endsWith('/')) {
|
|
||||||
url += '/';
|
|
||||||
} else {
|
|
||||||
url = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawStatus = (await fetchData(`${url}status`, {
|
async askRelayerStatus({
|
||||||
...this.fetchDataOptions,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json, application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
timeout: 30000,
|
|
||||||
maxRetry: this.fetchDataOptions?.torPort ? 2 : 0,
|
|
||||||
})) as object;
|
|
||||||
|
|
||||||
const statusValidator = ajv.compile(getStatusSchema(this.netId, this.config, this.tovarish));
|
|
||||||
|
|
||||||
if (!statusValidator(rawStatus)) {
|
|
||||||
throw new Error('Invalid status schema');
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = {
|
|
||||||
...rawStatus,
|
|
||||||
url,
|
|
||||||
} as RelayerStatus;
|
|
||||||
|
|
||||||
if (status.currentQueue > 5) {
|
|
||||||
throw new Error('Withdrawal queue is overloaded');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.netId !== this.netId) {
|
|
||||||
throw new Error('This relayer serves a different network');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relayerAddress && this.netId === NetId.MAINNET && status.rewardAccount !== relayerAddress) {
|
|
||||||
throw new Error('The Relayer reward address must match registered address');
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
async filterRelayer(relayer: CachedRelayerInfo): Promise<RelayerInfo | RelayerError | undefined> {
|
|
||||||
const hostname = relayer.hostnames[this.netId];
|
|
||||||
const { ensName, relayerAddress } = relayer;
|
|
||||||
|
|
||||||
if (!hostname) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const status = await this.askRelayerStatus({ hostname, relayerAddress });
|
|
||||||
|
|
||||||
return {
|
|
||||||
netId: status.netId,
|
|
||||||
url: status.url,
|
|
||||||
hostname,
|
hostname,
|
||||||
ensName,
|
url,
|
||||||
relayerAddress,
|
relayerAddress,
|
||||||
rewardAccount: getAddress(status.rewardAccount),
|
}: {
|
||||||
instances: getSupportedInstances(status.instances),
|
hostname?: string;
|
||||||
stakeBalance: relayer.stakeBalance,
|
// optional url if entered manually
|
||||||
gasPrice: status.gasPrices?.fast,
|
url?: string;
|
||||||
ethPrices: status.ethPrices,
|
// relayerAddress from registry contract to prevent cheating
|
||||||
currentQueue: status.currentQueue,
|
relayerAddress?: string;
|
||||||
tornadoServiceFee: status.tornadoServiceFee,
|
}): Promise<RelayerStatus> {
|
||||||
} as RelayerInfo;
|
if (!url && hostname) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
url = `https://${!hostname.endsWith('/') ? hostname + '/' : hostname}`;
|
||||||
} catch (err: any) {
|
} else if (url && !url.endsWith('/')) {
|
||||||
return {
|
url += '/';
|
||||||
hostname,
|
|
||||||
relayerAddress,
|
|
||||||
errorMessage: err.message,
|
|
||||||
hasError: true,
|
|
||||||
} as RelayerError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getValidRelayers(relayers: CachedRelayerInfo[]): Promise<{
|
|
||||||
validRelayers: RelayerInfo[];
|
|
||||||
invalidRelayers: RelayerError[];
|
|
||||||
}> {
|
|
||||||
const invalidRelayers: RelayerError[] = [];
|
|
||||||
|
|
||||||
const validRelayers = (await Promise.all(relayers.map((relayer) => this.filterRelayer(relayer)))).filter((r) => {
|
|
||||||
if (!r) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ((r as RelayerError).hasError) {
|
|
||||||
invalidRelayers.push(r as RelayerError);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}) as RelayerInfo[];
|
|
||||||
|
|
||||||
return {
|
|
||||||
validRelayers,
|
|
||||||
invalidRelayers,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pickWeightedRandomRelayer(relayers: RelayerInfo[]) {
|
|
||||||
return pickWeightedRandomRelayer(relayers);
|
|
||||||
}
|
|
||||||
|
|
||||||
async tornadoWithdraw(
|
|
||||||
{ contract, proof, args }: TornadoWithdrawParams,
|
|
||||||
callback?: (jobResp: RelayerTornadoWithdraw | RelayerTornadoJobs) => void,
|
|
||||||
) {
|
|
||||||
const { url } = this.selectedRelayer as RelayerInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request new job
|
|
||||||
*/
|
|
||||||
|
|
||||||
const withdrawResponse = (await fetchData(`${url}v1/tornadoWithdraw`, {
|
|
||||||
...this.fetchDataOptions,
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
contract,
|
|
||||||
proof,
|
|
||||||
args,
|
|
||||||
}),
|
|
||||||
})) as RelayerTornadoWithdraw;
|
|
||||||
|
|
||||||
const { id, error } = withdrawResponse;
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const jobValidator = ajv.compile(jobRequestSchema);
|
|
||||||
|
|
||||||
if (!jobValidator(withdrawResponse)) {
|
|
||||||
const errMsg = `${url}v1/tornadoWithdraw has an invalid job response`;
|
|
||||||
throw new Error(errMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof callback === 'function') {
|
|
||||||
callback(withdrawResponse as unknown as RelayerTornadoWithdraw);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get job status
|
|
||||||
*/
|
|
||||||
|
|
||||||
let relayerStatus: string | undefined;
|
|
||||||
|
|
||||||
const jobUrl = `${url}v1/jobs/${id}`;
|
|
||||||
|
|
||||||
console.log(`Job submitted: ${jobUrl}\n`);
|
|
||||||
|
|
||||||
while (!relayerStatus || !['FAILED', 'CONFIRMED'].includes(relayerStatus)) {
|
|
||||||
const jobResponse = await fetchData(jobUrl, {
|
|
||||||
...this.fetchDataOptions,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (jobResponse.error) {
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const jobValidator = ajv.compile(jobsSchema);
|
|
||||||
|
|
||||||
if (!jobValidator(jobResponse)) {
|
|
||||||
const errMsg = `${jobUrl} has an invalid job response`;
|
|
||||||
throw new Error(errMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { status, txHash, confirmations, failedReason } = jobResponse as unknown as RelayerTornadoJobs;
|
|
||||||
|
|
||||||
if (relayerStatus !== status) {
|
|
||||||
if (status === 'FAILED') {
|
|
||||||
const errMsg = `Job ${status}: ${jobUrl} failed reason: ${failedReason}`;
|
|
||||||
throw new Error(errMsg);
|
|
||||||
} else if (status === 'SENT') {
|
|
||||||
console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}\n`);
|
|
||||||
} else if (status === 'MINED') {
|
|
||||||
console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations}\n`);
|
|
||||||
} else if (status === 'CONFIRMED') {
|
|
||||||
console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations}\n`);
|
|
||||||
} else {
|
} else {
|
||||||
console.log(`Job ${status}: ${jobUrl}\n`);
|
url = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
relayerStatus = status;
|
const rawStatus = (await fetchData(`${url}status`, {
|
||||||
|
...this.fetchDataOptions,
|
||||||
|
headers: {
|
||||||
|
'Content-Type':
|
||||||
|
'application/json, application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
timeout: 30000,
|
||||||
|
maxRetry: this.fetchDataOptions?.torPort ? 2 : 0,
|
||||||
|
})) as object;
|
||||||
|
|
||||||
|
const statusValidator = ajv.compile(
|
||||||
|
getStatusSchema(this.netId, this.config, this.tovarish),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!statusValidator(rawStatus)) {
|
||||||
|
throw new Error('Invalid status schema');
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = {
|
||||||
|
...rawStatus,
|
||||||
|
url,
|
||||||
|
} as RelayerStatus;
|
||||||
|
|
||||||
|
if (status.currentQueue > 5) {
|
||||||
|
throw new Error('Withdrawal queue is overloaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.netId !== this.netId) {
|
||||||
|
throw new Error('This relayer serves a different network');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
relayerAddress &&
|
||||||
|
this.netId === NetId.MAINNET &&
|
||||||
|
status.rewardAccount !== relayerAddress
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'The Relayer reward address must match registered address',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
async filterRelayer(
|
||||||
|
relayer: CachedRelayerInfo,
|
||||||
|
): Promise<RelayerInfo | RelayerError | undefined> {
|
||||||
|
const hostname = relayer.hostnames[this.netId];
|
||||||
|
const { ensName, relayerAddress } = relayer;
|
||||||
|
|
||||||
|
if (!hostname) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const status = await this.askRelayerStatus({
|
||||||
|
hostname,
|
||||||
|
relayerAddress,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
netId: status.netId,
|
||||||
|
url: status.url,
|
||||||
|
hostname,
|
||||||
|
ensName,
|
||||||
|
relayerAddress,
|
||||||
|
rewardAccount: getAddress(status.rewardAccount),
|
||||||
|
instances: getSupportedInstances(status.instances),
|
||||||
|
stakeBalance: relayer.stakeBalance,
|
||||||
|
gasPrice: status.gasPrices?.fast,
|
||||||
|
ethPrices: status.ethPrices,
|
||||||
|
currentQueue: status.currentQueue,
|
||||||
|
tornadoServiceFee: status.tornadoServiceFee,
|
||||||
|
} as RelayerInfo;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} catch (err: any) {
|
||||||
|
return {
|
||||||
|
hostname,
|
||||||
|
relayerAddress,
|
||||||
|
errorMessage: err.message,
|
||||||
|
hasError: true,
|
||||||
|
} as RelayerError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getValidRelayers(relayers: CachedRelayerInfo[]): Promise<{
|
||||||
|
validRelayers: RelayerInfo[];
|
||||||
|
invalidRelayers: RelayerError[];
|
||||||
|
}> {
|
||||||
|
const invalidRelayers: RelayerError[] = [];
|
||||||
|
|
||||||
|
const validRelayers = (
|
||||||
|
await Promise.all(
|
||||||
|
relayers.map((relayer) => this.filterRelayer(relayer)),
|
||||||
|
)
|
||||||
|
).filter((r) => {
|
||||||
|
if (!r) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((r as RelayerError).hasError) {
|
||||||
|
invalidRelayers.push(r as RelayerError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}) as RelayerInfo[];
|
||||||
|
|
||||||
|
return {
|
||||||
|
validRelayers,
|
||||||
|
invalidRelayers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pickWeightedRandomRelayer(relayers: RelayerInfo[]) {
|
||||||
|
return pickWeightedRandomRelayer(relayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
async tornadoWithdraw(
|
||||||
|
{ contract, proof, args }: TornadoWithdrawParams,
|
||||||
|
callback?: (
|
||||||
|
jobResp: RelayerTornadoWithdraw | RelayerTornadoJobs,
|
||||||
|
) => void,
|
||||||
|
) {
|
||||||
|
const { url } = this.selectedRelayer as RelayerInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request new job
|
||||||
|
*/
|
||||||
|
|
||||||
|
const withdrawResponse = (await fetchData(`${url}v1/tornadoWithdraw`, {
|
||||||
|
...this.fetchDataOptions,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
contract,
|
||||||
|
proof,
|
||||||
|
args,
|
||||||
|
}),
|
||||||
|
})) as RelayerTornadoWithdraw;
|
||||||
|
|
||||||
|
const { id, error } = withdrawResponse;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jobValidator = ajv.compile(jobRequestSchema);
|
||||||
|
|
||||||
|
if (!jobValidator(withdrawResponse)) {
|
||||||
|
const errMsg = `${url}v1/tornadoWithdraw has an invalid job response`;
|
||||||
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
callback(jobResponse as unknown as RelayerTornadoJobs);
|
callback(withdrawResponse as unknown as RelayerTornadoWithdraw);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await sleep(3000);
|
/**
|
||||||
|
* Get job status
|
||||||
|
*/
|
||||||
|
|
||||||
|
let relayerStatus: string | undefined;
|
||||||
|
|
||||||
|
const jobUrl = `${url}v1/jobs/${id}`;
|
||||||
|
|
||||||
|
console.log(`Job submitted: ${jobUrl}\n`);
|
||||||
|
|
||||||
|
while (
|
||||||
|
!relayerStatus ||
|
||||||
|
!['FAILED', 'CONFIRMED'].includes(relayerStatus)
|
||||||
|
) {
|
||||||
|
const jobResponse = await fetchData(jobUrl, {
|
||||||
|
...this.fetchDataOptions,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (jobResponse.error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jobValidator = ajv.compile(jobsSchema);
|
||||||
|
|
||||||
|
if (!jobValidator(jobResponse)) {
|
||||||
|
const errMsg = `${jobUrl} has an invalid job response`;
|
||||||
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { status, txHash, confirmations, failedReason } =
|
||||||
|
jobResponse as unknown as RelayerTornadoJobs;
|
||||||
|
|
||||||
|
if (relayerStatus !== status) {
|
||||||
|
if (status === 'FAILED') {
|
||||||
|
const errMsg = `Job ${status}: ${jobUrl} failed reason: ${failedReason}`;
|
||||||
|
throw new Error(errMsg);
|
||||||
|
} else if (status === 'SENT') {
|
||||||
|
console.log(
|
||||||
|
`Job ${status}: ${jobUrl}, txhash: ${txHash}\n`,
|
||||||
|
);
|
||||||
|
} else if (status === 'MINED') {
|
||||||
|
console.log(
|
||||||
|
`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations}\n`,
|
||||||
|
);
|
||||||
|
} else if (status === 'CONFIRMED') {
|
||||||
|
console.log(
|
||||||
|
`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations}\n`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(`Job ${status}: ${jobUrl}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
relayerStatus = status;
|
||||||
|
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback(jobResponse as unknown as RelayerTornadoJobs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(3000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,28 +4,28 @@ import { BigNumberish, isAddress } from 'ethers';
|
|||||||
export const ajv = new Ajv({ allErrors: true });
|
export const ajv = new Ajv({ allErrors: true });
|
||||||
|
|
||||||
ajv.addKeyword({
|
ajv.addKeyword({
|
||||||
keyword: 'BN',
|
keyword: 'BN',
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
validate: (schema: any, data: BigNumberish) => {
|
validate: (schema: any, data: BigNumberish) => {
|
||||||
try {
|
try {
|
||||||
BigInt(data);
|
BigInt(data);
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
errors: true,
|
errors: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
ajv.addKeyword({
|
ajv.addKeyword({
|
||||||
keyword: 'isAddress',
|
keyword: 'isAddress',
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
validate: (schema: any, data: string) => {
|
validate: (schema: any, data: string) => {
|
||||||
try {
|
try {
|
||||||
return isAddress(data);
|
return isAddress(data);
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
errors: true,
|
errors: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,183 +3,214 @@ import { ajv } from './ajv';
|
|||||||
import { addressSchemaType, bnSchemaType, bytes32SchemaType } from './types';
|
import { addressSchemaType, bnSchemaType, bytes32SchemaType } from './types';
|
||||||
|
|
||||||
const baseEventsSchemaProperty = {
|
const baseEventsSchemaProperty = {
|
||||||
blockNumber: {
|
blockNumber: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
logIndex: {
|
logIndex: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
transactionHash: bytes32SchemaType,
|
transactionHash: bytes32SchemaType,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const baseEventsSchemaRequired = Object.keys(baseEventsSchemaProperty) as string[];
|
const baseEventsSchemaRequired = Object.keys(
|
||||||
|
baseEventsSchemaProperty,
|
||||||
|
) as string[];
|
||||||
|
|
||||||
export const governanceEventsSchema = {
|
export const governanceEventsSchema = {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: {
|
items: {
|
||||||
anyOf: [
|
anyOf: [
|
||||||
{
|
{
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
...baseEventsSchemaProperty,
|
...baseEventsSchemaProperty,
|
||||||
event: { type: 'string' },
|
event: { type: 'string' },
|
||||||
id: { type: 'number' },
|
id: { type: 'number' },
|
||||||
proposer: addressSchemaType,
|
proposer: addressSchemaType,
|
||||||
target: addressSchemaType,
|
target: addressSchemaType,
|
||||||
startTime: { type: 'number' },
|
startTime: { type: 'number' },
|
||||||
endTime: { type: 'number' },
|
endTime: { type: 'number' },
|
||||||
description: { type: 'string' },
|
description: { type: 'string' },
|
||||||
},
|
},
|
||||||
required: [
|
required: [
|
||||||
...baseEventsSchemaRequired,
|
...baseEventsSchemaRequired,
|
||||||
'event',
|
'event',
|
||||||
'id',
|
'id',
|
||||||
'proposer',
|
'proposer',
|
||||||
'target',
|
'target',
|
||||||
'startTime',
|
'startTime',
|
||||||
'endTime',
|
'endTime',
|
||||||
'description',
|
'description',
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
...baseEventsSchemaProperty,
|
||||||
|
event: { type: 'string' },
|
||||||
|
proposalId: { type: 'number' },
|
||||||
|
voter: addressSchemaType,
|
||||||
|
support: { type: 'boolean' },
|
||||||
|
votes: { type: 'string' },
|
||||||
|
from: addressSchemaType,
|
||||||
|
input: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: [
|
||||||
|
...baseEventsSchemaRequired,
|
||||||
|
'event',
|
||||||
|
'proposalId',
|
||||||
|
'voter',
|
||||||
|
'support',
|
||||||
|
'votes',
|
||||||
|
'from',
|
||||||
|
'input',
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
...baseEventsSchemaProperty,
|
||||||
|
event: { type: 'string' },
|
||||||
|
account: addressSchemaType,
|
||||||
|
delegateTo: addressSchemaType,
|
||||||
|
},
|
||||||
|
required: [
|
||||||
|
...baseEventsSchemaRequired,
|
||||||
|
'account',
|
||||||
|
'delegateTo',
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
...baseEventsSchemaProperty,
|
||||||
|
event: { type: 'string' },
|
||||||
|
account: addressSchemaType,
|
||||||
|
delegateFrom: addressSchemaType,
|
||||||
|
},
|
||||||
|
required: [
|
||||||
|
...baseEventsSchemaRequired,
|
||||||
|
'account',
|
||||||
|
'delegateFrom',
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
additionalProperties: false,
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
...baseEventsSchemaProperty,
|
|
||||||
event: { type: 'string' },
|
|
||||||
proposalId: { type: 'number' },
|
|
||||||
voter: addressSchemaType,
|
|
||||||
support: { type: 'boolean' },
|
|
||||||
votes: { type: 'string' },
|
|
||||||
from: addressSchemaType,
|
|
||||||
input: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: [...baseEventsSchemaRequired, 'event', 'proposalId', 'voter', 'support', 'votes', 'from', 'input'],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
...baseEventsSchemaProperty,
|
|
||||||
event: { type: 'string' },
|
|
||||||
account: addressSchemaType,
|
|
||||||
delegateTo: addressSchemaType,
|
|
||||||
},
|
|
||||||
required: [...baseEventsSchemaRequired, 'account', 'delegateTo'],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
...baseEventsSchemaProperty,
|
|
||||||
event: { type: 'string' },
|
|
||||||
account: addressSchemaType,
|
|
||||||
delegateFrom: addressSchemaType,
|
|
||||||
},
|
|
||||||
required: [...baseEventsSchemaRequired, 'account', 'delegateFrom'],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const registeredEventsSchema = {
|
export const registeredEventsSchema = {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
...baseEventsSchemaProperty,
|
...baseEventsSchemaProperty,
|
||||||
ensName: { type: 'string' },
|
ensName: { type: 'string' },
|
||||||
relayerAddress: addressSchemaType,
|
relayerAddress: addressSchemaType,
|
||||||
|
},
|
||||||
|
required: [...baseEventsSchemaRequired, 'ensName', 'relayerAddress'],
|
||||||
|
additionalProperties: false,
|
||||||
},
|
},
|
||||||
required: [...baseEventsSchemaRequired, 'ensName', 'relayerAddress'],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const depositsEventsSchema = {
|
export const depositsEventsSchema = {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
...baseEventsSchemaProperty,
|
...baseEventsSchemaProperty,
|
||||||
commitment: bytes32SchemaType,
|
commitment: bytes32SchemaType,
|
||||||
leafIndex: { type: 'number' },
|
leafIndex: { type: 'number' },
|
||||||
timestamp: { type: 'number' },
|
timestamp: { type: 'number' },
|
||||||
from: addressSchemaType,
|
from: addressSchemaType,
|
||||||
|
},
|
||||||
|
required: [
|
||||||
|
...baseEventsSchemaRequired,
|
||||||
|
'commitment',
|
||||||
|
'leafIndex',
|
||||||
|
'timestamp',
|
||||||
|
'from',
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
},
|
},
|
||||||
required: [...baseEventsSchemaRequired, 'commitment', 'leafIndex', 'timestamp', 'from'],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const withdrawalsEventsSchema = {
|
export const withdrawalsEventsSchema = {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
...baseEventsSchemaProperty,
|
...baseEventsSchemaProperty,
|
||||||
nullifierHash: bytes32SchemaType,
|
nullifierHash: bytes32SchemaType,
|
||||||
to: addressSchemaType,
|
to: addressSchemaType,
|
||||||
fee: bnSchemaType,
|
fee: bnSchemaType,
|
||||||
timestamp: { type: 'number' },
|
timestamp: { type: 'number' },
|
||||||
|
},
|
||||||
|
required: [
|
||||||
|
...baseEventsSchemaRequired,
|
||||||
|
'nullifierHash',
|
||||||
|
'to',
|
||||||
|
'fee',
|
||||||
|
'timestamp',
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
},
|
},
|
||||||
required: [...baseEventsSchemaRequired, 'nullifierHash', 'to', 'fee', 'timestamp'],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const echoEventsSchema = {
|
export const echoEventsSchema = {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
...baseEventsSchemaProperty,
|
...baseEventsSchemaProperty,
|
||||||
address: addressSchemaType,
|
address: addressSchemaType,
|
||||||
encryptedAccount: { type: 'string' },
|
encryptedAccount: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: [...baseEventsSchemaRequired, 'address', 'encryptedAccount'],
|
||||||
|
additionalProperties: false,
|
||||||
},
|
},
|
||||||
required: [...baseEventsSchemaRequired, 'address', 'encryptedAccount'],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const encryptedNotesSchema = {
|
export const encryptedNotesSchema = {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
...baseEventsSchemaProperty,
|
...baseEventsSchemaProperty,
|
||||||
encryptedNote: { type: 'string' },
|
encryptedNote: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: [...baseEventsSchemaRequired, 'encryptedNote'],
|
||||||
|
additionalProperties: false,
|
||||||
},
|
},
|
||||||
required: [...baseEventsSchemaRequired, 'encryptedNote'],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export function getEventsSchemaValidator(type: string) {
|
export function getEventsSchemaValidator(type: string) {
|
||||||
if (type === DEPOSIT) {
|
if (type === DEPOSIT) {
|
||||||
return ajv.compile(depositsEventsSchema);
|
return ajv.compile(depositsEventsSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === WITHDRAWAL) {
|
if (type === WITHDRAWAL) {
|
||||||
return ajv.compile(withdrawalsEventsSchema);
|
return ajv.compile(withdrawalsEventsSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'governance') {
|
if (type === 'governance') {
|
||||||
return ajv.compile(governanceEventsSchema);
|
return ajv.compile(governanceEventsSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'registered') {
|
if (type === 'registered') {
|
||||||
return ajv.compile(registeredEventsSchema);
|
return ajv.compile(registeredEventsSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'echo') {
|
if (type === 'echo') {
|
||||||
return ajv.compile(echoEventsSchema);
|
return ajv.compile(echoEventsSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'encrypted_notes') {
|
if (type === 'encrypted_notes') {
|
||||||
return ajv.compile(encryptedNotesSchema);
|
return ajv.compile(encryptedNotesSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Unsupported event type for schema validation');
|
throw new Error('Unsupported event type for schema validation');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,64 @@
|
|||||||
export interface jobsSchema {
|
export interface jobsSchema {
|
||||||
type: string;
|
type: string;
|
||||||
properties: {
|
properties: {
|
||||||
error: {
|
error: {
|
||||||
type: string;
|
type: string;
|
||||||
|
};
|
||||||
|
id: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
type: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
status: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
contract: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
proof: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
args: {
|
||||||
|
type: string;
|
||||||
|
items: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
txHash: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
confirmations: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
failedReason: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
id: {
|
required: string[];
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
type: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
status: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
contract: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
proof: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
args: {
|
|
||||||
type: string;
|
|
||||||
items: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
txHash: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
confirmations: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
failedReason: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
required: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const jobsSchema: jobsSchema = {
|
export const jobsSchema: jobsSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
error: { type: 'string' },
|
error: { type: 'string' },
|
||||||
id: { type: 'string' },
|
id: { type: 'string' },
|
||||||
type: { type: 'string' },
|
type: { type: 'string' },
|
||||||
status: { type: 'string' },
|
status: { type: 'string' },
|
||||||
contract: { type: 'string' },
|
contract: { type: 'string' },
|
||||||
proof: { type: 'string' },
|
proof: { type: 'string' },
|
||||||
args: {
|
args: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: { type: 'string' },
|
items: { type: 'string' },
|
||||||
|
},
|
||||||
|
txHash: { type: 'string' },
|
||||||
|
confirmations: { type: 'number' },
|
||||||
|
failedReason: { type: 'string' },
|
||||||
},
|
},
|
||||||
txHash: { type: 'string' },
|
required: ['id', 'status'],
|
||||||
confirmations: { type: 'number' },
|
|
||||||
failedReason: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: ['id', 'status'],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const jobRequestSchema: jobsSchema = {
|
export const jobRequestSchema: jobsSchema = {
|
||||||
...jobsSchema,
|
...jobsSchema,
|
||||||
required: ['id'],
|
required: ['id'],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,212 +2,258 @@ import { Config, NetId, NetIdType } from '../networkConfig';
|
|||||||
import { addressSchemaType, bnSchemaType } from '.';
|
import { addressSchemaType, bnSchemaType } from '.';
|
||||||
|
|
||||||
export interface statusInstanceType {
|
export interface statusInstanceType {
|
||||||
type: string;
|
type: string;
|
||||||
properties: {
|
properties: {
|
||||||
instanceAddress: {
|
instanceAddress: {
|
||||||
type: string;
|
type: string;
|
||||||
properties: {
|
properties: {
|
||||||
[key in string]: typeof addressSchemaType;
|
[key in string]: typeof addressSchemaType;
|
||||||
};
|
};
|
||||||
required: string[];
|
required: string[];
|
||||||
|
};
|
||||||
|
tokenAddress?: typeof addressSchemaType;
|
||||||
|
symbol?: { enum: string[] };
|
||||||
|
decimals: { enum: number[] };
|
||||||
};
|
};
|
||||||
tokenAddress?: typeof addressSchemaType;
|
required: string[];
|
||||||
symbol?: { enum: string[] };
|
|
||||||
decimals: { enum: number[] };
|
|
||||||
};
|
|
||||||
required: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface statusInstancesType {
|
export interface statusInstancesType {
|
||||||
type: string;
|
type: string;
|
||||||
properties: {
|
properties: {
|
||||||
[key in string]: statusInstanceType;
|
[key in string]: statusInstanceType;
|
||||||
};
|
};
|
||||||
required: string[];
|
required: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface statusEthPricesType {
|
export interface statusEthPricesType {
|
||||||
type: string;
|
type: string;
|
||||||
properties: {
|
properties: {
|
||||||
[key in string]: typeof bnSchemaType;
|
[key in string]: typeof bnSchemaType;
|
||||||
};
|
};
|
||||||
required?: string[];
|
required?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface statusSchema {
|
export interface statusSchema {
|
||||||
type: string;
|
type: string;
|
||||||
properties: {
|
properties: {
|
||||||
rewardAccount: typeof addressSchemaType;
|
rewardAccount: typeof addressSchemaType;
|
||||||
instances?: statusInstancesType;
|
instances?: statusInstancesType;
|
||||||
gasPrices: {
|
gasPrices: {
|
||||||
type: string;
|
type: string;
|
||||||
properties: {
|
properties: {
|
||||||
[key in string]: {
|
[key in string]: {
|
||||||
type: string;
|
type: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
required: string[];
|
||||||
|
};
|
||||||
|
netId: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
ethPrices?: statusEthPricesType;
|
||||||
|
tornadoServiceFee?: {
|
||||||
|
type: string;
|
||||||
|
maximum: number;
|
||||||
|
minimum: number;
|
||||||
|
};
|
||||||
|
latestBlock: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
latestBalance: {
|
||||||
|
type: string;
|
||||||
|
BN: boolean;
|
||||||
|
};
|
||||||
|
version: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
health: {
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
status: { const: string };
|
||||||
|
error: { type: string };
|
||||||
|
};
|
||||||
|
required: string[];
|
||||||
|
};
|
||||||
|
syncStatus: {
|
||||||
|
type: string;
|
||||||
|
properties: {
|
||||||
|
events: { type: string };
|
||||||
|
tokenPrice: { type: string };
|
||||||
|
gasPrice: { type: string };
|
||||||
|
};
|
||||||
|
required: string[];
|
||||||
|
};
|
||||||
|
onSyncEvents: { type: string };
|
||||||
|
currentQueue: {
|
||||||
|
type: string;
|
||||||
};
|
};
|
||||||
};
|
|
||||||
required: string[];
|
|
||||||
};
|
};
|
||||||
netId: {
|
required: string[];
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
ethPrices?: statusEthPricesType;
|
|
||||||
tornadoServiceFee?: {
|
|
||||||
type: string;
|
|
||||||
maximum: number;
|
|
||||||
minimum: number;
|
|
||||||
};
|
|
||||||
latestBlock: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
latestBalance: {
|
|
||||||
type: string;
|
|
||||||
BN: boolean;
|
|
||||||
};
|
|
||||||
version: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
health: {
|
|
||||||
type: string;
|
|
||||||
properties: {
|
|
||||||
status: { const: string };
|
|
||||||
error: { type: string };
|
|
||||||
};
|
|
||||||
required: string[];
|
|
||||||
};
|
|
||||||
syncStatus: {
|
|
||||||
type: string;
|
|
||||||
properties: {
|
|
||||||
events: { type: string };
|
|
||||||
tokenPrice: { type: string };
|
|
||||||
gasPrice: { type: string };
|
|
||||||
};
|
|
||||||
required: string[];
|
|
||||||
};
|
|
||||||
onSyncEvents: { type: string };
|
|
||||||
currentQueue: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
required: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusSchema: statusSchema = {
|
const statusSchema: statusSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
rewardAccount: addressSchemaType,
|
rewardAccount: addressSchemaType,
|
||||||
gasPrices: {
|
gasPrices: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
fast: { type: 'number' },
|
fast: { type: 'number' },
|
||||||
additionalProperties: { type: 'number' },
|
additionalProperties: { type: 'number' },
|
||||||
},
|
},
|
||||||
required: ['fast'],
|
required: ['fast'],
|
||||||
|
},
|
||||||
|
netId: { type: 'integer' },
|
||||||
|
tornadoServiceFee: { type: 'number', maximum: 20, minimum: 0 },
|
||||||
|
latestBlock: { type: 'number' },
|
||||||
|
latestBalance: bnSchemaType,
|
||||||
|
version: { type: 'string' },
|
||||||
|
health: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
status: { const: 'true' },
|
||||||
|
error: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['status'],
|
||||||
|
},
|
||||||
|
syncStatus: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
events: { type: 'boolean' },
|
||||||
|
tokenPrice: { type: 'boolean' },
|
||||||
|
gasPrice: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
required: ['events', 'tokenPrice', 'gasPrice'],
|
||||||
|
},
|
||||||
|
onSyncEvents: { type: 'boolean' },
|
||||||
|
currentQueue: { type: 'number' },
|
||||||
},
|
},
|
||||||
netId: { type: 'integer' },
|
required: [
|
||||||
tornadoServiceFee: { type: 'number', maximum: 20, minimum: 0 },
|
'rewardAccount',
|
||||||
latestBlock: { type: 'number' },
|
'instances',
|
||||||
latestBalance: bnSchemaType,
|
'netId',
|
||||||
version: { type: 'string' },
|
'tornadoServiceFee',
|
||||||
health: {
|
'version',
|
||||||
type: 'object',
|
'health',
|
||||||
properties: {
|
'currentQueue',
|
||||||
status: { const: 'true' },
|
],
|
||||||
error: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: ['status'],
|
|
||||||
},
|
|
||||||
syncStatus: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
events: { type: 'boolean' },
|
|
||||||
tokenPrice: { type: 'boolean' },
|
|
||||||
gasPrice: { type: 'boolean' },
|
|
||||||
},
|
|
||||||
required: ['events', 'tokenPrice', 'gasPrice'],
|
|
||||||
},
|
|
||||||
onSyncEvents: { type: 'boolean' },
|
|
||||||
currentQueue: { type: 'number' },
|
|
||||||
},
|
|
||||||
required: ['rewardAccount', 'instances', 'netId', 'tornadoServiceFee', 'version', 'health', 'currentQueue'],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getStatusSchema(netId: NetIdType, config: Config, tovarish: boolean) {
|
export function getStatusSchema(
|
||||||
const { tokens, optionalTokens, disabledTokens, nativeCurrency } = config;
|
netId: NetIdType,
|
||||||
|
config: Config,
|
||||||
|
tovarish: boolean,
|
||||||
|
) {
|
||||||
|
const { tokens, optionalTokens, disabledTokens, nativeCurrency } = config;
|
||||||
|
|
||||||
// deep copy schema
|
// deep copy schema
|
||||||
const schema = JSON.parse(JSON.stringify(statusSchema)) as statusSchema;
|
const schema = JSON.parse(JSON.stringify(statusSchema)) as statusSchema;
|
||||||
|
|
||||||
const instances = Object.keys(tokens).reduce(
|
const instances = Object.keys(tokens).reduce(
|
||||||
(acc: statusInstancesType, token) => {
|
(acc: statusInstancesType, token) => {
|
||||||
const { instanceAddress, tokenAddress, symbol, decimals, optionalInstances = [] } = tokens[token];
|
const {
|
||||||
const amounts = Object.keys(instanceAddress);
|
instanceAddress,
|
||||||
|
tokenAddress,
|
||||||
|
symbol,
|
||||||
|
decimals,
|
||||||
|
optionalInstances = [],
|
||||||
|
} = tokens[token];
|
||||||
|
const amounts = Object.keys(instanceAddress);
|
||||||
|
|
||||||
const instanceProperties: statusInstanceType = {
|
const instanceProperties: statusInstanceType = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
instanceAddress: {
|
instanceAddress: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: amounts.reduce((acc: { [key in string]: typeof addressSchemaType }, cur) => {
|
properties: amounts.reduce(
|
||||||
acc[cur] = addressSchemaType;
|
(
|
||||||
return acc;
|
acc: {
|
||||||
}, {}),
|
[key in string]: typeof addressSchemaType;
|
||||||
required: amounts.filter((amount) => !optionalInstances.includes(amount)),
|
},
|
||||||
},
|
cur,
|
||||||
decimals: { enum: [decimals] },
|
) => {
|
||||||
|
acc[cur] = addressSchemaType;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
required: amounts.filter(
|
||||||
|
(amount) => !optionalInstances.includes(amount),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
decimals: { enum: [decimals] },
|
||||||
|
},
|
||||||
|
required: ['instanceAddress', 'decimals'].concat(
|
||||||
|
tokenAddress ? ['tokenAddress'] : [],
|
||||||
|
symbol ? ['symbol'] : [],
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tokenAddress) {
|
||||||
|
instanceProperties.properties.tokenAddress = addressSchemaType;
|
||||||
|
}
|
||||||
|
if (symbol) {
|
||||||
|
instanceProperties.properties.symbol = { enum: [symbol] };
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.properties[token] = instanceProperties;
|
||||||
|
if (
|
||||||
|
!optionalTokens?.includes(token) &&
|
||||||
|
!disabledTokens?.includes(token)
|
||||||
|
) {
|
||||||
|
acc.required.push(token);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
},
|
},
|
||||||
required: ['instanceAddress', 'decimals'].concat(
|
{
|
||||||
tokenAddress ? ['tokenAddress'] : [],
|
type: 'object',
|
||||||
symbol ? ['symbol'] : [],
|
properties: {},
|
||||||
),
|
required: [],
|
||||||
};
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (tokenAddress) {
|
schema.properties.instances = instances;
|
||||||
instanceProperties.properties.tokenAddress = addressSchemaType;
|
|
||||||
}
|
|
||||||
if (symbol) {
|
|
||||||
instanceProperties.properties.symbol = { enum: [symbol] };
|
|
||||||
}
|
|
||||||
|
|
||||||
acc.properties[token] = instanceProperties;
|
const _tokens = Object.keys(tokens).filter(
|
||||||
if (!optionalTokens?.includes(token) && !disabledTokens?.includes(token)) {
|
(t) =>
|
||||||
acc.required.push(token);
|
t !== nativeCurrency &&
|
||||||
}
|
!config.optionalTokens?.includes(t) &&
|
||||||
return acc;
|
!config.disabledTokens?.includes(t),
|
||||||
},
|
);
|
||||||
{
|
|
||||||
type: 'object',
|
|
||||||
properties: {},
|
|
||||||
required: [],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
schema.properties.instances = instances;
|
if (netId === NetId.MAINNET) {
|
||||||
|
_tokens.push('torn');
|
||||||
|
}
|
||||||
|
|
||||||
const _tokens = Object.keys(tokens).filter(
|
if (_tokens.length) {
|
||||||
(t) => t !== nativeCurrency && !config.optionalTokens?.includes(t) && !config.disabledTokens?.includes(t),
|
const ethPrices: statusEthPricesType = {
|
||||||
);
|
type: 'object',
|
||||||
|
properties: _tokens.reduce(
|
||||||
|
(
|
||||||
|
acc: { [key in string]: typeof bnSchemaType },
|
||||||
|
token: string,
|
||||||
|
) => {
|
||||||
|
acc[token] = bnSchemaType;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
required: _tokens,
|
||||||
|
};
|
||||||
|
schema.properties.ethPrices = ethPrices;
|
||||||
|
schema.required.push('ethPrices');
|
||||||
|
}
|
||||||
|
|
||||||
if (netId === NetId.MAINNET) {
|
if (tovarish) {
|
||||||
_tokens.push('torn');
|
schema.required.push(
|
||||||
}
|
'gasPrices',
|
||||||
|
'latestBlock',
|
||||||
|
'latestBalance',
|
||||||
|
'syncStatus',
|
||||||
|
'onSyncEvents',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (_tokens.length) {
|
return schema;
|
||||||
const ethPrices: statusEthPricesType = {
|
|
||||||
type: 'object',
|
|
||||||
properties: _tokens.reduce((acc: { [key in string]: typeof bnSchemaType }, token: string) => {
|
|
||||||
acc[token] = bnSchemaType;
|
|
||||||
return acc;
|
|
||||||
}, {}),
|
|
||||||
required: _tokens,
|
|
||||||
};
|
|
||||||
schema.properties.ethPrices = ethPrices;
|
|
||||||
schema.required.push('ethPrices');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tovarish) {
|
|
||||||
schema.required.push('gasPrices', 'latestBlock', 'latestBalance', 'syncStatus', 'onSyncEvents');
|
|
||||||
}
|
|
||||||
|
|
||||||
return schema;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
export const addressSchemaType = {
|
export const addressSchemaType = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
pattern: '^0x[a-fA-F0-9]{40}$',
|
pattern: '^0x[a-fA-F0-9]{40}$',
|
||||||
isAddress: true,
|
isAddress: true,
|
||||||
} as const;
|
} as const;
|
||||||
export const bnSchemaType = { type: 'string', BN: true } as const;
|
export const bnSchemaType = { type: 'string', BN: true } as const;
|
||||||
export const proofSchemaType = { type: 'string', pattern: '^0x[a-fA-F0-9]{512}$' } as const;
|
export const proofSchemaType = {
|
||||||
export const bytes32SchemaType = { type: 'string', pattern: '^0x[a-fA-F0-9]{64}$' } as const;
|
type: 'string',
|
||||||
|
pattern: '^0x[a-fA-F0-9]{512}$',
|
||||||
|
} as const;
|
||||||
|
export const bytes32SchemaType = {
|
||||||
|
type: 'string',
|
||||||
|
pattern: '^0x[a-fA-F0-9]{64}$',
|
||||||
|
} as const;
|
||||||
export const bytes32BNSchemaType = { ...bytes32SchemaType, BN: true } as const;
|
export const bytes32BNSchemaType = { ...bytes32SchemaType, BN: true } as const;
|
||||||
|
|||||||
150
src/tokens.ts
150
src/tokens.ts
@@ -4,87 +4,91 @@ import { chunk } from './utils';
|
|||||||
import { Call3, multicall } from './multicall';
|
import { Call3, multicall } from './multicall';
|
||||||
|
|
||||||
export interface tokenBalances {
|
export interface tokenBalances {
|
||||||
address: string;
|
address: string;
|
||||||
name: string;
|
name: string;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
decimals: number;
|
decimals: number;
|
||||||
balance: bigint;
|
balance: bigint;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTokenBalances({
|
export async function getTokenBalances({
|
||||||
provider,
|
provider,
|
||||||
Multicall,
|
Multicall,
|
||||||
currencyName,
|
currencyName,
|
||||||
userAddress,
|
userAddress,
|
||||||
tokenAddresses = [],
|
tokenAddresses = [],
|
||||||
}: {
|
}: {
|
||||||
provider: Provider;
|
provider: Provider;
|
||||||
Multicall: Multicall;
|
Multicall: Multicall;
|
||||||
currencyName: string;
|
currencyName: string;
|
||||||
userAddress: string;
|
userAddress: string;
|
||||||
tokenAddresses: string[];
|
tokenAddresses: string[];
|
||||||
}): Promise<tokenBalances[]> {
|
}): Promise<tokenBalances[]> {
|
||||||
const tokenCalls = tokenAddresses
|
const tokenCalls = tokenAddresses
|
||||||
.map((tokenAddress) => {
|
.map((tokenAddress) => {
|
||||||
const Token = ERC20__factory.connect(tokenAddress, provider);
|
const Token = ERC20__factory.connect(tokenAddress, provider);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
contract: Token,
|
||||||
|
name: 'balanceOf',
|
||||||
|
params: [userAddress],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contract: Token,
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contract: Token,
|
||||||
|
name: 'symbol',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contract: Token,
|
||||||
|
name: 'decimals',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
})
|
||||||
|
.flat() as Call3[];
|
||||||
|
|
||||||
|
const multicallResults = await multicall(Multicall, [
|
||||||
{
|
{
|
||||||
contract: Token,
|
contract: Multicall,
|
||||||
name: 'balanceOf',
|
name: 'getEthBalance',
|
||||||
params: [userAddress],
|
params: [userAddress],
|
||||||
},
|
},
|
||||||
|
...(tokenCalls.length ? tokenCalls : []),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const ethResults = multicallResults[0];
|
||||||
|
const tokenResults = multicallResults.slice(1).length
|
||||||
|
? chunk(
|
||||||
|
multicallResults.slice(1),
|
||||||
|
tokenCalls.length / tokenAddresses.length,
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const tokenBalances = tokenResults.map((tokenResult, index) => {
|
||||||
|
const [tokenBalance, tokenName, tokenSymbol, tokenDecimals] =
|
||||||
|
tokenResult;
|
||||||
|
const tokenAddress = tokenAddresses[index];
|
||||||
|
|
||||||
|
return {
|
||||||
|
address: tokenAddress,
|
||||||
|
name: tokenName,
|
||||||
|
symbol: tokenSymbol,
|
||||||
|
decimals: Number(tokenDecimals),
|
||||||
|
balance: tokenBalance,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
contract: Token,
|
address: ZeroAddress,
|
||||||
name: 'name',
|
name: currencyName,
|
||||||
|
symbol: currencyName,
|
||||||
|
decimals: 18,
|
||||||
|
balance: ethResults,
|
||||||
},
|
},
|
||||||
{
|
...tokenBalances,
|
||||||
contract: Token,
|
];
|
||||||
name: 'symbol',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
contract: Token,
|
|
||||||
name: 'decimals',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
})
|
|
||||||
.flat() as Call3[];
|
|
||||||
|
|
||||||
const multicallResults = await multicall(Multicall, [
|
|
||||||
{
|
|
||||||
contract: Multicall,
|
|
||||||
name: 'getEthBalance',
|
|
||||||
params: [userAddress],
|
|
||||||
},
|
|
||||||
...(tokenCalls.length ? tokenCalls : []),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ethResults = multicallResults[0];
|
|
||||||
const tokenResults = multicallResults.slice(1).length
|
|
||||||
? chunk(multicallResults.slice(1), tokenCalls.length / tokenAddresses.length)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const tokenBalances = tokenResults.map((tokenResult, index) => {
|
|
||||||
const [tokenBalance, tokenName, tokenSymbol, tokenDecimals] = tokenResult;
|
|
||||||
const tokenAddress = tokenAddresses[index];
|
|
||||||
|
|
||||||
return {
|
|
||||||
address: tokenAddress,
|
|
||||||
name: tokenName,
|
|
||||||
symbol: tokenSymbol,
|
|
||||||
decimals: Number(tokenDecimals),
|
|
||||||
balance: tokenBalance,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
address: ZeroAddress,
|
|
||||||
name: currencyName,
|
|
||||||
symbol: currencyName,
|
|
||||||
decimals: 18,
|
|
||||||
balance: ethResults,
|
|
||||||
},
|
|
||||||
...tokenBalances,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { getAddress } from 'ethers';
|
import { getAddress } from 'ethers';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RelayerClient,
|
RelayerClient,
|
||||||
RelayerClientConstructor,
|
RelayerClientConstructor,
|
||||||
RelayerError,
|
RelayerError,
|
||||||
RelayerInfo,
|
RelayerInfo,
|
||||||
RelayerStatus,
|
RelayerStatus,
|
||||||
getSupportedInstances,
|
getSupportedInstances,
|
||||||
} from './relayerClient';
|
} from './relayerClient';
|
||||||
import { fetchData } from './providers';
|
import { fetchData } from './providers';
|
||||||
import { CachedRelayerInfo, MinimalEvents } from './events';
|
import { CachedRelayerInfo, MinimalEvents } from './events';
|
||||||
@@ -17,262 +17,213 @@ import { enabledChains, getConfig, NetId, NetIdType } from './networkConfig';
|
|||||||
export const MAX_TOVARISH_EVENTS = 5000;
|
export const MAX_TOVARISH_EVENTS = 5000;
|
||||||
|
|
||||||
export interface EventsStatus {
|
export interface EventsStatus {
|
||||||
events: number;
|
events: number;
|
||||||
lastBlock: number;
|
lastBlock: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstanceEventsStatus {
|
export interface InstanceEventsStatus {
|
||||||
[index: string]: {
|
[index: string]: {
|
||||||
deposits: EventsStatus;
|
deposits: EventsStatus;
|
||||||
withdrawals: EventsStatus;
|
withdrawals: EventsStatus;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CurrencyEventsStatus {
|
export interface CurrencyEventsStatus {
|
||||||
[index: string]: InstanceEventsStatus;
|
[index: string]: InstanceEventsStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TovarishEventsStatus {
|
export interface TovarishEventsStatus {
|
||||||
governance?: EventsStatus;
|
governance?: EventsStatus;
|
||||||
registered?: {
|
registered?: {
|
||||||
lastBlock: number;
|
lastBlock: number;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
relayers: number;
|
relayers: number;
|
||||||
};
|
};
|
||||||
echo: EventsStatus;
|
echo: EventsStatus;
|
||||||
encrypted_notes: EventsStatus;
|
encrypted_notes: EventsStatus;
|
||||||
instances: CurrencyEventsStatus;
|
instances: CurrencyEventsStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TovarishSyncStatus {
|
export interface TovarishSyncStatus {
|
||||||
events: boolean;
|
events: boolean;
|
||||||
tokenPrice: boolean;
|
tokenPrice: boolean;
|
||||||
gasPrice: boolean;
|
gasPrice: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expected response from /status endpoint
|
// Expected response from /status endpoint
|
||||||
export interface TovarishStatus extends RelayerStatus {
|
export interface TovarishStatus extends RelayerStatus {
|
||||||
latestBalance: string;
|
latestBalance: string;
|
||||||
events: TovarishEventsStatus;
|
events: TovarishEventsStatus;
|
||||||
syncStatus: TovarishSyncStatus;
|
syncStatus: TovarishSyncStatus;
|
||||||
onSyncEvents: boolean;
|
onSyncEvents: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Formatted TovarishStatus for Frontend usage
|
// Formatted TovarishStatus for Frontend usage
|
||||||
export interface TovarishInfo extends RelayerInfo {
|
export interface TovarishInfo extends RelayerInfo {
|
||||||
latestBlock: number;
|
latestBlock: number;
|
||||||
latestBalance: string;
|
latestBalance: string;
|
||||||
version: string;
|
version: string;
|
||||||
events: TovarishEventsStatus;
|
events: TovarishEventsStatus;
|
||||||
syncStatus: TovarishSyncStatus;
|
syncStatus: TovarishSyncStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query input for TovarishEvents
|
// Query input for TovarishEvents
|
||||||
export interface TovarishEventsQuery {
|
export interface TovarishEventsQuery {
|
||||||
type: string;
|
type: string;
|
||||||
currency?: string;
|
currency?: string;
|
||||||
amount?: string;
|
amount?: string;
|
||||||
fromBlock: number;
|
fromBlock: number;
|
||||||
recent?: boolean;
|
recent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseTovarishEvents<T> {
|
export interface BaseTovarishEvents<T> {
|
||||||
events: T[];
|
events: T[];
|
||||||
lastSyncBlock: number;
|
lastSyncBlock: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TovarishClient extends RelayerClient {
|
export class TovarishClient extends RelayerClient {
|
||||||
declare selectedRelayer?: TovarishInfo;
|
declare selectedRelayer?: TovarishInfo;
|
||||||
|
|
||||||
constructor(clientConstructor: RelayerClientConstructor) {
|
constructor(clientConstructor: RelayerClientConstructor) {
|
||||||
super(clientConstructor);
|
super(clientConstructor);
|
||||||
this.tovarish = true;
|
this.tovarish = true;
|
||||||
}
|
|
||||||
|
|
||||||
async askRelayerStatus({
|
|
||||||
hostname,
|
|
||||||
url,
|
|
||||||
relayerAddress,
|
|
||||||
}: {
|
|
||||||
hostname?: string;
|
|
||||||
// optional url if entered manually
|
|
||||||
url?: string;
|
|
||||||
// relayerAddress from registry contract to prevent cheating
|
|
||||||
relayerAddress?: string;
|
|
||||||
}): Promise<TovarishStatus> {
|
|
||||||
const status = (await super.askRelayerStatus({ hostname, url, relayerAddress })) as TovarishStatus;
|
|
||||||
|
|
||||||
if (!status.version.includes('tovarish')) {
|
|
||||||
throw new Error('Not a tovarish relayer!');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
async askRelayerStatus({
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ask status for all enabled chains for tovarish relayer
|
|
||||||
*/
|
|
||||||
async askAllStatus({
|
|
||||||
hostname,
|
|
||||||
url,
|
|
||||||
relayerAddress,
|
|
||||||
}: {
|
|
||||||
hostname?: string;
|
|
||||||
// optional url if entered manually
|
|
||||||
url?: string;
|
|
||||||
// relayerAddress from registry contract to prevent cheating
|
|
||||||
relayerAddress?: string;
|
|
||||||
}): Promise<TovarishStatus[]> {
|
|
||||||
if (!url && hostname) {
|
|
||||||
url = `https://${!hostname.endsWith('/') ? hostname + '/' : hostname}`;
|
|
||||||
} else if (url && !url.endsWith('/')) {
|
|
||||||
url += '/';
|
|
||||||
} else {
|
|
||||||
url = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusArray = (await fetchData(`${url}status`, {
|
|
||||||
...this.fetchDataOptions,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json, application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
timeout: 30000,
|
|
||||||
maxRetry: this.fetchDataOptions?.torPort ? 2 : 0,
|
|
||||||
})) as object;
|
|
||||||
|
|
||||||
if (!Array.isArray(statusArray)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const tovarishStatus: TovarishStatus[] = [];
|
|
||||||
|
|
||||||
for (const rawStatus of statusArray) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const netId = (rawStatus as any).netId as NetIdType;
|
|
||||||
const config = getConfig(netId);
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const statusValidator = ajv.compile(getStatusSchema((rawStatus as any).netId, config, this.tovarish));
|
|
||||||
|
|
||||||
if (!statusValidator) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = {
|
|
||||||
...rawStatus,
|
|
||||||
url: `${url}${netId}/`,
|
|
||||||
} as TovarishStatus;
|
|
||||||
|
|
||||||
if (status.currentQueue > 5) {
|
|
||||||
throw new Error('Withdrawal queue is overloaded');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!enabledChains.includes(status.netId)) {
|
|
||||||
throw new Error('This relayer serves a different network');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relayerAddress && status.netId === NetId.MAINNET && status.rewardAccount !== relayerAddress) {
|
|
||||||
throw new Error('The Relayer reward address must match registered address');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!status.version.includes('tovarish')) {
|
|
||||||
throw new Error('Not a tovarish relayer!');
|
|
||||||
}
|
|
||||||
|
|
||||||
tovarishStatus.push(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tovarishStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
async filterRelayer(relayer: CachedRelayerInfo): Promise<TovarishInfo | RelayerError | undefined> {
|
|
||||||
const { ensName, relayerAddress, tovarishHost, tovarishNetworks } = relayer;
|
|
||||||
|
|
||||||
if (!tovarishHost || !tovarishNetworks?.includes(this.netId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hostname = `${tovarishHost}/${this.netId}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const status = await this.askRelayerStatus({ hostname, relayerAddress });
|
|
||||||
|
|
||||||
return {
|
|
||||||
netId: status.netId,
|
|
||||||
url: status.url,
|
|
||||||
hostname,
|
hostname,
|
||||||
ensName,
|
url,
|
||||||
relayerAddress,
|
relayerAddress,
|
||||||
rewardAccount: getAddress(status.rewardAccount),
|
}: {
|
||||||
instances: getSupportedInstances(status.instances),
|
hostname?: string;
|
||||||
stakeBalance: relayer.stakeBalance,
|
// optional url if entered manually
|
||||||
gasPrice: status.gasPrices?.fast,
|
url?: string;
|
||||||
ethPrices: status.ethPrices,
|
// relayerAddress from registry contract to prevent cheating
|
||||||
currentQueue: status.currentQueue,
|
relayerAddress?: string;
|
||||||
tornadoServiceFee: status.tornadoServiceFee,
|
}): Promise<TovarishStatus> {
|
||||||
// Additional fields for tovarish relayer
|
const status = (await super.askRelayerStatus({
|
||||||
latestBlock: Number(status.latestBlock),
|
hostname,
|
||||||
latestBalance: status.latestBalance,
|
url,
|
||||||
version: status.version,
|
relayerAddress,
|
||||||
events: status.events,
|
})) as TovarishStatus;
|
||||||
syncStatus: status.syncStatus,
|
|
||||||
} as TovarishInfo;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
if (!status.version.includes('tovarish')) {
|
||||||
} catch (err: any) {
|
throw new Error('Not a tovarish relayer!');
|
||||||
return {
|
}
|
||||||
hostname,
|
|
||||||
relayerAddress,
|
return status;
|
||||||
errorMessage: err.message,
|
|
||||||
hasError: true,
|
|
||||||
} as RelayerError;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async getValidRelayers(relayers: CachedRelayerInfo[]): Promise<{
|
/**
|
||||||
validRelayers: TovarishInfo[];
|
* Ask status for all enabled chains for tovarish relayer
|
||||||
invalidRelayers: RelayerError[];
|
*/
|
||||||
}> {
|
async askAllStatus({
|
||||||
const invalidRelayers: RelayerError[] = [];
|
hostname,
|
||||||
|
url,
|
||||||
|
relayerAddress,
|
||||||
|
}: {
|
||||||
|
hostname?: string;
|
||||||
|
// optional url if entered manually
|
||||||
|
url?: string;
|
||||||
|
// relayerAddress from registry contract to prevent cheating
|
||||||
|
relayerAddress?: string;
|
||||||
|
}): Promise<TovarishStatus[]> {
|
||||||
|
if (!url && hostname) {
|
||||||
|
url = `https://${!hostname.endsWith('/') ? hostname + '/' : hostname}`;
|
||||||
|
} else if (url && !url.endsWith('/')) {
|
||||||
|
url += '/';
|
||||||
|
} else {
|
||||||
|
url = '';
|
||||||
|
}
|
||||||
|
|
||||||
const validRelayers = (await Promise.all(relayers.map((relayer) => this.filterRelayer(relayer)))).filter((r) => {
|
const statusArray = (await fetchData(`${url}status`, {
|
||||||
if (!r) {
|
...this.fetchDataOptions,
|
||||||
return false;
|
headers: {
|
||||||
}
|
'Content-Type':
|
||||||
if ((r as RelayerError).hasError) {
|
'application/json, application/x-www-form-urlencoded',
|
||||||
invalidRelayers.push(r as RelayerError);
|
},
|
||||||
return false;
|
timeout: 30000,
|
||||||
}
|
maxRetry: this.fetchDataOptions?.torPort ? 2 : 0,
|
||||||
return true;
|
})) as object;
|
||||||
}) as TovarishInfo[];
|
|
||||||
|
|
||||||
return {
|
if (!Array.isArray(statusArray)) {
|
||||||
validRelayers,
|
return [];
|
||||||
invalidRelayers,
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async getTovarishRelayers(relayers: CachedRelayerInfo[]): Promise<{
|
const tovarishStatus: TovarishStatus[] = [];
|
||||||
validRelayers: TovarishInfo[];
|
|
||||||
invalidRelayers: RelayerError[];
|
|
||||||
}> {
|
|
||||||
const validRelayers: TovarishInfo[] = [];
|
|
||||||
const invalidRelayers: RelayerError[] = [];
|
|
||||||
|
|
||||||
await Promise.all(
|
for (const rawStatus of statusArray) {
|
||||||
relayers
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
.filter((r) => r.tovarishHost && r.tovarishNetworks?.length)
|
const netId = (rawStatus as any).netId as NetIdType;
|
||||||
.map(async (relayer) => {
|
const config = getConfig(netId);
|
||||||
const { ensName, relayerAddress, tovarishHost } = relayer;
|
|
||||||
|
|
||||||
try {
|
const statusValidator = ajv.compile(
|
||||||
const statusArray = await this.askAllStatus({ hostname: tovarishHost as string, relayerAddress });
|
getStatusSchema(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(rawStatus as any).netId,
|
||||||
|
config,
|
||||||
|
this.tovarish,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
for (const status of statusArray) {
|
if (!statusValidator) {
|
||||||
validRelayers.push({
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = {
|
||||||
|
...rawStatus,
|
||||||
|
url: `${url}${netId}/`,
|
||||||
|
} as TovarishStatus;
|
||||||
|
|
||||||
|
if (status.currentQueue > 5) {
|
||||||
|
throw new Error('Withdrawal queue is overloaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enabledChains.includes(status.netId)) {
|
||||||
|
throw new Error('This relayer serves a different network');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
relayerAddress &&
|
||||||
|
status.netId === NetId.MAINNET &&
|
||||||
|
status.rewardAccount !== relayerAddress
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'The Relayer reward address must match registered address',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!status.version.includes('tovarish')) {
|
||||||
|
throw new Error('Not a tovarish relayer!');
|
||||||
|
}
|
||||||
|
|
||||||
|
tovarishStatus.push(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tovarishStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
async filterRelayer(
|
||||||
|
relayer: CachedRelayerInfo,
|
||||||
|
): Promise<TovarishInfo | RelayerError | undefined> {
|
||||||
|
const { ensName, relayerAddress, tovarishHost, tovarishNetworks } =
|
||||||
|
relayer;
|
||||||
|
|
||||||
|
if (!tovarishHost || !tovarishNetworks?.includes(this.netId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostname = `${tovarishHost}/${this.netId}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const status = await this.askRelayerStatus({
|
||||||
|
hostname,
|
||||||
|
relayerAddress,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
netId: status.netId,
|
netId: status.netId,
|
||||||
url: status.url,
|
url: status.url,
|
||||||
hostname: tovarishHost as string,
|
hostname,
|
||||||
ensName,
|
ensName,
|
||||||
relayerAddress,
|
relayerAddress,
|
||||||
rewardAccount: getAddress(status.rewardAccount),
|
rewardAccount: getAddress(status.rewardAccount),
|
||||||
@@ -288,108 +239,192 @@ export class TovarishClient extends RelayerClient {
|
|||||||
version: status.version,
|
version: status.version,
|
||||||
events: status.events,
|
events: status.events,
|
||||||
syncStatus: status.syncStatus,
|
syncStatus: status.syncStatus,
|
||||||
});
|
} as TovarishInfo;
|
||||||
}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
invalidRelayers.push({
|
return {
|
||||||
hostname: tovarishHost as string,
|
hostname,
|
||||||
relayerAddress,
|
relayerAddress,
|
||||||
errorMessage: err.message,
|
errorMessage: err.message,
|
||||||
hasError: true,
|
hasError: true,
|
||||||
});
|
} as RelayerError;
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
);
|
|
||||||
|
async getValidRelayers(relayers: CachedRelayerInfo[]): Promise<{
|
||||||
return {
|
validRelayers: TovarishInfo[];
|
||||||
validRelayers,
|
invalidRelayers: RelayerError[];
|
||||||
invalidRelayers,
|
}> {
|
||||||
};
|
const invalidRelayers: RelayerError[] = [];
|
||||||
}
|
|
||||||
|
const validRelayers = (
|
||||||
async getEvents<T extends MinimalEvents>({
|
await Promise.all(
|
||||||
type,
|
relayers.map((relayer) => this.filterRelayer(relayer)),
|
||||||
currency,
|
)
|
||||||
amount,
|
).filter((r) => {
|
||||||
fromBlock,
|
if (!r) {
|
||||||
recent,
|
return false;
|
||||||
}: TovarishEventsQuery): Promise<BaseTovarishEvents<T>> {
|
}
|
||||||
const url = `${this.selectedRelayer?.url}events`;
|
if ((r as RelayerError).hasError) {
|
||||||
|
invalidRelayers.push(r as RelayerError);
|
||||||
const schemaValidator = getEventsSchemaValidator(type);
|
return false;
|
||||||
|
}
|
||||||
try {
|
return true;
|
||||||
const events = [];
|
}) as TovarishInfo[];
|
||||||
let lastSyncBlock = fromBlock;
|
|
||||||
|
return {
|
||||||
// eslint-disable-next-line no-constant-condition
|
validRelayers,
|
||||||
while (true) {
|
invalidRelayers,
|
||||||
// eslint-disable-next-line prefer-const
|
};
|
||||||
let { events: fetchedEvents, lastSyncBlock: currentBlock } = (await fetchData(url, {
|
}
|
||||||
...this.fetchDataOptions,
|
|
||||||
method: 'POST',
|
async getTovarishRelayers(relayers: CachedRelayerInfo[]): Promise<{
|
||||||
headers: {
|
validRelayers: TovarishInfo[];
|
||||||
'Content-Type': 'application/json',
|
invalidRelayers: RelayerError[];
|
||||||
},
|
}> {
|
||||||
body: JSON.stringify({
|
const validRelayers: TovarishInfo[] = [];
|
||||||
type,
|
const invalidRelayers: RelayerError[] = [];
|
||||||
currency,
|
|
||||||
amount,
|
await Promise.all(
|
||||||
fromBlock,
|
relayers
|
||||||
recent,
|
.filter((r) => r.tovarishHost && r.tovarishNetworks?.length)
|
||||||
}),
|
.map(async (relayer) => {
|
||||||
})) as BaseTovarishEvents<T>;
|
const { ensName, relayerAddress, tovarishHost } = relayer;
|
||||||
|
|
||||||
if (!schemaValidator(fetchedEvents)) {
|
try {
|
||||||
const errMsg = `Schema validation failed for ${type} events`;
|
const statusArray = await this.askAllStatus({
|
||||||
throw new Error(errMsg);
|
hostname: tovarishHost as string,
|
||||||
|
relayerAddress,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const status of statusArray) {
|
||||||
|
validRelayers.push({
|
||||||
|
netId: status.netId,
|
||||||
|
url: status.url,
|
||||||
|
hostname: tovarishHost as string,
|
||||||
|
ensName,
|
||||||
|
relayerAddress,
|
||||||
|
rewardAccount: getAddress(status.rewardAccount),
|
||||||
|
instances: getSupportedInstances(
|
||||||
|
status.instances,
|
||||||
|
),
|
||||||
|
stakeBalance: relayer.stakeBalance,
|
||||||
|
gasPrice: status.gasPrices?.fast,
|
||||||
|
ethPrices: status.ethPrices,
|
||||||
|
currentQueue: status.currentQueue,
|
||||||
|
tornadoServiceFee: status.tornadoServiceFee,
|
||||||
|
// Additional fields for tovarish relayer
|
||||||
|
latestBlock: Number(status.latestBlock),
|
||||||
|
latestBalance: status.latestBalance,
|
||||||
|
version: status.version,
|
||||||
|
events: status.events,
|
||||||
|
syncStatus: status.syncStatus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} catch (err: any) {
|
||||||
|
invalidRelayers.push({
|
||||||
|
hostname: tovarishHost as string,
|
||||||
|
relayerAddress,
|
||||||
|
errorMessage: err.message,
|
||||||
|
hasError: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
validRelayers,
|
||||||
|
invalidRelayers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEvents<T extends MinimalEvents>({
|
||||||
|
type,
|
||||||
|
currency,
|
||||||
|
amount,
|
||||||
|
fromBlock,
|
||||||
|
recent,
|
||||||
|
}: TovarishEventsQuery): Promise<BaseTovarishEvents<T>> {
|
||||||
|
const url = `${this.selectedRelayer?.url}events`;
|
||||||
|
|
||||||
|
const schemaValidator = getEventsSchemaValidator(type);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const events = [];
|
||||||
|
let lastSyncBlock = fromBlock;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
let { events: fetchedEvents, lastSyncBlock: currentBlock } =
|
||||||
|
(await fetchData(url, {
|
||||||
|
...this.fetchDataOptions,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
type,
|
||||||
|
currency,
|
||||||
|
amount,
|
||||||
|
fromBlock,
|
||||||
|
recent,
|
||||||
|
}),
|
||||||
|
})) as BaseTovarishEvents<T>;
|
||||||
|
|
||||||
|
if (!schemaValidator(fetchedEvents)) {
|
||||||
|
const errMsg = `Schema validation failed for ${type} events`;
|
||||||
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recent) {
|
||||||
|
return {
|
||||||
|
events: fetchedEvents,
|
||||||
|
lastSyncBlock: currentBlock,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSyncBlock = currentBlock;
|
||||||
|
|
||||||
|
if (!Array.isArray(fetchedEvents) || !fetchedEvents.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchedEvents = fetchedEvents.sort((a, b) => {
|
||||||
|
if (a.blockNumber === b.blockNumber) {
|
||||||
|
return a.logIndex - b.logIndex;
|
||||||
|
}
|
||||||
|
return a.blockNumber - b.blockNumber;
|
||||||
|
});
|
||||||
|
|
||||||
|
const [lastEvent] = fetchedEvents.slice(-1);
|
||||||
|
|
||||||
|
if (fetchedEvents.length < MAX_TOVARISH_EVENTS - 100) {
|
||||||
|
events.push(...fetchedEvents);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchedEvents = fetchedEvents.filter(
|
||||||
|
(e) => e.blockNumber !== lastEvent.blockNumber,
|
||||||
|
);
|
||||||
|
fromBlock = Number(lastEvent.blockNumber);
|
||||||
|
|
||||||
|
events.push(...fetchedEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
events,
|
||||||
|
lastSyncBlock,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error from TovarishClient events endpoint');
|
||||||
|
console.log(err);
|
||||||
|
return {
|
||||||
|
events: [],
|
||||||
|
lastSyncBlock: fromBlock,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recent) {
|
|
||||||
return {
|
|
||||||
events: fetchedEvents,
|
|
||||||
lastSyncBlock: currentBlock,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSyncBlock = currentBlock;
|
|
||||||
|
|
||||||
if (!Array.isArray(fetchedEvents) || !fetchedEvents.length) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchedEvents = fetchedEvents.sort((a, b) => {
|
|
||||||
if (a.blockNumber === b.blockNumber) {
|
|
||||||
return a.logIndex - b.logIndex;
|
|
||||||
}
|
|
||||||
return a.blockNumber - b.blockNumber;
|
|
||||||
});
|
|
||||||
|
|
||||||
const [lastEvent] = fetchedEvents.slice(-1);
|
|
||||||
|
|
||||||
if (fetchedEvents.length < MAX_TOVARISH_EVENTS - 100) {
|
|
||||||
events.push(...fetchedEvents);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchedEvents = fetchedEvents.filter((e) => e.blockNumber !== lastEvent.blockNumber);
|
|
||||||
fromBlock = Number(lastEvent.blockNumber);
|
|
||||||
|
|
||||||
events.push(...fetchedEvents);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
events,
|
|
||||||
lastSyncBlock,
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
console.log('Error from TovarishClient events endpoint');
|
|
||||||
console.log(err);
|
|
||||||
return {
|
|
||||||
events: [],
|
|
||||||
lastSyncBlock: fromBlock,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
199
src/utils.ts
199
src/utils.ts
@@ -4,172 +4,197 @@ import type { BigNumberish } from 'ethers';
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(BigInt.prototype as any).toJSON = function () {
|
(BigInt.prototype as any).toJSON = function () {
|
||||||
return this.toString();
|
return this.toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
type bnInput = number | string | number[] | Uint8Array | Buffer | BN;
|
type bnInput = number | string | number[] | Uint8Array | Buffer | BN;
|
||||||
|
|
||||||
export const isNode =
|
export const isNode =
|
||||||
!(
|
!(
|
||||||
process as typeof process & {
|
process as typeof process & {
|
||||||
browser?: boolean;
|
browser?: boolean;
|
||||||
}
|
}
|
||||||
).browser && typeof globalThis.window === 'undefined';
|
).browser && typeof globalThis.window === 'undefined';
|
||||||
|
|
||||||
export const crypto = isNode ? webcrypto : (globalThis.crypto as typeof webcrypto);
|
export const crypto = isNode
|
||||||
|
? webcrypto
|
||||||
|
: (globalThis.crypto as typeof webcrypto);
|
||||||
|
|
||||||
export const chunk = <T>(arr: T[], size: number): T[][] =>
|
export const chunk = <T>(arr: T[], size: number): T[][] =>
|
||||||
[...Array(Math.ceil(arr.length / size))].map((_, i) => arr.slice(size * i, size + size * i));
|
[...Array(Math.ceil(arr.length / size))].map((_, i) =>
|
||||||
|
arr.slice(size * i, size + size * i),
|
||||||
|
);
|
||||||
|
|
||||||
export function sleep(ms: number) {
|
export function sleep(ms: number) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateUrl(url: string, protocols?: string[]) {
|
export function validateUrl(url: string, protocols?: string[]) {
|
||||||
try {
|
try {
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
if (protocols && protocols.length) {
|
if (protocols && protocols.length) {
|
||||||
return protocols.map((p) => p.toLowerCase()).includes(parsedUrl.protocol);
|
return protocols
|
||||||
|
.map((p) => p.toLowerCase())
|
||||||
|
.includes(parsedUrl.protocol);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
|
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
|
||||||
const totalSize = arrays.reduce((acc, e) => acc + e.length, 0);
|
const totalSize = arrays.reduce((acc, e) => acc + e.length, 0);
|
||||||
const merged = new Uint8Array(totalSize);
|
const merged = new Uint8Array(totalSize);
|
||||||
|
|
||||||
arrays.forEach((array, i, arrays) => {
|
arrays.forEach((array, i, arrays) => {
|
||||||
const offset = arrays.slice(0, i).reduce((acc, e) => acc + e.length, 0);
|
const offset = arrays.slice(0, i).reduce((acc, e) => acc + e.length, 0);
|
||||||
merged.set(array, offset);
|
merged.set(array, offset);
|
||||||
});
|
});
|
||||||
|
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bufferToBytes(b: Buffer) {
|
export function bufferToBytes(b: Buffer) {
|
||||||
return new Uint8Array(b.buffer);
|
return new Uint8Array(b.buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bytesToBase64(bytes: Uint8Array) {
|
export function bytesToBase64(bytes: Uint8Array) {
|
||||||
return btoa(bytes.reduce((data, byte) => data + String.fromCharCode(byte), ''));
|
return btoa(
|
||||||
|
bytes.reduce((data, byte) => data + String.fromCharCode(byte), ''),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function base64ToBytes(base64: string) {
|
export function base64ToBytes(base64: string) {
|
||||||
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bytesToHex(bytes: Uint8Array) {
|
export function bytesToHex(bytes: Uint8Array) {
|
||||||
return (
|
return (
|
||||||
'0x' +
|
'0x' +
|
||||||
Array.from(bytes)
|
Array.from(bytes)
|
||||||
.map((b) => b.toString(16).padStart(2, '0'))
|
.map((b) => b.toString(16).padStart(2, '0'))
|
||||||
.join('')
|
.join('')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hexToBytes(hexString: string) {
|
export function hexToBytes(hexString: string) {
|
||||||
if (hexString.slice(0, 2) === '0x') {
|
if (hexString.slice(0, 2) === '0x') {
|
||||||
hexString = hexString.slice(2);
|
hexString = hexString.slice(2);
|
||||||
}
|
}
|
||||||
if (hexString.length % 2 !== 0) {
|
if (hexString.length % 2 !== 0) {
|
||||||
hexString = '0' + hexString;
|
hexString = '0' + hexString;
|
||||||
}
|
}
|
||||||
return Uint8Array.from((hexString.match(/.{1,2}/g) as string[]).map((byte) => parseInt(byte, 16)));
|
return Uint8Array.from(
|
||||||
|
(hexString.match(/.{1,2}/g) as string[]).map((byte) =>
|
||||||
|
parseInt(byte, 16),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert BE encoded bytes (Buffer | Uint8Array) array to BigInt
|
// Convert BE encoded bytes (Buffer | Uint8Array) array to BigInt
|
||||||
export function bytesToBN(bytes: Uint8Array) {
|
export function bytesToBN(bytes: Uint8Array) {
|
||||||
return BigInt(bytesToHex(bytes));
|
return BigInt(bytesToHex(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert BigInt to BE encoded Uint8Array type
|
// Convert BigInt to BE encoded Uint8Array type
|
||||||
export function bnToBytes(bigint: bigint | string) {
|
export function bnToBytes(bigint: bigint | string) {
|
||||||
// Parse bigint to hex string
|
// Parse bigint to hex string
|
||||||
let hexString: string = typeof bigint === 'bigint' ? bigint.toString(16) : bigint;
|
let hexString: string =
|
||||||
// Remove hex string prefix if exists
|
typeof bigint === 'bigint' ? bigint.toString(16) : bigint;
|
||||||
if (hexString.slice(0, 2) === '0x') {
|
// Remove hex string prefix if exists
|
||||||
hexString = hexString.slice(2);
|
if (hexString.slice(0, 2) === '0x') {
|
||||||
}
|
hexString = hexString.slice(2);
|
||||||
// Hex string length should be a multiplier of two (To make correct bytes)
|
}
|
||||||
if (hexString.length % 2 !== 0) {
|
// Hex string length should be a multiplier of two (To make correct bytes)
|
||||||
hexString = '0' + hexString;
|
if (hexString.length % 2 !== 0) {
|
||||||
}
|
hexString = '0' + hexString;
|
||||||
return Uint8Array.from((hexString.match(/.{1,2}/g) as string[]).map((byte) => parseInt(byte, 16)));
|
}
|
||||||
|
return Uint8Array.from(
|
||||||
|
(hexString.match(/.{1,2}/g) as string[]).map((byte) =>
|
||||||
|
parseInt(byte, 16),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert LE encoded bytes (Buffer | Uint8Array) array to BigInt
|
// Convert LE encoded bytes (Buffer | Uint8Array) array to BigInt
|
||||||
export function leBuff2Int(bytes: Uint8Array) {
|
export function leBuff2Int(bytes: Uint8Array) {
|
||||||
return new BN(bytes, 16, 'le');
|
return new BN(bytes, 16, 'le');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert BigInt to LE encoded Uint8Array type
|
// Convert BigInt to LE encoded Uint8Array type
|
||||||
export function leInt2Buff(bigint: bnInput | bigint) {
|
export function leInt2Buff(bigint: bnInput | bigint) {
|
||||||
return Uint8Array.from(new BN(bigint as bnInput).toArray('le', 31));
|
return Uint8Array.from(new BN(bigint as bnInput).toArray('le', 31));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inherited from tornado-core and tornado-cli
|
// Inherited from tornado-core and tornado-cli
|
||||||
export function toFixedHex(numberish: BigNumberish, length = 32) {
|
export function toFixedHex(numberish: BigNumberish, length = 32) {
|
||||||
return (
|
return (
|
||||||
'0x' +
|
'0x' +
|
||||||
BigInt(numberish)
|
BigInt(numberish)
|
||||||
.toString(16)
|
.toString(16)
|
||||||
.padStart(length * 2, '0')
|
.padStart(length * 2, '0')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toFixedLength(string: string, length: number = 32) {
|
export function toFixedLength(string: string, length: number = 32) {
|
||||||
string = string.replace('0x', '');
|
string = string.replace('0x', '');
|
||||||
return '0x' + string.padStart(length * 2, '0');
|
return '0x' + string.padStart(length * 2, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Random BigInt in a range of bytes
|
// Random BigInt in a range of bytes
|
||||||
export function rBigInt(nbytes: number = 31) {
|
export function rBigInt(nbytes: number = 31) {
|
||||||
return bytesToBN(crypto.getRandomValues(new Uint8Array(nbytes)));
|
return bytesToBN(crypto.getRandomValues(new Uint8Array(nbytes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rHex(nbytes: number = 32) {
|
export function rHex(nbytes: number = 32) {
|
||||||
return bytesToHex(crypto.getRandomValues(new Uint8Array(nbytes)));
|
return bytesToHex(crypto.getRandomValues(new Uint8Array(nbytes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for JSON.stringify(value, bigIntReplacer, space)
|
// Used for JSON.stringify(value, bigIntReplacer, space)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export function bigIntReplacer(key: any, value: any) {
|
export function bigIntReplacer(key: any, value: any) {
|
||||||
return typeof value === 'bigint' ? value.toString() : value;
|
return typeof value === 'bigint' ? value.toString() : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function substring(str: string, length: number = 10) {
|
export function substring(str: string, length: number = 10) {
|
||||||
if (str.length < length * 2) {
|
if (str.length < length * 2) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${str.substring(0, length)}...${str.substring(str.length - length)}`;
|
return `${str.substring(0, length)}...${str.substring(str.length - length)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function digest(bytes: Uint8Array, algo: string = 'SHA-384') {
|
export async function digest(bytes: Uint8Array, algo: string = 'SHA-384') {
|
||||||
return new Uint8Array(await crypto.subtle.digest(algo, bytes));
|
return new Uint8Array(await crypto.subtle.digest(algo, bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function numberFormatter(num: string | number | bigint, digits: number = 3): string {
|
export function numberFormatter(
|
||||||
const lookup = [
|
num: string | number | bigint,
|
||||||
{ value: 1, symbol: '' },
|
digits: number = 3,
|
||||||
{ value: 1e3, symbol: 'K' },
|
): string {
|
||||||
{ value: 1e6, symbol: 'M' },
|
const lookup = [
|
||||||
{ value: 1e9, symbol: 'G' },
|
{ value: 1, symbol: '' },
|
||||||
{ value: 1e12, symbol: 'T' },
|
{ value: 1e3, symbol: 'K' },
|
||||||
{ value: 1e15, symbol: 'P' },
|
{ value: 1e6, symbol: 'M' },
|
||||||
{ value: 1e18, symbol: 'E' },
|
{ value: 1e9, symbol: 'G' },
|
||||||
];
|
{ value: 1e12, symbol: 'T' },
|
||||||
const regexp = /\.0+$|(?<=\.[0-9]*[1-9])0+$/;
|
{ value: 1e15, symbol: 'P' },
|
||||||
const item = lookup
|
{ value: 1e18, symbol: 'E' },
|
||||||
.slice()
|
];
|
||||||
.reverse()
|
const regexp = /\.0+$|(?<=\.[0-9]*[1-9])0+$/;
|
||||||
.find((item) => Number(num) >= item.value);
|
const item = lookup
|
||||||
return item ? (Number(num) / item.value).toFixed(digits).replace(regexp, '').concat(item.symbol) : '0';
|
.slice()
|
||||||
|
.reverse()
|
||||||
|
.find((item) => Number(num) >= item.value);
|
||||||
|
return item
|
||||||
|
? (Number(num) / item.value)
|
||||||
|
.toFixed(digits)
|
||||||
|
.replace(regexp, '')
|
||||||
|
.concat(item.symbol)
|
||||||
|
: '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isHex(value: string) {
|
export function isHex(value: string) {
|
||||||
return /^0x[0-9a-fA-F]*$/.test(value);
|
return /^0x[0-9a-fA-F]*$/.test(value);
|
||||||
}
|
}
|
||||||
|
|||||||
115
src/websnark.ts
115
src/websnark.ts
@@ -6,81 +6,86 @@ import type { Element } from '@tornado/fixed-merkle-tree';
|
|||||||
import { toFixedHex } from './utils';
|
import { toFixedHex } from './utils';
|
||||||
|
|
||||||
export interface snarkInputs {
|
export interface snarkInputs {
|
||||||
// Public snark inputs
|
// Public snark inputs
|
||||||
root: Element;
|
root: Element;
|
||||||
nullifierHex: string;
|
nullifierHex: string;
|
||||||
recipient: string;
|
recipient: string;
|
||||||
relayer: string;
|
relayer: string;
|
||||||
fee: bigint;
|
fee: bigint;
|
||||||
refund: bigint;
|
refund: bigint;
|
||||||
|
|
||||||
// Private snark inputs
|
// Private snark inputs
|
||||||
nullifier: bigint;
|
nullifier: bigint;
|
||||||
secret: bigint;
|
secret: bigint;
|
||||||
pathElements: Element[];
|
pathElements: Element[];
|
||||||
pathIndices: Element[];
|
pathIndices: Element[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type snarkArgs = [
|
export type snarkArgs = [
|
||||||
_root: string,
|
_root: string,
|
||||||
_nullifierHash: string,
|
_nullifierHash: string,
|
||||||
_recipient: string,
|
_recipient: string,
|
||||||
_relayer: string,
|
_relayer: string,
|
||||||
_fee: string,
|
_fee: string,
|
||||||
_refund: string,
|
_refund: string,
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface snarkProofs {
|
export interface snarkProofs {
|
||||||
proof: string;
|
proof: string;
|
||||||
args: snarkArgs;
|
args: snarkArgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let groth16: any;
|
let groth16: any;
|
||||||
|
|
||||||
export async function initGroth16() {
|
export async function initGroth16() {
|
||||||
if (!groth16) {
|
if (!groth16) {
|
||||||
groth16 = await websnarkGroth({ wasmInitialMemory: 2000 });
|
groth16 = await websnarkGroth({ wasmInitialMemory: 2000 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function calculateSnarkProof(
|
export async function calculateSnarkProof(
|
||||||
input: snarkInputs,
|
input: snarkInputs,
|
||||||
circuit: object,
|
circuit: object,
|
||||||
provingKey: ArrayBuffer,
|
provingKey: ArrayBuffer,
|
||||||
): Promise<snarkProofs> {
|
): Promise<snarkProofs> {
|
||||||
if (!groth16) {
|
if (!groth16) {
|
||||||
await initGroth16();
|
await initGroth16();
|
||||||
}
|
}
|
||||||
|
|
||||||
const snarkInput = {
|
const snarkInput = {
|
||||||
root: input.root,
|
root: input.root,
|
||||||
nullifierHash: BigInt(input.nullifierHex).toString(),
|
nullifierHash: BigInt(input.nullifierHex).toString(),
|
||||||
recipient: BigInt(input.recipient as string),
|
recipient: BigInt(input.recipient as string),
|
||||||
relayer: BigInt(input.relayer as string),
|
relayer: BigInt(input.relayer as string),
|
||||||
fee: input.fee,
|
fee: input.fee,
|
||||||
refund: input.refund,
|
refund: input.refund,
|
||||||
|
|
||||||
nullifier: input.nullifier,
|
nullifier: input.nullifier,
|
||||||
secret: input.secret,
|
secret: input.secret,
|
||||||
pathElements: input.pathElements,
|
pathElements: input.pathElements,
|
||||||
pathIndices: input.pathIndices,
|
pathIndices: input.pathIndices,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Start generating SNARK proof', snarkInput);
|
console.log('Start generating SNARK proof', snarkInput);
|
||||||
console.time('SNARK proof time');
|
console.time('SNARK proof time');
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(await groth16, snarkInput, circuit, provingKey);
|
const proofData = await websnarkUtils.genWitnessAndProve(
|
||||||
const proof = websnarkUtils.toSolidityInput(proofData).proof;
|
await groth16,
|
||||||
console.timeEnd('SNARK proof time');
|
snarkInput,
|
||||||
|
circuit,
|
||||||
|
provingKey,
|
||||||
|
);
|
||||||
|
const proof = websnarkUtils.toSolidityInput(proofData).proof;
|
||||||
|
console.timeEnd('SNARK proof time');
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
toFixedHex(input.root, 32),
|
toFixedHex(input.root, 32),
|
||||||
toFixedHex(input.nullifierHex, 32),
|
toFixedHex(input.nullifierHex, 32),
|
||||||
input.recipient,
|
input.recipient,
|
||||||
input.relayer,
|
input.relayer,
|
||||||
toFixedHex(input.fee, 32),
|
toFixedHex(input.fee, 32),
|
||||||
toFixedHex(input.refund, 32),
|
toFixedHex(input.refund, 32),
|
||||||
] as snarkArgs;
|
] as snarkArgs;
|
||||||
|
|
||||||
return { proof, args };
|
return { proof, args };
|
||||||
}
|
}
|
||||||
|
|||||||
88
src/zip.ts
88
src/zip.ts
@@ -3,66 +3,68 @@ import { fetchData } from './providers';
|
|||||||
import { bytesToBase64, digest } from './utils';
|
import { bytesToBase64, digest } from './utils';
|
||||||
|
|
||||||
export function zipAsync(file: AsyncZippable): Promise<Uint8Array> {
|
export function zipAsync(file: AsyncZippable): Promise<Uint8Array> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
zip(file, { mtime: new Date('1/1/1980') }, (err, data) => {
|
zip(file, { mtime: new Date('1/1/1980') }, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
rej(err);
|
rej(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res(data);
|
res(data);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unzipAsync(data: Uint8Array): Promise<Unzipped> {
|
export function unzipAsync(data: Uint8Array): Promise<Unzipped> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
unzip(data, {}, (err, data) => {
|
unzip(data, {}, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
rej(err);
|
rej(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
res(data);
|
res(data);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadZip<T>({
|
export async function downloadZip<T>({
|
||||||
staticUrl = '',
|
staticUrl = '',
|
||||||
zipName,
|
zipName,
|
||||||
zipDigest,
|
zipDigest,
|
||||||
parseJson = true,
|
parseJson = true,
|
||||||
}: {
|
}: {
|
||||||
staticUrl?: string;
|
staticUrl?: string;
|
||||||
zipName: string;
|
zipName: string;
|
||||||
zipDigest?: string;
|
zipDigest?: string;
|
||||||
parseJson?: boolean;
|
parseJson?: boolean;
|
||||||
}): Promise<T> {
|
}): Promise<T> {
|
||||||
const url = `${staticUrl}/${zipName}.zip`;
|
const url = `${staticUrl}/${zipName}.zip`;
|
||||||
|
|
||||||
const resp = (await fetchData(url, {
|
const resp = (await fetchData(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
returnResponse: true,
|
returnResponse: true,
|
||||||
})) as Response;
|
})) as Response;
|
||||||
|
|
||||||
const data = new Uint8Array(await resp.arrayBuffer());
|
const data = new Uint8Array(await resp.arrayBuffer());
|
||||||
|
|
||||||
// If the zip has digest value, compare it
|
// If the zip has digest value, compare it
|
||||||
if (zipDigest) {
|
if (zipDigest) {
|
||||||
const hash = 'sha384-' + bytesToBase64(await digest(data));
|
const hash = 'sha384-' + bytesToBase64(await digest(data));
|
||||||
|
|
||||||
if (zipDigest !== hash) {
|
if (zipDigest !== hash) {
|
||||||
const errMsg = `Invalid digest hash for file ${url}, wants ${zipDigest} has ${hash}`;
|
const errMsg = `Invalid digest hash for file ${url}, wants ${zipDigest} has ${hash}`;
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const { [zipName]: content } = await unzipAsync(data);
|
const { [zipName]: content } = await unzipAsync(data);
|
||||||
|
|
||||||
console.log(`Downloaded ${url}${zipDigest ? ` ( Digest: ${zipDigest} )` : ''}`);
|
console.log(
|
||||||
|
`Downloaded ${url}${zipDigest ? ` ( Digest: ${zipDigest} )` : ''}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (parseJson) {
|
if (parseJson) {
|
||||||
return JSON.parse(new TextDecoder().decode(content)) as T;
|
return JSON.parse(new TextDecoder().decode(content)) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
return content as T;
|
return content as T;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,47 +12,65 @@ const { getSigners } = ethers;
|
|||||||
const NOTES_COUNT = 100;
|
const NOTES_COUNT = 100;
|
||||||
|
|
||||||
describe('./src/deposit.ts', function () {
|
describe('./src/deposit.ts', function () {
|
||||||
const instanceFixture = async () => {
|
const instanceFixture = async () => {
|
||||||
const [owner] = await getSigners();
|
const [owner] = await getSigners();
|
||||||
|
|
||||||
const Hasher = (await (await deployHasher(owner)).wait())?.contractAddress as string;
|
const Hasher = (await (await deployHasher(owner)).wait())
|
||||||
|
?.contractAddress as string;
|
||||||
|
|
||||||
const Verifier = await new Verifier__factory(owner).deploy();
|
const Verifier = await new Verifier__factory(owner).deploy();
|
||||||
|
|
||||||
const Instance = await new ETHTornado__factory(owner).deploy(Verifier.target, Hasher, 1n, 20);
|
const Instance = await new ETHTornado__factory(owner).deploy(
|
||||||
|
Verifier.target,
|
||||||
|
Hasher,
|
||||||
|
1n,
|
||||||
|
20,
|
||||||
|
);
|
||||||
|
|
||||||
return { Instance };
|
return { Instance };
|
||||||
};
|
};
|
||||||
|
|
||||||
it('Deposit New Note', async function () {
|
it('Deposit New Note', async function () {
|
||||||
const { Instance } = await loadFixture(instanceFixture);
|
const { Instance } = await loadFixture(instanceFixture);
|
||||||
|
|
||||||
const [owner] = await getSigners();
|
const [owner] = await getSigners();
|
||||||
|
|
||||||
const netId = Number((await owner.provider.getNetwork()).chainId);
|
const netId = Number((await owner.provider.getNetwork()).chainId);
|
||||||
|
|
||||||
const deposit = await Deposit.createNote({ currency: 'eth', amount: formatEther(1), netId });
|
const deposit = await Deposit.createNote({
|
||||||
|
currency: 'eth',
|
||||||
|
amount: formatEther(1),
|
||||||
|
netId,
|
||||||
|
});
|
||||||
|
|
||||||
const resp = await Instance.deposit(deposit.commitmentHex, { value: 1n });
|
const resp = await Instance.deposit(deposit.commitmentHex, {
|
||||||
|
value: 1n,
|
||||||
|
});
|
||||||
|
|
||||||
await expect(resp).to.emit(Instance, 'Deposit').withArgs(deposit.commitmentHex, 0, anyValue);
|
await expect(resp)
|
||||||
|
.to.emit(Instance, 'Deposit')
|
||||||
|
.withArgs(deposit.commitmentHex, 0, anyValue);
|
||||||
|
|
||||||
expect(await Instance.commitments(deposit.commitmentHex)).to.be.true;
|
expect(await Instance.commitments(deposit.commitmentHex)).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
xit(`Creating ${NOTES_COUNT} random notes`, async function () {
|
xit(`Creating ${NOTES_COUNT} random notes`, async function () {
|
||||||
const notes = (await Promise.all(
|
const notes = (await Promise.all(
|
||||||
// eslint-disable-next-line prefer-spread
|
// eslint-disable-next-line prefer-spread
|
||||||
Array.apply(null, Array(NOTES_COUNT)).map(() =>
|
Array.apply(null, Array(NOTES_COUNT)).map(() =>
|
||||||
Deposit.createNote({ currency: 'eth', amount: '0.1', netId: 31337 }),
|
Deposit.createNote({
|
||||||
),
|
currency: 'eth',
|
||||||
)) as Deposit[];
|
amount: '0.1',
|
||||||
|
netId: 31337,
|
||||||
notes.forEach(({ noteHex, commitmentHex, nullifierHex }) => {
|
}),
|
||||||
// ((secret.length: 31) + (nullifier.length: 31)) * 2 + (prefix: 2) = 126
|
),
|
||||||
expect(noteHex.length === 126).to.be.true;
|
)) as Deposit[];
|
||||||
expect(commitmentHex.length === 66).to.be.true;
|
|
||||||
expect(nullifierHex.length === 66).to.be.true;
|
notes.forEach(({ noteHex, commitmentHex, nullifierHex }) => {
|
||||||
|
// ((secret.length: 31) + (nullifier.length: 31)) * 2 + (prefix: 2) = 126
|
||||||
|
expect(noteHex.length === 126).to.be.true;
|
||||||
|
expect(commitmentHex.length === 66).to.be.true;
|
||||||
|
expect(nullifierHex.length === 66).to.be.true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { describe } from 'mocha';
|
|||||||
import { ethers } from 'hardhat';
|
import { ethers } from 'hardhat';
|
||||||
|
|
||||||
describe('Tornado Core', function () {
|
describe('Tornado Core', function () {
|
||||||
it('Get Provider', async function () {
|
it('Get Provider', async function () {
|
||||||
const [owner] = await ethers.getSigners();
|
const [owner] = await ethers.getSigners();
|
||||||
|
|
||||||
console.log(owner);
|
console.log(owner);
|
||||||
|
|
||||||
const { provider } = owner;
|
const { provider } = owner;
|
||||||
|
|
||||||
console.log(await provider.getBlock('latest'));
|
console.log(await provider.getBlock('latest'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,171 +3,171 @@ const path = require('path');
|
|||||||
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
|
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
|
||||||
|
|
||||||
const esbuildLoader = {
|
const esbuildLoader = {
|
||||||
test: /\.ts?$/,
|
test: /\.ts?$/,
|
||||||
loader: 'esbuild-loader',
|
loader: 'esbuild-loader',
|
||||||
options: {
|
options: {
|
||||||
loader: 'ts',
|
loader: 'ts',
|
||||||
target: 'es2022',
|
target: 'es2022',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const commonAlias = {
|
const commonAlias = {
|
||||||
fs: false,
|
fs: false,
|
||||||
'path': false,
|
'path': false,
|
||||||
'url': false,
|
'url': false,
|
||||||
'worker_threads': false,
|
'worker_threads': false,
|
||||||
'fflate': 'fflate/browser',
|
'fflate': 'fflate/browser',
|
||||||
'http-proxy-agent': false,
|
'http-proxy-agent': false,
|
||||||
'https-proxy-agent': false,
|
'https-proxy-agent': false,
|
||||||
'socks-proxy-agent': false,
|
'socks-proxy-agent': false,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
{
|
{
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
module: {
|
module: {
|
||||||
rules: [esbuildLoader]
|
rules: [esbuildLoader]
|
||||||
|
},
|
||||||
|
entry: './src/index.ts',
|
||||||
|
output: {
|
||||||
|
filename: 'tornado.umd.js',
|
||||||
|
path: path.resolve(__dirname, './dist'),
|
||||||
|
library: 'Tornado',
|
||||||
|
libraryTarget: 'umd'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new NodePolyfillPlugin(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
alias: {
|
||||||
|
...commonAlias,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
entry: './src/index.ts',
|
{
|
||||||
output: {
|
mode: 'production',
|
||||||
filename: 'tornado.umd.js',
|
module: {
|
||||||
path: path.resolve(__dirname, './dist'),
|
rules: [esbuildLoader]
|
||||||
library: 'Tornado',
|
},
|
||||||
libraryTarget: 'umd'
|
entry: './src/index.ts',
|
||||||
|
output: {
|
||||||
|
filename: 'tornado.umd.min.js',
|
||||||
|
path: path.resolve(__dirname, './dist'),
|
||||||
|
library: 'Tornado',
|
||||||
|
libraryTarget: 'umd'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new NodePolyfillPlugin(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
alias: {
|
||||||
|
...commonAlias,
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
{
|
||||||
new NodePolyfillPlugin(),
|
mode: 'production',
|
||||||
],
|
module: {
|
||||||
resolve: {
|
rules: [esbuildLoader]
|
||||||
extensions: ['.tsx', '.ts', '.js'],
|
},
|
||||||
alias: {
|
entry: './src/merkleTreeWorker.ts',
|
||||||
...commonAlias,
|
output: {
|
||||||
}
|
filename: 'merkleTreeWorker.umd.js',
|
||||||
|
path: path.resolve(__dirname, './dist'),
|
||||||
|
libraryTarget: 'umd'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new NodePolyfillPlugin(),
|
||||||
|
new BannerPlugin({
|
||||||
|
banner: 'globalThis.process = { browser: true, env: {}, };\n',
|
||||||
|
raw: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
alias: {
|
||||||
|
...commonAlias,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
optimization: {
|
{
|
||||||
minimize: false,
|
mode: 'production',
|
||||||
}
|
module: {
|
||||||
},
|
rules: [esbuildLoader]
|
||||||
{
|
},
|
||||||
mode: 'production',
|
entry: './src/merkleTreeWorker.ts',
|
||||||
module: {
|
output: {
|
||||||
rules: [esbuildLoader]
|
filename: 'merkleTreeWorker.umd.min.js',
|
||||||
|
path: path.resolve(__dirname, './dist'),
|
||||||
|
libraryTarget: 'umd'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new NodePolyfillPlugin(),
|
||||||
|
new BannerPlugin({
|
||||||
|
banner: 'globalThis.process = { browser: true, env: {}, };',
|
||||||
|
raw: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
alias: {
|
||||||
|
...commonAlias,
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
entry: './src/index.ts',
|
{
|
||||||
output: {
|
mode: 'production',
|
||||||
filename: 'tornado.umd.min.js',
|
module: {
|
||||||
path: path.resolve(__dirname, './dist'),
|
rules: [esbuildLoader]
|
||||||
library: 'Tornado',
|
},
|
||||||
libraryTarget: 'umd'
|
entry: './src/contracts.ts',
|
||||||
|
output: {
|
||||||
|
filename: 'tornadoContracts.umd.js',
|
||||||
|
path: path.resolve(__dirname, './dist'),
|
||||||
|
library: 'TornadoContracts',
|
||||||
|
libraryTarget: 'umd'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new NodePolyfillPlugin(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
alias: {
|
||||||
|
...commonAlias,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
plugins: [
|
{
|
||||||
new NodePolyfillPlugin(),
|
mode: 'production',
|
||||||
],
|
module: {
|
||||||
resolve: {
|
rules: [esbuildLoader]
|
||||||
extensions: ['.tsx', '.ts', '.js'],
|
},
|
||||||
alias: {
|
entry: './src/contracts.ts',
|
||||||
...commonAlias,
|
output: {
|
||||||
}
|
filename: 'tornadoContracts.umd.min.js',
|
||||||
|
path: path.resolve(__dirname, './dist'),
|
||||||
|
library: 'TornadoContracts',
|
||||||
|
libraryTarget: 'umd'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new NodePolyfillPlugin(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
alias: {
|
||||||
|
...commonAlias,
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'production',
|
|
||||||
module: {
|
|
||||||
rules: [esbuildLoader]
|
|
||||||
},
|
|
||||||
entry: './src/merkleTreeWorker.ts',
|
|
||||||
output: {
|
|
||||||
filename: 'merkleTreeWorker.umd.js',
|
|
||||||
path: path.resolve(__dirname, './dist'),
|
|
||||||
libraryTarget: 'umd'
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new NodePolyfillPlugin(),
|
|
||||||
new BannerPlugin({
|
|
||||||
banner: 'globalThis.process = { browser: true, env: {}, };\n',
|
|
||||||
raw: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.tsx', '.ts', '.js'],
|
|
||||||
alias: {
|
|
||||||
...commonAlias,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
optimization: {
|
|
||||||
minimize: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'production',
|
|
||||||
module: {
|
|
||||||
rules: [esbuildLoader]
|
|
||||||
},
|
|
||||||
entry: './src/merkleTreeWorker.ts',
|
|
||||||
output: {
|
|
||||||
filename: 'merkleTreeWorker.umd.min.js',
|
|
||||||
path: path.resolve(__dirname, './dist'),
|
|
||||||
libraryTarget: 'umd'
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new NodePolyfillPlugin(),
|
|
||||||
new BannerPlugin({
|
|
||||||
banner: 'globalThis.process = { browser: true, env: {}, };',
|
|
||||||
raw: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.tsx', '.ts', '.js'],
|
|
||||||
alias: {
|
|
||||||
...commonAlias,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'production',
|
|
||||||
module: {
|
|
||||||
rules: [esbuildLoader]
|
|
||||||
},
|
|
||||||
entry: './src/contracts.ts',
|
|
||||||
output: {
|
|
||||||
filename: 'tornadoContracts.umd.js',
|
|
||||||
path: path.resolve(__dirname, './dist'),
|
|
||||||
library: 'TornadoContracts',
|
|
||||||
libraryTarget: 'umd'
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new NodePolyfillPlugin(),
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.tsx', '.ts', '.js'],
|
|
||||||
alias: {
|
|
||||||
...commonAlias,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
optimization: {
|
|
||||||
minimize: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mode: 'production',
|
|
||||||
module: {
|
|
||||||
rules: [esbuildLoader]
|
|
||||||
},
|
|
||||||
entry: './src/contracts.ts',
|
|
||||||
output: {
|
|
||||||
filename: 'tornadoContracts.umd.min.js',
|
|
||||||
path: path.resolve(__dirname, './dist'),
|
|
||||||
library: 'TornadoContracts',
|
|
||||||
libraryTarget: 'umd'
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new NodePolyfillPlugin(),
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.tsx', '.ts', '.js'],
|
|
||||||
alias: {
|
|
||||||
...commonAlias,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user