Remove broken external oracle and fixed EIP-1559 gasPrice calculation #2

Open
tornadocontrib wants to merge 1 commits from tornadocontrib/tornado-oracles:main into main
14 changed files with 961 additions and 165 deletions

File diff suppressed because one or more lines are too long

@ -1,6 +1,6 @@
{
"name": "@tornado/tornado-oracles",
"version": "3.3.0",
"version": "3.4.0",
"description": "Oracles for Tornado-specific transactions & actions",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
@ -29,7 +29,6 @@
"author": "Theo",
"license": "MIT",
"dependencies": {
"@tornado/gas-price-oracle": "^0.5.3",
"@tornado/tornado-config": "^2.0.0",
"@types/node": "^20.5.1",
"bignumber.js": "^9.1.1",

@ -9,6 +9,7 @@ export enum ChainId {
OPTIMISM = 10,
ARBITRUM = 42161,
AVAX = 43114,
SEPOLIA = 11155111,
}
export enum InstanceTokenSymbol {
@ -72,6 +73,12 @@ export const defaultGasPrices: GasPricesConfig = {
standard: 25,
low: 25,
},
[ChainId.SEPOLIA]: {
instant: 80,
fast: 50,
standard: 25,
low: 8,
},
};
type GasLimitConfig = {
@ -87,6 +94,7 @@ export const defaultWithdrawalGasLimit: GasLimitConfig = {
[ChainId.BSC]: 390000,
[ChainId.POLYGON]: 390000,
[ChainId.XDAI]: 390000,
[ChainId.SEPOLIA]: 550000,
};
type InstanceTokenGasLimitConfig = {
@ -102,5 +110,6 @@ export const defaultInstanceTokensGasLimit: InstanceTokenGasLimitConfig = {
};
export const optimismL1FeeOracleAddress = '0x420000000000000000000000000000000000000F';
export const offchainOracleAddress = '0x07D91f5fb9Bf7798734C3f606dB065549F6893bb';
export const multiCallAddress = '0xda3c19c6fe954576707fa24695efb830d9cca1ca';
export const offchainOracleAddress = '0x00000000000D6FFc74A8feb35aF5827bf57f6786';
export const sepoliaOffchainOracleAddress = '0x1f89EAF03E5b260Bc6D4Ae3c3334b1B750F3e127';
export const multiCallAddress = '0xcA11bde05977b3631167028862bE2a173976CA11';

@ -1,30 +1,137 @@
/* Autogenerated file. Do not edit manually. */
/* tslint:disable */
/* eslint-disable */
import type { BaseContract, BigNumber, BytesLike, CallOverrides, PopulatedTransaction, Signer, utils } from 'ethers';
import type {
BaseContract,
BigNumber,
BigNumberish,
BytesLike,
CallOverrides,
ContractTransaction,
PayableOverrides,
PopulatedTransaction,
Signer,
utils,
} from 'ethers';
import type { FunctionFragment, Result } from '@ethersproject/abi';
import type { Listener, Provider } from '@ethersproject/providers';
import type { TypedEventFilter, TypedEvent, TypedListener, OnEvent } from './common';
export declare namespace MultiCall {
export type CallStruct = { to: string; data: BytesLike };
export declare namespace Multicall3 {
export type CallStruct = { target: string; callData: BytesLike };
export type CallStructOutput = [string, string] & {
to: string;
data: string;
target: string;
callData: string;
};
export type Call3Struct = {
target: string;
allowFailure: boolean;
callData: BytesLike;
};
export type Call3StructOutput = [string, boolean, string] & {
target: string;
allowFailure: boolean;
callData: string;
};
export type ResultStruct = { success: boolean; returnData: BytesLike };
export type ResultStructOutput = [boolean, string] & {
success: boolean;
returnData: string;
};
export type Call3ValueStruct = {
target: string;
allowFailure: boolean;
value: BigNumberish;
callData: BytesLike;
};
export type Call3ValueStructOutput = [string, boolean, BigNumber, string] & {
target: string;
allowFailure: boolean;
value: BigNumber;
callData: string;
};
}
export interface MulticallAbiInterface extends utils.Interface {
functions: {
'multicall((address,bytes)[])': FunctionFragment;
'aggregate((address,bytes)[])': FunctionFragment;
'aggregate3((address,bool,bytes)[])': FunctionFragment;
'aggregate3Value((address,bool,uint256,bytes)[])': FunctionFragment;
'blockAndAggregate((address,bytes)[])': FunctionFragment;
'getBasefee()': FunctionFragment;
'getBlockHash(uint256)': FunctionFragment;
'getBlockNumber()': FunctionFragment;
'getChainId()': FunctionFragment;
'getCurrentBlockCoinbase()': FunctionFragment;
'getCurrentBlockDifficulty()': FunctionFragment;
'getCurrentBlockGasLimit()': FunctionFragment;
'getCurrentBlockTimestamp()': FunctionFragment;
'getEthBalance(address)': FunctionFragment;
'getLastBlockHash()': FunctionFragment;
'tryAggregate(bool,(address,bytes)[])': FunctionFragment;
'tryBlockAndAggregate(bool,(address,bytes)[])': FunctionFragment;
};
getFunction(nameOrSignatureOrTopic: 'multicall'): FunctionFragment;
getFunction(
nameOrSignatureOrTopic:
| 'aggregate'
| 'aggregate3'
| 'aggregate3Value'
| 'blockAndAggregate'
| 'getBasefee'
| 'getBlockHash'
| 'getBlockNumber'
| 'getChainId'
| 'getCurrentBlockCoinbase'
| 'getCurrentBlockDifficulty'
| 'getCurrentBlockGasLimit'
| 'getCurrentBlockTimestamp'
| 'getEthBalance'
| 'getLastBlockHash'
| 'tryAggregate'
| 'tryBlockAndAggregate',
): FunctionFragment;
encodeFunctionData(functionFragment: 'multicall', values: [MultiCall.CallStruct[]]): string;
encodeFunctionData(functionFragment: 'aggregate', values: [Multicall3.CallStruct[]]): string;
encodeFunctionData(functionFragment: 'aggregate3', values: [Multicall3.Call3Struct[]]): string;
encodeFunctionData(functionFragment: 'aggregate3Value', values: [Multicall3.Call3ValueStruct[]]): string;
encodeFunctionData(functionFragment: 'blockAndAggregate', values: [Multicall3.CallStruct[]]): string;
encodeFunctionData(functionFragment: 'getBasefee', values?: undefined): string;
encodeFunctionData(functionFragment: 'getBlockHash', values: [BigNumberish]): string;
encodeFunctionData(functionFragment: 'getBlockNumber', values?: undefined): string;
encodeFunctionData(functionFragment: 'getChainId', values?: undefined): string;
encodeFunctionData(functionFragment: 'getCurrentBlockCoinbase', values?: undefined): string;
encodeFunctionData(functionFragment: 'getCurrentBlockDifficulty', values?: undefined): string;
encodeFunctionData(functionFragment: 'getCurrentBlockGasLimit', values?: undefined): string;
encodeFunctionData(functionFragment: 'getCurrentBlockTimestamp', values?: undefined): string;
encodeFunctionData(functionFragment: 'getEthBalance', values: [string]): string;
encodeFunctionData(functionFragment: 'getLastBlockHash', values?: undefined): string;
encodeFunctionData(functionFragment: 'tryAggregate', values: [boolean, Multicall3.CallStruct[]]): string;
encodeFunctionData(functionFragment: 'tryBlockAndAggregate', values: [boolean, Multicall3.CallStruct[]]): string;
decodeFunctionResult(functionFragment: 'multicall', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'aggregate', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'aggregate3', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'aggregate3Value', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'blockAndAggregate', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'getBasefee', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'getBlockHash', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'getBlockNumber', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'getChainId', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'getCurrentBlockCoinbase', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'getCurrentBlockDifficulty', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'getCurrentBlockGasLimit', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'getCurrentBlockTimestamp', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'getEthBalance', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'getLastBlockHash', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'tryAggregate', data: BytesLike): Result;
decodeFunctionResult(functionFragment: 'tryBlockAndAggregate', data: BytesLike): Result;
events: {};
}
@ -52,31 +159,275 @@ export interface MulticallAbi extends BaseContract {
removeListener: OnEvent<this>;
functions: {
multicall(
calls: MultiCall.CallStruct[],
overrides?: CallOverrides,
): Promise<[string[], boolean[]] & { results: string[]; success: boolean[] }>;
aggregate(
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<ContractTransaction>;
aggregate3(
calls: Multicall3.Call3Struct[],
overrides?: PayableOverrides & { from?: string },
): Promise<ContractTransaction>;
aggregate3Value(
calls: Multicall3.Call3ValueStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<ContractTransaction>;
blockAndAggregate(
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<ContractTransaction>;
getBasefee(overrides?: CallOverrides): Promise<[BigNumber] & { basefee: BigNumber }>;
getBlockHash(blockNumber: BigNumberish, overrides?: CallOverrides): Promise<[string] & { blockHash: string }>;
getBlockNumber(overrides?: CallOverrides): Promise<[BigNumber] & { blockNumber: BigNumber }>;
getChainId(overrides?: CallOverrides): Promise<[BigNumber] & { chainid: BigNumber }>;
getCurrentBlockCoinbase(overrides?: CallOverrides): Promise<[string] & { coinbase: string }>;
getCurrentBlockDifficulty(overrides?: CallOverrides): Promise<[BigNumber] & { difficulty: BigNumber }>;
getCurrentBlockGasLimit(overrides?: CallOverrides): Promise<[BigNumber] & { gaslimit: BigNumber }>;
getCurrentBlockTimestamp(overrides?: CallOverrides): Promise<[BigNumber] & { timestamp: BigNumber }>;
getEthBalance(addr: string, overrides?: CallOverrides): Promise<[BigNumber] & { balance: BigNumber }>;
getLastBlockHash(overrides?: CallOverrides): Promise<[string] & { blockHash: string }>;
tryAggregate(
requireSuccess: boolean,
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<ContractTransaction>;
tryBlockAndAggregate(
requireSuccess: boolean,
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<ContractTransaction>;
};
multicall(
calls: MultiCall.CallStruct[],
overrides?: CallOverrides,
): Promise<[string[], boolean[]] & { results: string[]; success: boolean[] }>;
aggregate(
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<ContractTransaction>;
aggregate3(
calls: Multicall3.Call3Struct[],
overrides?: PayableOverrides & { from?: string },
): Promise<ContractTransaction>;
aggregate3Value(
calls: Multicall3.Call3ValueStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<ContractTransaction>;
blockAndAggregate(
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<ContractTransaction>;
getBasefee(overrides?: CallOverrides): Promise<BigNumber>;
getBlockHash(blockNumber: BigNumberish, overrides?: CallOverrides): Promise<string>;
getBlockNumber(overrides?: CallOverrides): Promise<BigNumber>;
getChainId(overrides?: CallOverrides): Promise<BigNumber>;
getCurrentBlockCoinbase(overrides?: CallOverrides): Promise<string>;
getCurrentBlockDifficulty(overrides?: CallOverrides): Promise<BigNumber>;
getCurrentBlockGasLimit(overrides?: CallOverrides): Promise<BigNumber>;
getCurrentBlockTimestamp(overrides?: CallOverrides): Promise<BigNumber>;
getEthBalance(addr: string, overrides?: CallOverrides): Promise<BigNumber>;
getLastBlockHash(overrides?: CallOverrides): Promise<string>;
tryAggregate(
requireSuccess: boolean,
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<ContractTransaction>;
tryBlockAndAggregate(
requireSuccess: boolean,
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<ContractTransaction>;
callStatic: {
multicall(
calls: MultiCall.CallStruct[],
aggregate(
calls: Multicall3.CallStruct[],
overrides?: CallOverrides,
): Promise<[string[], boolean[]] & { results: string[]; success: boolean[] }>;
): Promise<[BigNumber, string[]] & { blockNumber: BigNumber; returnData: string[] }>;
aggregate3(calls: Multicall3.Call3Struct[], overrides?: CallOverrides): Promise<Multicall3.ResultStructOutput[]>;
aggregate3Value(
calls: Multicall3.Call3ValueStruct[],
overrides?: CallOverrides,
): Promise<Multicall3.ResultStructOutput[]>;
blockAndAggregate(
calls: Multicall3.CallStruct[],
overrides?: CallOverrides,
): Promise<
[BigNumber, string, Multicall3.ResultStructOutput[]] & {
blockNumber: BigNumber;
blockHash: string;
returnData: Multicall3.ResultStructOutput[];
}
>;
getBasefee(overrides?: CallOverrides): Promise<BigNumber>;
getBlockHash(blockNumber: BigNumberish, overrides?: CallOverrides): Promise<string>;
getBlockNumber(overrides?: CallOverrides): Promise<BigNumber>;
getChainId(overrides?: CallOverrides): Promise<BigNumber>;
getCurrentBlockCoinbase(overrides?: CallOverrides): Promise<string>;
getCurrentBlockDifficulty(overrides?: CallOverrides): Promise<BigNumber>;
getCurrentBlockGasLimit(overrides?: CallOverrides): Promise<BigNumber>;
getCurrentBlockTimestamp(overrides?: CallOverrides): Promise<BigNumber>;
getEthBalance(addr: string, overrides?: CallOverrides): Promise<BigNumber>;
getLastBlockHash(overrides?: CallOverrides): Promise<string>;
tryAggregate(
requireSuccess: boolean,
calls: Multicall3.CallStruct[],
overrides?: CallOverrides,
): Promise<Multicall3.ResultStructOutput[]>;
tryBlockAndAggregate(
requireSuccess: boolean,
calls: Multicall3.CallStruct[],
overrides?: CallOverrides,
): Promise<
[BigNumber, string, Multicall3.ResultStructOutput[]] & {
blockNumber: BigNumber;
blockHash: string;
returnData: Multicall3.ResultStructOutput[];
}
>;
};
filters: {};
estimateGas: {
multicall(calls: MultiCall.CallStruct[], overrides?: CallOverrides): Promise<BigNumber>;
aggregate(calls: Multicall3.CallStruct[], overrides?: PayableOverrides & { from?: string }): Promise<BigNumber>;
aggregate3(calls: Multicall3.Call3Struct[], overrides?: PayableOverrides & { from?: string }): Promise<BigNumber>;
aggregate3Value(
calls: Multicall3.Call3ValueStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<BigNumber>;
blockAndAggregate(
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<BigNumber>;
getBasefee(overrides?: CallOverrides): Promise<BigNumber>;
getBlockHash(blockNumber: BigNumberish, overrides?: CallOverrides): Promise<BigNumber>;
getBlockNumber(overrides?: CallOverrides): Promise<BigNumber>;
getChainId(overrides?: CallOverrides): Promise<BigNumber>;
getCurrentBlockCoinbase(overrides?: CallOverrides): Promise<BigNumber>;
getCurrentBlockDifficulty(overrides?: CallOverrides): Promise<BigNumber>;
getCurrentBlockGasLimit(overrides?: CallOverrides): Promise<BigNumber>;
getCurrentBlockTimestamp(overrides?: CallOverrides): Promise<BigNumber>;
getEthBalance(addr: string, overrides?: CallOverrides): Promise<BigNumber>;
getLastBlockHash(overrides?: CallOverrides): Promise<BigNumber>;
tryAggregate(
requireSuccess: boolean,
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<BigNumber>;
tryBlockAndAggregate(
requireSuccess: boolean,
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<BigNumber>;
};
populateTransaction: {
multicall(calls: MultiCall.CallStruct[], overrides?: CallOverrides): Promise<PopulatedTransaction>;
aggregate(
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<PopulatedTransaction>;
aggregate3(
calls: Multicall3.Call3Struct[],
overrides?: PayableOverrides & { from?: string },
): Promise<PopulatedTransaction>;
aggregate3Value(
calls: Multicall3.Call3ValueStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<PopulatedTransaction>;
blockAndAggregate(
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<PopulatedTransaction>;
getBasefee(overrides?: CallOverrides): Promise<PopulatedTransaction>;
getBlockHash(blockNumber: BigNumberish, overrides?: CallOverrides): Promise<PopulatedTransaction>;
getBlockNumber(overrides?: CallOverrides): Promise<PopulatedTransaction>;
getChainId(overrides?: CallOverrides): Promise<PopulatedTransaction>;
getCurrentBlockCoinbase(overrides?: CallOverrides): Promise<PopulatedTransaction>;
getCurrentBlockDifficulty(overrides?: CallOverrides): Promise<PopulatedTransaction>;
getCurrentBlockGasLimit(overrides?: CallOverrides): Promise<PopulatedTransaction>;
getCurrentBlockTimestamp(overrides?: CallOverrides): Promise<PopulatedTransaction>;
getEthBalance(addr: string, overrides?: CallOverrides): Promise<PopulatedTransaction>;
getLastBlockHash(overrides?: CallOverrides): Promise<PopulatedTransaction>;
tryAggregate(
requireSuccess: boolean,
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<PopulatedTransaction>;
tryBlockAndAggregate(
requireSuccess: boolean,
calls: Multicall3.CallStruct[],
overrides?: PayableOverrides & { from?: string },
): Promise<PopulatedTransaction>;
};
}

@ -1,12 +1,21 @@
import { OptimismL1FeeOracleAbi__factory, OffchainOracleAbi__factory, MulticallAbi__factory } from './';
import { optimismL1FeeOracleAddress, offchainOracleAddress, multiCallAddress } from '../config';
import {
optimismL1FeeOracleAddress,
offchainOracleAddress,
multiCallAddress,
sepoliaOffchainOracleAddress,
} from '../config';
import { Provider } from '@ethersproject/abstract-provider';
import { ChainId } from '../config';
export const getOptimismL1FeeOracle = (provider: Provider) => {
return OptimismL1FeeOracleAbi__factory.connect(optimismL1FeeOracleAddress, provider);
};
export const getOffchainOracleContract = (provider: Provider) => {
export const getOffchainOracleContract = (provider: Provider, chainId?: ChainId) => {
if (chainId === ChainId.SEPOLIA) {
return OffchainOracleAbi__factory.connect(sepoliaOffchainOracleAddress, provider);
}
return OffchainOracleAbi__factory.connect(offchainOracleAddress, provider);
};

@ -13,36 +13,438 @@ const _abi = [
components: [
{
internalType: 'address',
name: 'to',
name: 'target',
type: 'address',
},
{
internalType: 'bytes',
name: 'data',
name: 'callData',
type: 'bytes',
},
],
internalType: 'struct MultiCall.Call[]',
internalType: 'struct Multicall3.Call[]',
name: 'calls',
type: 'tuple[]',
},
],
name: 'multicall',
name: 'aggregate',
outputs: [
{
internalType: 'bytes[]',
name: 'results',
type: 'bytes[]',
internalType: 'uint256',
name: 'blockNumber',
type: 'uint256',
},
{
internalType: 'bool[]',
name: 'success',
type: 'bool[]',
internalType: 'bytes[]',
name: 'returnData',
type: 'bytes[]',
},
],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{
components: [
{
internalType: 'address',
name: 'target',
type: 'address',
},
{
internalType: 'bool',
name: 'allowFailure',
type: 'bool',
},
{
internalType: 'bytes',
name: 'callData',
type: 'bytes',
},
],
internalType: 'struct Multicall3.Call3[]',
name: 'calls',
type: 'tuple[]',
},
],
name: 'aggregate3',
outputs: [
{
components: [
{
internalType: 'bool',
name: 'success',
type: 'bool',
},
{
internalType: 'bytes',
name: 'returnData',
type: 'bytes',
},
],
internalType: 'struct Multicall3.Result[]',
name: 'returnData',
type: 'tuple[]',
},
],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{
components: [
{
internalType: 'address',
name: 'target',
type: 'address',
},
{
internalType: 'bool',
name: 'allowFailure',
type: 'bool',
},
{
internalType: 'uint256',
name: 'value',
type: 'uint256',
},
{
internalType: 'bytes',
name: 'callData',
type: 'bytes',
},
],
internalType: 'struct Multicall3.Call3Value[]',
name: 'calls',
type: 'tuple[]',
},
],
name: 'aggregate3Value',
outputs: [
{
components: [
{
internalType: 'bool',
name: 'success',
type: 'bool',
},
{
internalType: 'bytes',
name: 'returnData',
type: 'bytes',
},
],
internalType: 'struct Multicall3.Result[]',
name: 'returnData',
type: 'tuple[]',
},
],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{
components: [
{
internalType: 'address',
name: 'target',
type: 'address',
},
{
internalType: 'bytes',
name: 'callData',
type: 'bytes',
},
],
internalType: 'struct Multicall3.Call[]',
name: 'calls',
type: 'tuple[]',
},
],
name: 'blockAndAggregate',
outputs: [
{
internalType: 'uint256',
name: 'blockNumber',
type: 'uint256',
},
{
internalType: 'bytes32',
name: 'blockHash',
type: 'bytes32',
},
{
components: [
{
internalType: 'bool',
name: 'success',
type: 'bool',
},
{
internalType: 'bytes',
name: 'returnData',
type: 'bytes',
},
],
internalType: 'struct Multicall3.Result[]',
name: 'returnData',
type: 'tuple[]',
},
],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [],
name: 'getBasefee',
outputs: [
{
internalType: 'uint256',
name: 'basefee',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'blockNumber',
type: 'uint256',
},
],
name: 'getBlockHash',
outputs: [
{
internalType: 'bytes32',
name: 'blockHash',
type: 'bytes32',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getBlockNumber',
outputs: [
{
internalType: 'uint256',
name: 'blockNumber',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getChainId',
outputs: [
{
internalType: 'uint256',
name: 'chainid',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getCurrentBlockCoinbase',
outputs: [
{
internalType: 'address',
name: 'coinbase',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getCurrentBlockDifficulty',
outputs: [
{
internalType: 'uint256',
name: 'difficulty',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getCurrentBlockGasLimit',
outputs: [
{
internalType: 'uint256',
name: 'gaslimit',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getCurrentBlockTimestamp',
outputs: [
{
internalType: 'uint256',
name: 'timestamp',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'addr',
type: 'address',
},
],
name: 'getEthBalance',
outputs: [
{
internalType: 'uint256',
name: 'balance',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getLastBlockHash',
outputs: [
{
internalType: 'bytes32',
name: 'blockHash',
type: 'bytes32',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'bool',
name: 'requireSuccess',
type: 'bool',
},
{
components: [
{
internalType: 'address',
name: 'target',
type: 'address',
},
{
internalType: 'bytes',
name: 'callData',
type: 'bytes',
},
],
internalType: 'struct Multicall3.Call[]',
name: 'calls',
type: 'tuple[]',
},
],
name: 'tryAggregate',
outputs: [
{
components: [
{
internalType: 'bool',
name: 'success',
type: 'bool',
},
{
internalType: 'bytes',
name: 'returnData',
type: 'bytes',
},
],
internalType: 'struct Multicall3.Result[]',
name: 'returnData',
type: 'tuple[]',
},
],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{
internalType: 'bool',
name: 'requireSuccess',
type: 'bool',
},
{
components: [
{
internalType: 'address',
name: 'target',
type: 'address',
},
{
internalType: 'bytes',
name: 'callData',
type: 'bytes',
},
],
internalType: 'struct Multicall3.Call[]',
name: 'calls',
type: 'tuple[]',
},
],
name: 'tryBlockAndAggregate',
outputs: [
{
internalType: 'uint256',
name: 'blockNumber',
type: 'uint256',
},
{
internalType: 'bytes32',
name: 'blockHash',
type: 'bytes32',
},
{
components: [
{
internalType: 'bool',
name: 'success',
type: 'bool',
},
{
internalType: 'bytes',
name: 'returnData',
type: 'bytes',
},
],
internalType: 'struct Multicall3.Result[]',
name: 'returnData',
type: 'tuple[]',
},
],
stateMutability: 'payable',
type: 'function',
},
] as const;
export class MulticallAbi__factory {

@ -1,4 +1,3 @@
import { GasPriceOracle } from '@tornado/gas-price-oracle';
import { BigNumber, BigNumberish, ethers } from 'ethers';
import BigNumberFloat from 'bignumber.js';
import { parseUnits } from 'ethers/lib/utils';
@ -24,10 +23,9 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle {
protected provider: JsonRpcProvider;
public constructor(
public version: 4 | 5,
public version: 4 | 5 | 6,
protected chainId: ChainId,
rpcUrl: string,
protected oracle: GasPriceOracle,
) {
this.provider = new ethers.providers.JsonRpcProvider(rpcUrl);
}
@ -122,12 +120,41 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle {
* @param {number} [bumpPercent=0] Gas bump percent to prioritize transaction
* @returns {Promise<GasPriceParams>} Estimated gas price info in WEI (hexed) - legacy object with gasPrice property or
* EIP-1559 object with maxFeePerGas and maxPriorityFeePerGas properties
* NOTICE: It is recommended to bump fees for EIP-1559 transactions, because they can bump 12.5% per block
*/
async getGasPriceParams(speed?: LegacyGasPriceKey, bumpPercent: number = 0): Promise<GasPriceParams> {
// Use instant for BSC, because on this chain "fast" and "instant" differs more than third and "fast" transaction can take hours
if (!speed) speed = this.chainId === ChainId.BSC ? 'instant' : 'fast';
try {
return await this.oracle.getTxGasParams({ legacySpeed: speed, bumpPercent });
const [block, gasPrice, priorityFee] = await Promise.all([
this.provider.getBlock('latest'),
this.provider.getGasPrice(),
(async () => {
try {
return BigNumber.from(
(await this.provider.send('eth_maxPriorityFeePerGas', [])) || parseUnits('1.5', 'gwei'),
);
} catch {
return parseUnits('1.5', 'gwei');
}
})(),
]);
if (block?.baseFeePerGas?.gt(0)) {
const maxPriorityFeePerGas = priorityFee.toHexString();
const maxFeePerGas = bump(block.baseFeePerGas as BigNumber, bumpPercent)
.add(priorityFee)
.toHexString();
return {
maxFeePerGas,
maxPriorityFeePerGas,
};
}
return {
gasPrice: bump(gasPrice, bumpPercent).toHexString(),
};
} catch (e) {
return { gasPrice: bump(fromGweiToWeiHex(defaultGasPrices[this.chainId][speed]), bumpPercent).toHexString() };
}
@ -256,22 +283,33 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle {
tokenPriceInEth,
predefinedGasLimit,
predefinedGasPrice,
bumpGasLimitPercent,
bumpGasPricePercent,
}: GetWithdrawalFeeViaRelayerInput): Promise<HexadecimalStringifiedNumber> {
const relayerFee = this.calculateRelayerFeeInWei(relayerFeePercent, amount, decimals);
const { gasPrice, gasLimit } = await this.getGasParams({ tx, txType, predefinedGasLimit, predefinedGasPrice });
const { gasPrice, gasLimit } = await this.getGasParams({
tx,
txType,
predefinedGasLimit,
predefinedGasPrice,
bumpGasLimitPercent,
bumpGasPricePercent,
});
const gasCosts = BigNumber.from(gasPrice).mul(gasLimit);
if ((this.chainId === ChainId.MAINNET || this.chainId === ChainId.GOERLI) && currency.toLowerCase() != 'eth') {
if (!tokenPriceInEth) {
console.error('Token price is required argument, if not native chain token is withdrawed');
return '0';
}
const hasTokenPrice =
typeof tokenPriceInEth === 'object' && typeof (tokenPriceInEth as BigNumber)?.gt === 'function'
? Boolean((tokenPriceInEth as BigNumber).gt(0))
: Boolean(tokenPriceInEth);
if (hasTokenPrice) {
if (txType === 'user_withdrawal' && refundInEth === undefined)
refundInEth = this.calculateRefundInETH(gasPrice, currency.toLowerCase() as InstanceTokenSymbol);
const feeInEth = BigNumber.from(gasCosts).add(refundInEth || 0);
return convertETHToToken(feeInEth, decimals, tokenPriceInEth).add(relayerFee).toHexString();
return convertETHToToken(feeInEth, decimals, tokenPriceInEth as BigNumberish)
.add(relayerFee)
.toHexString();
}
return BigNumber.from(gasCosts).add(relayerFee).toHexString();

@ -1,22 +1,14 @@
import { defaultWithdrawalGasLimit } from './config';
import { TornadoFeeOracle } from './feeOracle';
import { ITornadoFeeOracle, TransactionData, TxType, LegacyGasPrices } from './types';
import { GasPriceOracle } from '@tornado/gas-price-oracle';
import { ITornadoFeeOracle, TransactionData, TxType } from './types';
import { bump } from './utils';
/**
* Oracle for V4 (old-version) transactions - estimates fee with predefined gas limit and without smart bumping
*/
export class TornadoFeeOracleV4 extends TornadoFeeOracle implements ITornadoFeeOracle {
public constructor(chainId: number, rpcUrl: string, defaultGasPrices?: LegacyGasPrices) {
const oracleConfig = {
chainId,
defaultRpc: rpcUrl,
defaultFallbackGasPrices: defaultGasPrices,
};
const gasPriceOracle = new GasPriceOracle(oracleConfig);
super(4, chainId, rpcUrl, gasPriceOracle);
public constructor(chainId: number, rpcUrl: string) {
super(4, chainId, rpcUrl);
}
async getGasLimit(tx?: TransactionData, type: TxType = 'other', bumpPercent: number = 0): Promise<number> {

@ -1,14 +1,6 @@
import { ChainId } from './config';
import { TornadoFeeOracle } from './feeOracle';
import {
ITornadoFeeOracle,
TransactionData,
TxType,
LegacyGasPrices,
LegacyGasPriceKey,
GasPriceParams,
} from './types';
import { GasPriceOracle } from '@tornado/gas-price-oracle';
import { ITornadoFeeOracle, TransactionData, TxType, LegacyGasPriceKey, GasPriceParams } from './types';
import { bump } from './utils';
import { TornadoFeeOracleV4 } from './feeOracleV4';
@ -18,20 +10,10 @@ import { TornadoFeeOracleV4 } from './feeOracleV4';
export class TornadoFeeOracleV5 extends TornadoFeeOracle implements ITornadoFeeOracle {
private fallbackFeeOracle: TornadoFeeOracleV4;
public constructor(chainId: number, rpcUrl: string, defaultGasPrices?: LegacyGasPrices) {
const oracleConfig = {
chainId,
defaultRpc: rpcUrl,
minPriority: chainId === ChainId.MAINNET || chainId === ChainId.GOERLI ? 2 : chainId === ChainId.BSC ? 3 : 0.05,
percentile: 5,
blocksCount: 20,
defaultFallbackGasPrices: defaultGasPrices,
};
const gasPriceOracle = new GasPriceOracle(oracleConfig);
public constructor(chainId: number, rpcUrl: string) {
super(5, chainId, rpcUrl);
super(5, chainId, rpcUrl, gasPriceOracle);
this.fallbackFeeOracle = new TornadoFeeOracleV4(chainId, rpcUrl, defaultGasPrices);
this.fallbackFeeOracle = new TornadoFeeOracleV4(chainId, rpcUrl);
}
async getGasLimit(tx?: TransactionData, type: TxType = 'other', bumpPercent: number = 20): Promise<number> {

73
src/feeOracleV6.ts Normal file

@ -0,0 +1,73 @@
import { ChainId } from './config';
import { TornadoFeeOracle } from './feeOracle';
import {
ITornadoFeeOracle,
TransactionData,
TxType,
LegacyGasPriceKey,
GasPriceParams,
GetWithdrawalFeeViaRelayerInput,
HexadecimalStringifiedNumber,
} from './types';
import { bump } from './utils';
import { TornadoFeeOracleV5 } from './feeOracleV5';
/**
* V6 version that would cover every available networks and relayers
*
* Ported from https://git.tornado.ws/tornadocontrib/tornado-core/src/branch/main/src/fees.ts and has been verified over time
*/
export class TornadoFeeOracleV6 extends TornadoFeeOracle implements ITornadoFeeOracle {
private fallbackFeeOracle: TornadoFeeOracleV5;
// Override value for BSC (Since old relayers use static value of 3.3 gwei)
public overrideGasPrice?: GasPriceParams;
public constructor(chainId: number, rpcUrl: string, overrideGasPrice?: GasPriceParams) {
super(6, chainId, rpcUrl);
this.fallbackFeeOracle = new TornadoFeeOracleV5(chainId, rpcUrl);
this.overrideGasPrice = overrideGasPrice;
}
/**
* Apply static 10% gasLimit premium as it wouldn't change across nodes or relayers
*/
async getGasLimit(tx?: TransactionData, type: TxType = 'other', bumpPercent: number = 10): Promise<number> {
if (!tx || Object.keys(tx).length === 0) return this.fallbackFeeOracle.getGasLimit(tx, type, bumpPercent);
const fetchedGasLimit = await this.provider.estimateGas(tx);
return bump(fetchedGasLimit, bumpPercent).toNumber();
}
/**
* Apply static 50% gasPrice premium for EIP-1559 enabled networks
*
* Because Tornado Transactions are censored from block builders
* it would take some time to be confirmed after when the transaction is broadcasted.
*
* We apply 50% premium to combat basefee spike (ethers.js uses 100% premium by default)
* (Can spike 12.5% per block accoring to the EIP-1559 metrics https://metamask.io/1559/)
*/
async getGasPriceParams(speed?: LegacyGasPriceKey, bumpPercent?: number): Promise<GasPriceParams> {
if (this.overrideGasPrice) {
return this.overrideGasPrice;
}
if (typeof bumpPercent === 'undefined') {
bumpPercent = this.chainId === ChainId.BSC ? undefined : 50;
}
return super.getGasPriceParams(speed, bumpPercent);
}
/**
* Apply 20% premium from the calculated fee from UI, applies 5% fee from relayer to prevent arbitrage from refund
*/
async calculateWithdrawalFeeViaRelayer(
params: GetWithdrawalFeeViaRelayerInput & { premium?: number },
): Promise<HexadecimalStringifiedNumber> {
const fees = await super.calculateWithdrawalFeeViaRelayer(params);
const premium = params.premium || (params.txType === 'user_withdrawal' ? 20 : 5);
return bump(fees, premium).toHexString();
}
}

@ -1,3 +1,4 @@
export * from './feeOracleV4';
export * from './feeOracleV5';
export * from './feeOracleV6';
export * from './tokenPriceOracle';

@ -1,4 +1,4 @@
import { MultiCall } from './contracts/MulticallAbi';
import { Multicall3 } from './contracts/MulticallAbi';
import { ITornadoPriceOracle, Token, TokenPrices, TokenSymbol } from './types';
import { MulticallAbi, OffchainOracleAbi } from './contracts';
import { getMultiCallContract, getOffchainOracleContract } from './contracts/factories';
@ -49,9 +49,10 @@ export class TokenPriceOracle implements ITornadoPriceOracle {
rpcUrl: string,
private tokens: Token[] = tornadoTokens,
private defaultTokenPrices: TokenPrices = defaultTornadoTokenPrices,
chainId?: ChainId,
) {
this.provider = new ethers.providers.JsonRpcProvider(rpcUrl);
this.oracle = getOffchainOracleContract(this.provider);
this.oracle = getOffchainOracleContract(this.provider, chainId);
this.multiCall = getMultiCallContract(this.provider);
}
@ -65,10 +66,11 @@ export class TokenPriceOracle implements ITornadoPriceOracle {
* @param {Token[]} [tokens] Tokens array
* @returns Valid structure to provide to MultiCall contract
*/
private prepareCallData(tokens: Token[] = this.tokens): MultiCall.CallStruct[] {
private prepareCallData(tokens: Token[] = this.tokens): Multicall3.Call3Struct[] {
return tokens.map((token) => ({
to: this.oracle.address,
data: this.oracle.interface.encodeFunctionData('getRateToEth', [token.tokenAddress, true]),
target: this.oracle.address,
allowFailure: true,
callData: this.oracle.interface.encodeFunctionData('getRateToEth', [token.tokenAddress, true]),
}));
}
@ -82,17 +84,17 @@ export class TokenPriceOracle implements ITornadoPriceOracle {
if (!tokens?.length) return {};
const callData = this.prepareCallData(tokens);
const { results, success } = await this.multiCall.multicall(callData);
const results = await this.multiCall.callStatic.aggregate3(callData);
const prices: TokenPrices = {};
for (let i = 0; i < results.length; i++) {
const tokenSymbol = tokens[i].symbol.toLowerCase() as TokenSymbol;
if (!success[i]) {
if (!results[i].success) {
if (this.defaultTokenPrices[tokenSymbol]) prices[tokenSymbol] = this.defaultTokenPrices[tokenSymbol];
continue;
}
const decodedRate = defaultAbiCoder.decode(['uint256'], results[i]).toString();
const decodedRate = defaultAbiCoder.decode(['uint256'], results[i].returnData).toString();
const tokenDecimals = BigNumber.from(10).pow(tokens[i].decimals);
const ethDecimals = BigNumber.from(10).pow(18);
const price = BigNumber.from(decodedRate).mul(tokenDecimals).div(ethDecimals);

@ -1,13 +1,19 @@
import { BigNumberish, BytesLike } from 'ethers';
import { GasPriceKey, GetTxGasParamsRes } from '@tornado/gas-price-oracle/lib/services';
import { AvailableTokenSymbols } from '@tornado/tornado-config';
import { InstanceTokenSymbol } from './config';
// Type for big hexadecimal numbers, like 0x1eff87f47e37a0
export type HexadecimalStringifiedNumber = string;
export type LegacyGasPriceKey = GasPriceKey;
export type GasPriceParams = GetTxGasParamsRes;
export type LegacyGasPriceKey = 'instant' | 'fast' | 'standard' | 'low';
export type GasPriceParams =
| {
gasPrice: string;
}
| {
maxFeePerGas: string;
maxPriorityFeePerGas: string;
};
export type LegacyGasPrices = {
[gasPriceType in LegacyGasPriceKey]: number;
};
@ -111,4 +117,6 @@ export type GetWithdrawalFeeViaRelayerInput = {
tokenPriceInEth?: HexadecimalStringifiedNumber | string; // Token (currency) price in ETH wei, if withdrawing non-native currency
predefinedGasPrice?: HexadecimalStringifiedNumber; // Predefined gas price for withdrawal tx (wont be calculated again in function)
predefinedGasLimit?: number; // Predefined gas limit for withdrawal tx (wont be calculated again in function)
bumpGasLimitPercent?: number; // Gas limit bump percent to prioritize transaction (recenlty used)
bumpGasPricePercent?: number; // Gas price bump percent to prioritize transaction (rarely used)
};

@ -369,15 +369,6 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@tornado/gas-price-oracle@^0.5.3":
version "0.5.3"
resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Fgas-price-oracle/-/0.5.3/gas-price-oracle-0.5.3.tgz#fb5423dddee2f52edbc16174c5ddce90bea5413d"
integrity sha512-LpVfPiPIz3FOmJdiqJf/yoeO5n9/Pd5jgtdY+6hB9lNW0AiWhylhpScojICViS+3OL9QC8CoTlgr+kbfGeO9pQ==
dependencies:
axios "^0.21.2"
bignumber.js "^9.0.0"
node-cache "^5.1.2"
"@tornado/tornado-config@^2.0.0":
version "2.0.0"
resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Ftornado-config/-/2.0.0/tornado-config-2.0.0.tgz#52bbc179ecb2385f71b4d56e060b68e7dd6fb8b4"
@ -478,13 +469,6 @@ array-back@^4.0.1, array-back@^4.0.2:
resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e"
integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==
axios@^0.21.2:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies:
follow-redirects "^1.14.0"
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@ -495,7 +479,7 @@ bech32@1.1.4:
resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9"
integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==
bignumber.js@^9.0.0, bignumber.js@^9.1.1:
bignumber.js@^9.1.1:
version "9.1.1"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6"
integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==
@ -567,11 +551,6 @@ chokidar@^3.5.2:
optionalDependencies:
fsevents "~2.3.2"
clone@2.x:
version "2.1.2"
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@ -718,11 +697,6 @@ find-replace@^3.0.0:
dependencies:
array-back "^3.0.1"
follow-redirects@^1.14.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
fs-extra@^7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
@ -901,13 +875,6 @@ ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
node-cache@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d"
integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==
dependencies:
clone "2.x"
nodemon@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.0.1.tgz#affe822a2c5f21354466b2fc8ae83277d27dadc7"