before dangerous ops

Signed-off-by: T-Hax <>
This commit is contained in:
T-Hax 2023-05-06 23:59:38 +00:00
parent 4065d89909
commit 704648ed2c
41 changed files with 31021 additions and 460 deletions

@ -1,13 +0,0 @@
# RPC URLs
ETH_MAINNET_TEST_RPC=
# debug (debug events are logged to console)
DEBUG=
# use tor (torify tests)
TORIFY=
# tor port (regular = 9050, browser = 9150)
TOR_PORT=
# relayer DOMAIN (the example.xyz in https://example.xyz) for testing
TEST_RELAYER_DOMAIN=

@ -0,0 +1 @@
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.

@ -47,13 +47,14 @@
"ganache": "^7.7.7",
"mocha": "^10.2.0",
"prettier": "^2.3.0",
"rimraf": "^4.4.0",
"rimraf": "^5.0.0",
"source-map-support": "^0.5.19",
"ts-essentials": "^9.3.1",
"ts-node": "^10.9.1",
"tsc-alias": "^1.2.11",
"tsconfig-paths": "^4.1.2",
"typechain": "^8.1.1",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
},
"scripts": {
@ -65,7 +66,7 @@
"postversion": "git push --follow-tags && npm publish",
"lint": "eslint --ext ts,js --fix src",
"build-live": "tsc -w && tsc-alias -w",
"clean": "rm -rf --interactive=never cache/*",
"clean": "rm -rf --interactive=never docs/*",
"format": "prettier src/**/*.ts -w"
},
"files": [

@ -70,23 +70,39 @@ export class Chain {
public signer?: Signer
public provider: Provider
private _emptySigner: VoidSigner
public chainId?: number
public symbol?: string
private _chainId?: number
private _symbol?: string
private _fetched: boolean
constructor(provider: Provider, signer?: Signer) {
this.provider = provider
this.signer = signer
this._emptySigner = new VoidSigner('0x' + randomBytes(20).toString('hex'), provider)
this._fetched = false
}
async getChainId(): Promise<number> {
if (!this.chainId) this.chainId = (await this.provider.getNetwork()).chainId
return this.chainId
async fetchChainData(): Promise<void> {
const network = await this.provider.getNetwork()
this._chainId = network.chainId
this._symbol = await Onchain.getNetworkSymbol(String(network.chainId))
this._fetched = true
}
async getChainSymbol(): Promise<string> {
if (!this.symbol) this.symbol = await Onchain.getNetworkSymbol(String(await this.getChainId()))
return this.symbol
private _propertiesFetched(parentCallName: string): void {
if (!this._fetched)
throw ErrorUtils.getError(
`Chain.${parentCallName}: properties must be fetched first with \`fetchProperties\`.`
)
}
get id(): number {
this._propertiesFetched('id')
return this._chainId!
}
get symbol(): string {
this._propertiesFetched('symbol')
return this._symbol!
}
latestBlockNum(): Promise<number> {
@ -127,12 +143,12 @@ export class Chain {
): Promise<TransactionRequest> {
if (callStruct[0].value)
return await Multicall3Contract__factory.connect(
await Onchain.getMulticall3Address(String(this.chainId)),
await Onchain.getMulticall3Address(String(this.id)),
this.provider
).populateTransaction.aggregate3Value(callStruct as Array<Multicall3.Call3ValueStruct>)
return await Multicall3Contract__factory.connect(
await Onchain.getMulticall3Address(String(this.chainId)),
await Onchain.getMulticall3Address(String(this.id)),
this.provider
).populateTransaction.aggregate3(callStruct)
}
@ -143,12 +159,12 @@ export class Chain {
if (this.signer)
if (callStruct[0].value)
return await Multicall3Contract__factory.connect(
await Onchain.getMulticall3Address(String(this.chainId)),
await Onchain.getMulticall3Address(String(this.id)),
this.signer
).aggregate3Value(callStruct as Array<Multicall3.Call3ValueStruct>)
else {
return await Multicall3Contract__factory.connect(
await Onchain.getMulticall3Address(String(this.chainId)),
await Onchain.getMulticall3Address(String(this.id)),
this.provider
).aggregate3(callStruct)
}
@ -181,33 +197,30 @@ export namespace Contracts {
type Path = string
const contractMap: Map<Path, BaseContract> = new Map<Path, BaseContract>()
export async function getProxy(
network: string,
signerOrProvider: Signer | Provider
): Promise<TornadoProxy> {
export function getProxy(network: string, signerOrProvider: Signer | Provider): TornadoProxy {
const key = `TornadoProxy${network}`
if (!contractMap.has(key)) {
contractMap.set(
key,
_getContract<TornadoProxy>('TornadoProxy', await Onchain.getProxyAddress(network), signerOrProvider)
_getContract<TornadoProxy>('TornadoProxy', Onchain.getProxyAddressSync(network), signerOrProvider)
)
}
return contractMap.get(`TornadoProxy${network}`) as TornadoProxy
}
export async function getInstance(
export function getInstance(
network: string,
token: string,
denomination: string,
signerOrProvider: Signer | Provider
): Promise<TornadoInstance> {
): TornadoInstance {
const key = `TornadoInstance${network}${token}${denomination}`
if (!contractMap.has(key)) {
contractMap.set(
key,
_getContract<TornadoInstance>(
'TornadoInstance',
await Onchain.getInstanceAddress(network, token, denomination),
Onchain.getInstanceAddressSync(network, token, denomination),
signerOrProvider
)
)
@ -235,7 +248,8 @@ export namespace Contracts {
export abstract class Synchronizer extends EventEmitter {
async sync(
event: EventFilter,
eventName: string,
filter: EventFilter,
contract: BaseContract,
cache: Cache.Syncable<Docs.Base>,
options?: Options.Sync
@ -301,11 +315,7 @@ export abstract class Synchronizer extends EventEmitter {
this.emit('sync', 'synced')
// Immediately start listening if we're doing this
if (_options.listenForEvents) {
contract = contract.on(event, (...eventArgs) => {
this.emit(cache.name, 'received', cache.db.put(cache.buildDoc(eventArgs[eventArgs.length - 1])))
})
}
if (_options.listenForEvents) this.listenForEvents(eventName, contract, filter, cache)
// Then wait for all pooler requests to resolve
let results = await cache.pooler!.all()
@ -325,5 +335,21 @@ export abstract class Synchronizer extends EventEmitter {
})
}
listenForEvents(
name: string,
contract: BaseContract,
filter: EventFilter,
cache: Cache.Syncable<Docs.Base>
) {
contract.on(filter, (...eventArgs) => {
this.emit(name, cache.name, cache.db.put(cache.buildDoc(eventArgs[eventArgs.length - 1])))
})
}
clearListenerByIndex(contract: BaseContract, event: EventFilter, listenerIndex: number = 0): void {
const listeners = contract.listeners()
contract.off(event, listeners[listenerIndex])
}
protected abstract _populateSyncOptions(options?: Options.Sync): Promise<DeepRequired<Options.Sync>>
}

@ -1,13 +0,0 @@
# RPC URLs
ETH_MAINNET_TEST_RPC=
# debug (debug events are logged to console)
DEBUG=
# use tor (torify tests)
TORIFY=
# tor port (regular = 9050, browser = 9150)
TOR_PORT=
# relayer DOMAIN (the example.xyz in https://example.xyz) for testing
TEST_RELAYER_DOMAIN=

@ -0,0 +1 @@
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.

@ -49,13 +49,14 @@
"ganache": "^7.7.7",
"mocha": "^10.2.0",
"prettier": "^2.3.0",
"rimraf": "^4.4.0",
"rimraf": "^5.0.0",
"source-map-support": "^0.5.19",
"ts-essentials": "^9.3.1",
"ts-node": "^10.9.1",
"tsc-alias": "^1.2.11",
"tsconfig-paths": "^4.1.2",
"typechain": "^8.1.1",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
},
"scripts": {
@ -67,7 +68,7 @@
"postversion": "git push --follow-tags && npm publish",
"lint": "eslint --ext ts,js --fix src",
"build-live": "tsc -w && tsc-alias -w",
"clean": "rm -rf --interactive=never cache/*",
"clean": "rm -rf --interactive=never docs/*",
"format": "prettier src/**/*.ts -w"
},
"files": [

@ -10,7 +10,7 @@ import { ZKDepositData, InputFor } from '@tornado/sdk-crypto'
// External imports
import { TransactionRequest } from '@ethersproject/abstract-provider'
import { BigNumber, providers } from 'ethers'
import { BigNumber, EventFilter, providers } from 'ethers'
import { parseUnits } from 'ethers/lib/utils'
import { bigInt } from 'snarkjs'
@ -30,10 +30,6 @@ export namespace Options {
export interface Deposit {
depositsPerInstance?: Array<number>
doNotPopulate?: boolean
backup?: {
invoices?: boolean
notes?: boolean
}
}
export type Invoice = Deposit
@ -128,58 +124,52 @@ export class WithdrawalCache extends Cache.Syncable<Docs.Withdrawal> {
type Provider = providers.Provider
type BackupDepositDoc = {
network: string
denomination: string
token: string
invoice?: string
note?: string
}
type RelayerProperties = MarkOptional<
Pick<RelayerDataProperties, 'address' | 'serviceFeePercent' | 'prices'>,
'serviceFeePercent' | 'prices'
>
export class Core extends Synchronizer {
chain: Chain
private _mutex: AsyncUtils.SimpleMutex
caches: Map<string, Cache.Base<Docs.Base>>
instances: Map<string, TornadoInstance>
chain?: Chain
constructor(provider: providers.Provider) {
constructor() {
super()
this.chain = new Chain(provider)
this.caches = new Map<string, Cache.Syncable<Docs.Base>>()
this.instances = new Map<string, TornadoInstance>()
this._mutex = new AsyncUtils.SimpleMutex()
}
connect(provider: Provider): void {
this.chain.provider = provider
private _checkProvider(parentCallName: string): void {
try {
this.chain?.id
} catch (err) {
throw ErrorUtils.getError('Core.' + parentCallName + ': you must first connect a provider!')
}
}
async getInstances(
keys: Array<{ token: string; denomination: number | string }>
): Promise<Array<TornadoInstance>> {
const chainId = await this.chain.getChainId()
return Promise.all(
keys.map((key) =>
Contracts.getInstance(String(chainId), key.token, String(key.denomination), this.chain.provider)
)
async connect(provider: Provider): Promise<void> {
if (!this.chain) this.chain = new Chain(provider)
else this.chain.provider = provider
await this.chain.fetchChainData()
}
getInstances(keys: Array<{ token: string; denomination: number | string }>): Array<TornadoInstance> {
this._checkProvider('getInstances')
return keys.map((key) =>
Contracts.getInstance(String(this.chain!.id), key.token, String(key.denomination), this.chain!.provider)
)
}
async getInstance(token: string, denomination: number | string): Promise<TornadoInstance> {
const chainId = String(await this.chain.getChainId())
token = token.toLowerCase()
denomination = String(denomination)
if (this.instances.has(chainId + token + denomination))
return this.instances.get(chainId + token + denomination)!
else return Contracts.getInstance(chainId, token, denomination, this.chain.provider)
getInstance(token: string, denomination: number | string): TornadoInstance {
this._checkProvider('getInstance')
return this.loadInstance(this.chain!.id, token, denomination)
}
async getProxy(): Promise<TornadoProxy> {
const chainId = await this.chain.getChainId()
return Contracts.getProxy(String(chainId), this.chain.provider)
getProxy(): TornadoProxy {
this._checkProvider('getProxy')
return Contracts.getProxy(String(this.chain!.id), this.chain!.provider)
}
async buildDepositProof(
@ -215,6 +205,8 @@ export class Core extends Synchronizer {
zkDepositsData: Array<ZKDepositData>,
options?: Options.Core.BuildDepositProof
): Promise<Array<Array<string>>> {
this._checkProvider('buildDepositProofs')
// Extract commitments and nullifier hashes
const hexCommitments: string[] = []
const hexNullifierHashes: string[] = []
@ -237,8 +229,8 @@ export class Core extends Synchronizer {
})
// Determine cache name
const lookupKeys = await Onchain.getInstanceLookupKeys(instance.address)
const name = 'Deposits' + (lookupKeys.network + lookupKeys.token + lookupKeys.denomination).toUpperCase()
const { network, token, denomination } = await Onchain.getInstanceLookupKeys(instance.address)
const name = 'Deposits' + (network + token + denomination).toUpperCase()
// Find all leaves & indices by reading from cache
const [leaves, leafIndices] = await this._findLeavesAndIndices(name, hexCommitments)
@ -305,20 +297,23 @@ export class Core extends Synchronizer {
// Rest of note invariant arguments
const inputsForProofs: InputFor.ZKProof[] = []
const gasPrice = options?.gasPrice ?? (await this.chain.getGasPrice())
const gasPrice = options?.gasPrice ?? (await this.chain!.getGasPrice())
const gasPriceCushion = options?.gasPrice ?? gasPrice.mul(10).div(100)
// In reality, if a manual withdraw is made, we don't differentiate it from a relayer withdraw
// Since it is only serviceFee 0 AND without a token price, the function will not buy more tokens
const serviceFeePercent = relayerProperties.serviceFeePercent ?? 0
const tokenPrice = relayerProperties.prices?.get(lookupKeys.token)
const tokenPrice = relayerProperties.prices?.get(token)
const decimals = BigNumber.from(10).pow(
options?.tokenDecimals ?? (await Onchain.getTokenDecimals(lookupKeys.network, lookupKeys.token))
options?.tokenDecimals ?? (await Onchain.getTokenDecimals(network, token))
)
const toWithdraw = BigNumber.from(+lookupKeys.denomination * 10 ** lookupKeys.denomination.length)
const toWithdraw = BigNumber.from(+denomination * 10 ** denomination.length)
.mul(decimals)
.div(10 ** lookupKeys.denomination.length)
const native = lookupKeys.token == (await this.chain.getChainSymbol())
.div(10 ** denomination.length)
const native = token == this.chain!.symbol
if (!tokenPrice && !native)
throw ErrorUtils.getError(
@ -403,6 +398,9 @@ export class Core extends Synchronizer {
const leaves: Array<string> = []
const cache = this.loadCache<Cache.Base<Docs.Deposit>>(instanceName)
// Shallow copy so we can find indexes again for commitments
const commitmentsCopy = [...commitments]
const docs = await cache.db.allDocs()
// If no docs in cache throw and stop
@ -415,13 +413,25 @@ export class Core extends Synchronizer {
// Otherwise start looking for commitment leaf indices and also pick up all other leafs on the way
for (const row of docs.rows) {
const [, leafIndex, loadedCommitment] = parseIndexableString(row.id)
const index = commitments.findIndex((commitment) => commitment === loadedCommitment)
let index = -1
// If some commitment is found then add the leaf index and remove that commitment
const [, leafIndex, loadedCommitment] = parseIndexableString(row.id)
// Search only if there is some left
if (commitments.length !== 0)
index = commitments.findIndex((commitment) => commitment === loadedCommitment)
// If some commitment is found then add the leaf index
if (index !== -1) {
indices[index] = leafIndex
// Add it there where we intended for it to be originally
indices[commitmentsCopy.findIndex((commitment) => commitment === loadedCommitment)] = leafIndex
commitments.splice(index, 1)
this.emit(
'debug',
`\nMatched commitment ${loadedCommitment} @ leaf index ${leafIndex}, leftover commitments:\n\n${commitments.join(
'\n'
)}\n`
)
}
// In any case push every leaf
@ -443,8 +453,7 @@ export class Core extends Synchronizer {
if (indexes)
for (let i = 0, len = rows.length; i < len; i++) {
const [index, , ,] = parseIndexableString(rows[i].id)[0]
if (0 < indexes.findIndex(index)) docs.push(rows[i].doc)
docs.push(rows[indexes[i]].doc)
}
else docs = rows.map((row) => row.doc)
@ -469,82 +478,141 @@ export class Core extends Synchronizer {
return this.parseNotes([note])[0]
}
async createInvoice(
instance: TornadoInstance,
options?: Omit<Options.Core.Invoice, 'depositsPerInstance'>
): Promise<Transactions.Invoice> {
let opts: Options.Core.Invoice = options ?? {}
opts.depositsPerInstance = [1]
return (await this.createInvoices([instance], options))[0]
clearListener(
instance: TornadoInstance | string,
event: Function | number = 0,
listenerIndex: number = 0
): void {
const _instance = this._resolveInstance(instance)
const filter = this._resolveInstanceEvent(_instance, event)
this.clearListenerByIndex(
_instance,
this._instanceEventToFilter(filter, _instance.filters.Deposit),
listenerIndex
)
}
async createInvoices(
instances: Array<TornadoInstance>,
options?: Options.Core.Invoice
): Promise<Array<Transactions.Invoice>> {
if (!options) options = {}
if (!options.backup) options.backup = {}
options.backup.invoices = options.backup.invoices ?? true
options.backup.notes = options.backup.notes ?? true
options.doNotPopulate = options.doNotPopulate ?? true
return this.buildDepositTransactions(instances, options)
clearListeners(instance: TornadoInstance | string): void {
this._resolveInstance(instance).removeAllListeners()
}
async buildDepositTransaction(
instance: TornadoInstance,
options?: Options.Core.Deposit
): Promise<Transactions.Deposit> {
listenForDeposits(instance: TornadoInstance | string): void {
this.listenForInstanceEvents(instance, 0)
}
listenForWithdrawals(instance: TornadoInstance | string): void {
this.listenForInstanceEvents(instance, 1)
}
listenForInstanceEvents(instance: TornadoInstance | string, event: Function | number = 0): void {
let _instance: TornadoInstance
let key: string
if (typeof instance !== 'string') {
const { network, token, denomination } = Onchain.getInstanceLookupKeysSync(instance.address)
_instance = this.loadInstance(network, token, denomination)
key = network + token + denomination
} else {
key = instance.toLowerCase()
_instance = this._resolveInstance(key)
}
const filter = this._resolveInstanceEvent(_instance!, event)
const isDeposit = filter == _instance.filters.Deposit
const cache = isDeposit
? this.loadDepositCache('Deposits' + key.toUpperCase())
: this.loadWithdrawalCache('Withdrawals' + key.toUpperCase())
this.listenForEvents(
isDeposit ? 'deposit' : 'withdrawal',
_instance!,
this._instanceEventToFilter(filter, _instance.filters.Deposit),
cache
)
}
private _instanceEventToFilter(event: Function, depositEvent: Function): EventFilter {
return event == depositEvent ? event(null, null, null) : event(null, null, null, null)
}
private _resolveInstanceEvent(instance: TornadoInstance, event: Function | number = 0): Function {
let filter: Function
if (typeof event === 'number') {
filter = event === 0 ? instance.filters.Deposit : instance.filters.Withdrawal
} else filter = event
return filter
}
private _resolveInstance(instance: TornadoInstance | string): TornadoInstance {
let _instance: TornadoInstance
if (typeof instance === 'string') {
instance = instance.toLowerCase()
const regexp = /([0-9]+)([a-z]+)([0-9.]+)/
const matches = instance.match(regexp)?.slice(1)
if (!matches || matches.length === 0)
throw ErrorUtils.getError('Core._resolveInstance: instance string key invalid.')
_instance = this.loadInstance(matches[0], matches[1], matches[2])
} else _instance = instance
return _instance
}
/**
* This is the main function to build a single Tornado Cash Classic deposit. An address need not be supplied because the returned note proves a deposit.
* @param instance The TornadoInstance for which to build transactions.
* @param options Whether or not to populate the transactions (only in the sense of encoding transaction data), and whether to backup notes and invoices. Defaults: `depositsPerInstance = [1], doNotPopulate = false, backup { notes = true, invoices = false }` Deposits per instance are hardcoded to 1, since we're doing a single transaction.
* @returns A promise which resolves to the created transaction.
*/
buildDepositTransaction(instance: TornadoInstance, options?: Options.Core.Deposit): Transactions.Deposit {
let opts: Options.Core.Deposit = options ?? {}
opts.depositsPerInstance = [1]
return (await this.buildDepositTransactions([instance], opts))[0]
return this.buildDepositTransactions([instance], opts)[0]
}
async buildDepositTransactions(
/**
* This is the main function which is used to build Tornado Cash Classic deposit transactions. An address need not be supplied because the returned note proves a deposit.
* @param instances The TornadoInstance instances for which to build transactions.
* @param options The number of deposits per instance, whether or not to populate the transactions (only in the sense of encoding transaction data), and whether to backup notes and invoices. Defaults: `depositsPerInstance = [1]*instance_num, doNotPopulate = false, backup { notes = true, invoices = false }`
* @returns A promise which resolves to the created transactions.
* @todo TODO: Maybe this should be sync and deposit backups should be async somewhere else
*/
buildDepositTransactions(
instances: Array<TornadoInstance>,
options?: Options.Core.Deposit
): Promise<Array<Transactions.Deposit>> {
): Array<Transactions.Deposit> {
this._checkProvider('buildDepositTransactions')
const depositsPerInstance = options?.depositsPerInstance ?? new Array<number>(instances.length).fill(1)
const doNotPopulate = options?.doNotPopulate ?? false
const backupNotes = options?.backup?.notes ?? true
const backupInvoices = options?.backup?.invoices ?? false
if (depositsPerInstance.length != instances.length)
throw ErrorUtils.getError(
'Core.buildDepositTx: number of deposit amount elements must equal the number of instances!'
)
const notesToBackup: Array<BackupDepositDoc> = []
const invoicesToBackup: Array<BackupDepositDoc> = []
const txs: Array<Transactions.Deposit> = []
const chainId = await this.chain.getChainId()
const chainId = this.chain!.id
const proxy: TornadoProxy = await Contracts.getProxy(String(chainId), this.chain.provider)
const proxy: TornadoProxy = Contracts.getProxy(String(chainId), this.chain!.provider)
const txs: Array<Transactions.Deposit> = []
for (let i = 0, nInstances = instances.length; i < nInstances; i++) {
const lookupKeys = await Onchain.getInstanceLookupKeys(instances[i].address)
const pathstring = lookupKeys.network + lookupKeys.token + lookupKeys.denomination
const { network, token, denomination } = Onchain.getInstanceLookupKeysSync(instances[i].address)
const pathstring = network + token + denomination
for (let d = 0, nDeposits = depositsPerInstance[i]; d < nDeposits; d++) {
const deposit = Primitives.createDeposit()
const note = Primitives.createNote(deposit.preimage)
if (backupNotes)
notesToBackup.push({
network: lookupKeys.network,
denomination: lookupKeys.denomination,
token: lookupKeys.token,
note: note
})
if (backupInvoices)
invoicesToBackup.push({
network: lookupKeys.network,
denomination: lookupKeys.denomination,
token: lookupKeys.token,
invoice: deposit.hexCommitment
})
if (!doNotPopulate) {
txs.push({
request: {
@ -554,7 +622,7 @@ export class Core extends Synchronizer {
deposit.hexCommitment,
[]
]),
value: lookupKeys.token == 'eth' ? parseUnits(lookupKeys.denomination) : BigNumber.from(0)
value: token == 'eth' ? parseUnits(denomination) : BigNumber.from(0)
},
note: pathstring + '_' + note,
invoice: pathstring + '_' + deposit.hexCommitment
@ -568,41 +636,67 @@ export class Core extends Synchronizer {
}
}
if (backupNotes)
await this._backupDepositData(this.loadCache<Cache.Base<Docs.Note>>('DepositNotes'), notesToBackup)
if (backupInvoices)
await this._backupDepositData(
this.loadCache<Cache.Base<Docs.Invoice>>('DepositInvoices'),
invoicesToBackup
)
return txs
}
async backupNote(instance: TornadoInstance, transaction: Transactions.Deposit): Promise<void> {
await this.backupNotes(instance, [transaction])
}
async backupInvoice(instance: TornadoInstance, transaction: Transactions.Deposit): Promise<void> {
await this.backupInvoices(instance, [transaction])
}
async backupNotes(instance: TornadoInstance, transactions: Array<Transactions.Deposit>): Promise<void> {
const { network, token, denomination } = await Onchain.getInstanceLookupKeys(instance.address)
await this._backupDepositData(
network,
token,
denomination,
transactions,
this.loadCache<Cache.Base<Docs.Note>>('DepositNotes')
)
}
async backupInvoices(instance: TornadoInstance, transactions: Array<Transactions.Deposit>): Promise<void> {
const { network, token, denomination } = await Onchain.getInstanceLookupKeys(instance.address)
await this._backupDepositData(
network,
token,
denomination,
transactions,
this.loadCache<Cache.Base<Docs.Invoice>>('DepositInvoices')
)
}
private async _backupDepositData<T extends Docs.Note | Docs.Invoice>(
cache: Cache.Base<T>,
backupData: Array<BackupDepositDoc>
network: string,
token: string,
denomination: string,
transactions: Array<Transactions.Deposit>,
cache: Cache.Base<T>
): Promise<void> {
const notes = cache.name.length === 12 ? true : false
const name = notes ? 'notes' : 'invoices'
// We need a mutex here
const release = await this._mutex.acquire(name)
let id = +(await cache.db.info()).update_seq
await cache.db
.bulkDocs(
backupData.map((entry) => {
if (entry.note)
return new Docs.Note(++id, entry.network, entry.token, entry.denomination, entry.note)
else if (entry.invoice)
return new Docs.Invoice(++id, entry.network, entry.token, entry.denomination, entry.invoice)
transactions.map((transaction) => {
if (notes) return new Docs.Note(++id, network, token, denomination, transaction.note!)
else return new Docs.Invoice(++id, network, token, denomination, transaction.invoice!)
}) as Array<T>
)
.catch((err) => {
throw ErrorUtils.ensureError(err)
})
// TODO: Decide whether to close caches by default or not
//await cache.close().catch((err) => {
// throw ErrorUtils.ensureError(err)
//})
// Release
release()
}
loadDepositCache(name: string, options?: Options.Sync): DepositCache {
@ -644,50 +738,57 @@ export class Core extends Synchronizer {
return this.caches.get(name) as C
}
loadInstance(chainId: number | string, token: string, denomination: number | string): TornadoInstance {
token = token.toLowerCase()
return Contracts.getInstance('' + chainId, token, '' + denomination, this.chain!.provider)
}
async syncDeposits(instance: TornadoInstance, options?: Options.Sync): Promise<void> {
const lookupKeys = await Onchain.getInstanceLookupKeys(instance.address)
const pathstring = lookupKeys.network + lookupKeys.token + lookupKeys.denomination
this._checkProvider('syncDeposits')
const { network, token, denomination } = await Onchain.getInstanceLookupKeys(instance.address)
const pathstring = network + token + denomination
options = options ?? {}
options.startBlock = await Onchain.getInstanceDeployBlockNum(
lookupKeys.network,
lookupKeys.token,
lookupKeys.denomination
)
options.startBlock = await Onchain.getInstanceDeployBlockNum(network, token, denomination)
const populatedOptions = await this._populateSyncOptions(options)
const cache = this.loadDepositCache('Deposits' + pathstring.toUpperCase(), populatedOptions)
await this.sync(instance.filters.Deposit(null, null, null), instance, cache, populatedOptions)
await this.sync('deposit', instance.filters.Deposit(null, null, null), instance, cache, populatedOptions)
if (!this.instances.has(pathstring)) this.instances.set(pathstring, instance)
if (!this.caches.has(cache.name)) this.caches.set(cache.name, cache)
}
async syncWithdrawals(instance: TornadoInstance, options?: Options.Sync): Promise<void> {
const lookupKeys = await Onchain.getInstanceLookupKeys(instance.address)
const pathstring = lookupKeys.network + lookupKeys.token + lookupKeys.denomination
this._checkProvider('syncWithdrawals')
const { network, token, denomination } = await Onchain.getInstanceLookupKeys(instance.address)
const pathstring = network + token + denomination
options = options ?? {}
options.startBlock = await Onchain.getInstanceDeployBlockNum(
lookupKeys.network,
lookupKeys.token,
lookupKeys.denomination
)
options.startBlock = await Onchain.getInstanceDeployBlockNum(network, token, denomination)
const populatedOptions = await this._populateSyncOptions(options)
const cache = this.loadWithdrawalCache('Withdrawals' + pathstring.toUpperCase(), populatedOptions)
await this.sync(instance.filters.Withdrawal(null, null, null), instance, cache, populatedOptions)
await this.sync(
'withdrawal',
instance.filters.Withdrawal(null, null, null),
instance,
cache,
populatedOptions
)
if (!this.instances.has(pathstring)) this.instances.set(pathstring, instance)
if (!this.caches.has(cache.name)) this.caches.set(cache.name, cache)
}
protected async _populateSyncOptions(options: Options.Sync): Promise<DeepRequired<Options.Sync>> {
if (!options.startBlock) throw ErrorUtils.getError('Core._populateSyncOptions: startBlock not set.')
options.targetBlock = options.targetBlock ?? (await this.chain.latestBlockNum())
options.targetBlock = options.targetBlock ?? (await this.chain!.latestBlockNum())
options.blockDivisor = options.blockDivisor ?? 40

@ -1,13 +0,0 @@
# RPC URLs
ETH_MAINNET_TEST_RPC=
# debug (debug events are logged to console)
DEBUG=
# use tor (torify tests)
TORIFY=
# tor port (regular = 9050, browser = 9150)
TOR_PORT=
# relayer DOMAIN (the example.xyz in https://example.xyz) for testing
TEST_RELAYER_DOMAIN=

@ -0,0 +1 @@
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.

@ -43,12 +43,13 @@
"fs-extra": "^11.1.0",
"mocha": "^10.2.0",
"prettier": "^2.3.0",
"rimraf": "^4.4.0",
"rimraf": "^5.0.0",
"source-map-support": "^0.5.19",
"ts-essentials": "^9.3.1",
"ts-node": "^10.9.1",
"tsc-alias": "^1.2.11",
"tsconfig-paths": "^4.1.2",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
},
"scripts": {
@ -60,7 +61,7 @@
"postversion": "git push --follow-tags && npm publish",
"lint": "eslint --ext ts,js --fix src",
"build-live": "tsc -w && tsc-alias -w",
"clean": "rm -rf --interactive=never cache/*",
"clean": "rm -rf --interactive=never docs/*",
"format": "prettier src/*.ts -w"
},
"files": [

@ -140,12 +140,12 @@ export type ZKDepositData = OutputOf.CreateDeposit
* (will be) contained within this namespace.
*/
export namespace Setup {
export async function getProvingKey(): Promise<ArrayBufferLike> {
return (await Files.loadRaw('circuits/tornadoProvingKey.bin')).buffer
export function getProvingKey(): ArrayBufferLike {
return Files.loadRawSync('circuits/tornadoProvingKey.bin').buffer
}
export async function getTornadoCircuit(): Promise<any> {
return Json.load('circuits/tornado.json')
export function getTornadoCircuit(): any {
return Json.loadSync('circuits/tornado.json')
}
let cachedGroth16Prover: Groth16 | null = null
@ -158,6 +158,11 @@ export namespace Setup {
if (!cachedGroth16Prover) cachedGroth16Prover = await buildGroth16(defaultParams)
return cachedGroth16Prover
}
export function terminateGroth16(): void {
cachedGroth16Prover!.terminate()
cachedGroth16Prover = null
}
}
export namespace Primitives {
@ -212,8 +217,8 @@ export namespace Primitives {
export async function calcDepositProofs(inputs: Array<InputFor.ZKProof>): Promise<Array<Array<string>>> {
const proofs: string[][] = []
const groth16 = await Setup.getGroth16()
const circuit = await Setup.getTornadoCircuit()
const provingKey = await Setup.getProvingKey()
const circuit = Setup.getTornadoCircuit()
const provingKey = Setup.getProvingKey()
for (let i = 0, len = inputs.length; i < len; i++) {
const input = inputs[i]
@ -273,7 +278,7 @@ export namespace Primitives {
}
// Done. 🤷‍♀️
groth16.terminate()
Setup.terminateGroth16()
return proofs
}

@ -1,13 +0,0 @@
# RPC URLs
ETH_MAINNET_TEST_RPC=
# debug (debug events are logged to console)
DEBUG=
# use tor (torify tests)
TORIFY=
# tor port (regular = 9050, browser = 9150)
TOR_PORT=
# relayer DOMAIN (the example.xyz in https://example.xyz) for testing
TEST_RELAYER_DOMAIN=

@ -0,0 +1 @@
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.

@ -42,12 +42,13 @@
"fs-extra": "^11.1.0",
"mocha": "^10.2.0",
"prettier": "^2.3.0",
"rimraf": "^4.4.0",
"rimraf": "^5.0.0",
"source-map-support": "^0.5.19",
"ts-essentials": "^9.3.1",
"ts-node": "^10.9.1",
"tsc-alias": "^1.2.11",
"tsconfig-paths": "^4.1.2",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
},
"scripts": {
@ -59,7 +60,7 @@
"postversion": "git push --follow-tags && npm publish",
"lint": "eslint --ext ts,js --fix src",
"build-live": "tsc -w && tsc-alias -w",
"clean": "rm -rf --interactive=never cache/*",
"clean": "rm -rf --interactive=never docs/*",
"format": "prettier src/*.ts -w"
},
"files": [

@ -1,6 +1,6 @@
// Big modules
import { BigNumber } from 'ethers'
import { existsSync, mkdirSync } from 'fs'
import { existsSync, mkdirSync, readFileSync } from 'fs'
import { opendir, readFile, rm } from 'fs/promises'
import { createInterface } from 'readline'
@ -80,6 +80,7 @@ export namespace Files {
export const makeCacheDir = (prefix?: string): void => mkdirSync(getCachePath('', prefix))
export const loadRaw = (relative: string): Promise<Buffer> => readFile(getResourcePath(relative))
export const loadRawSync = (relative: string): Buffer => readFileSync(getResourcePath(relative))
export async function wipeCache(prompt: boolean = true): Promise<void> {
const dir = await opendir(getCachePath(''))
@ -126,7 +127,6 @@ export namespace Files {
export namespace Json {
const cachedJsonData = new Map<string, any>()
// reading
export async function load(
relativePath: string,
encoding: BufferEncoding = 'utf8',
@ -140,6 +140,19 @@ export namespace Json {
}
}
export function loadSync(
relativePath: string,
encoding: BufferEncoding = 'utf8',
pathGetter: Files.PathGetter = Files.getResourcePath
): any {
if (cachedJsonData.has(relativePath)) return cachedJsonData.get(relativePath)
else {
const obj = JSON.parse(readFileSync(pathGetter(relativePath), encoding))
cachedJsonData.set(relativePath, obj)
return obj
}
}
export function toMap<V>(jsonData: any): Map<string, V> {
return new Map<string, V>(Object.entries(jsonData))
}
@ -187,6 +200,23 @@ export namespace Onchain {
}
}
export function getClassicInstanceDataSync(
network: string,
token: string,
denomination: string
): ClassicInstance {
const instanceData = Json.getValue(Json.loadSync('onchain/instances.json'), [network, token])
return {
network: +network,
symbol: token.toUpperCase(),
decimals: Json.getValue(instanceData, ['decimals']),
denomination: +denomination,
deployBlock: Json.getValue(instanceData, ['deployedBlockNumber', denomination]),
address: Json.getValue(instanceData, ['instanceAddress', denomination]),
anonymityMiningEnabled: Json.getValue(instanceData, ['miningEnabled'])
}
}
export async function getInstanceLookupKeys(instanceAddress: string): Promise<Keys.InstanceLookup> {
// lookup some stuff first
const lookupObj: { [key: string]: string } = await Json.load('onchain/instanceAddresses.json')
@ -204,6 +234,23 @@ export namespace Onchain {
}
}
export function getInstanceLookupKeysSync(instanceAddress: string): Keys.InstanceLookup {
// lookup some stuff first
const lookupObj: { [key: string]: string } = Json.loadSync('onchain/instanceAddresses.json')
const pathstring: string = Object.entries(lookupObj).find((el) => el[1] === instanceAddress)![0]
const network = pathstring.match('[0-9]+')![0],
token = pathstring.substring(network.length).match('[a-z]+')![0],
denomination = pathstring.substring(network.length + token.length)
return {
network: network,
token: token,
denomination: denomination
}
}
export async function getPathstringBasedContent<T>(
filepath: string,
paths: Array<{
@ -220,12 +267,31 @@ export namespace Onchain {
)
}
export function getPathstringBasedContentSync<T>(
filepath: string,
paths: Array<{
network?: string
token?: string
denomination?: string
}>
): Array<T> {
return paths.map((path) =>
Json.getValue(Json.loadSync(filepath), [
`${path.network ?? ''}${path.token ?? ''}${path.denomination ?? ''}`
])
)
}
export async function getNetworkSymbol(networkId: string): Promise<string> {
return (
await getPathstringBasedContent<string>('onchain/networkSymbols.json', [{ network: networkId }])
)[0]
}
export function getNetworkSymbolSync(networkId: string): string {
return getPathstringBasedContentSync<string>('onchain/networkSymbols.json', [{ network: networkId }])[0]
}
export function getInstanceAddresses(
paths: Array<{
network: string
@ -236,6 +302,16 @@ export namespace Onchain {
return getPathstringBasedContent<string>('onchain/instanceAddresses.json', paths)
}
export function getInstanceAddressesSync(
paths: Array<{
network: string
token: string
denomination: string
}>
): Array<string> {
return getPathstringBasedContentSync<string>('onchain/instanceAddresses.json', paths)
}
export async function getInstanceAddress(
network: string,
token: string,
@ -244,6 +320,10 @@ export namespace Onchain {
return (await getInstanceAddresses([{ network: network, token: token, denomination: denomination }]))[0]
}
export function getInstanceAddressSync(network: string, token: string, denomination: string): string {
return getInstanceAddressesSync([{ network: network, token: token, denomination: denomination }])[0]
}
export function getInstanceDeployBlockNums(
paths: Array<{
network: string
@ -254,6 +334,16 @@ export namespace Onchain {
return getPathstringBasedContent<number>('onchain/deployedBlockNumbers.json', paths)
}
export function getInstanceDeployBlockNumsSync(
paths: Array<{
network: string
token: string
denomination: string
}>
): Array<number> {
return getPathstringBasedContentSync<number>('onchain/deployedBlockNumbers.json', paths)
}
export async function getInstanceDeployBlockNum(
network: string,
token: string,
@ -264,6 +354,14 @@ export namespace Onchain {
)[0]
}
export function getInstanceDeployBlockNumSync(
network: string,
token: string,
denomination: string
): number {
return getInstanceDeployBlockNumsSync([{ network: network, token: token, denomination: denomination }])[0]
}
export async function getProxyAddress(network: string): Promise<string> {
return Json.getValue(await Json.load('onchain/infrastructure.json'), [network, 'proxy'])
}
@ -276,6 +374,18 @@ export namespace Onchain {
return Json.getValue(await Json.load('onchain/infrastructure.json'), [network, 'multicall3'])
}
export function getProxyAddressSync(network: string): string {
return Json.getValue(Json.loadSync('onchain/infrastructure.json'), [network, 'proxy'])
}
export function getMulticallAddressSync(network: string): string {
return Json.getValue(Json.loadSync('onchain/infrastructure.json'), [network, 'multicall'])
}
export function getMulticall3AddressSync(network: string): string {
return Json.getValue(Json.loadSync('onchain/infrastructure.json'), [network, 'multicall3'])
}
export async function getTokenData(network: string, token: string): Promise<TokenData> {
const data = Json.getValue(await Json.load('onchain/tokens.json'), [network, token])
return {
@ -285,6 +395,15 @@ export namespace Onchain {
}
}
export function getTokenDataSync(network: string, token: string): TokenData {
const data = Json.getValue(Json.loadSync('onchain/tokens.json'), [network, token])
return {
network: +network,
decimals: +data['decimals'],
address: data['address']
}
}
export async function getTokenAddress(network: string, token: string): Promise<string> {
return (
await getPathstringBasedContent<string>('onchain/tokenAddresses.json', [
@ -293,11 +412,23 @@ export namespace Onchain {
)[0]
}
export function getTokenAddressSync(network: string, token: string): string {
return getPathstringBasedContentSync<string>('onchain/tokenAddresses.json', [
{ network: network, token: token }
])[0]
}
export async function getTokenDecimals(network: string, token: string): Promise<number> {
return (
await getPathstringBasedContent<number>('onchain/decimals.json', [{ network: network, token: token }])
)[0]
}
export function getTokenDecimalsSync(network: string, token: string): number {
return getPathstringBasedContentSync<number>('onchain/decimals.json', [
{ network: network, token: token }
])[0]
}
}
export namespace Offchain {
@ -317,9 +448,29 @@ export namespace Offchain {
return rpcs.get(keys.next().value)!
}
export function getUncensoredRpcURLSync(network: string, name: string = ''): string {
const rpcs = Json.toMap<string>(
Json.getValue(Json.loadSync('offchain/infrastructure.json'), ['jrpc-uncensored', network])
)
if (name.length !== 0) {
return rpcs.get(name)!
}
let keys = rpcs.keys()
let randCount = NumberUtils.getRandomFromRange(0, rpcs.size - 1)
for (let i = 0; i < randCount; i++) keys.next()
return rpcs.get(keys.next().value)!
}
export async function getClassicSubgraphURL(network: string): Promise<string> {
return Json.getValue(await Json.load('offchain/infrastructure.json'), ['subgraph', network])
}
export function getClassicSubgraphURLSync(network: string): string {
return Json.getValue(Json.loadSync('offchain/infrastructure.json'), ['subgraph', network])
}
}
export namespace Constants {

@ -1,13 +0,0 @@
# RPC URLs
ETH_MAINNET_TEST_RPC=
# debug (debug events are logged to console)
DEBUG=
# use tor (torify tests)
TORIFY=
# tor port (regular = 9050, browser = 9150)
TOR_PORT=
# relayer DOMAIN (the example.xyz in https://example.xyz) for testing
TEST_RELAYER_DOMAIN=

@ -0,0 +1 @@
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.

@ -39,12 +39,13 @@
"fs-extra": "^11.1.0",
"mocha": "^10.2.0",
"prettier": "^2.3.0",
"rimraf": "^4.4.0",
"rimraf": "^5.0.0",
"source-map-support": "^0.5.19",
"ts-essentials": "^9.3.1",
"ts-node": "^10.9.1",
"tsc-alias": "^1.2.11",
"tsconfig-paths": "^4.1.2",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
},
"scripts": {
@ -55,7 +56,7 @@
"postversion": "git push --follow-tags && npm publish",
"lint": "eslint --ext ts,js --fix src",
"build-live": "tsc -w && tsc-alias -w",
"clean": "rm -rf --interactive=never cache/*",
"clean": "rm -rf --interactive=never docs/*",
"format": "prettier src/*.ts -w"
},
"files": [

@ -176,6 +176,31 @@ export namespace AsyncUtils {
export function timeout(msTimeout: number): Promise<any> {
return new Promise((resolve) => setTimeout(resolve, msTimeout))
}
export class SimpleMutex {
private _mutexes: Map<string, Promise<any>>
constructor() {
this._mutexes = new Map<string, Promise<any>>()
}
async acquire(parentCallName: string): Promise<Function> {
let release: Function = () => null
const prevMutex = this._mutexes.get(parentCallName)
this._mutexes.set(
parentCallName,
new Promise((resolve) => {
release = resolve
})
)
if (prevMutex) await prevMutex
return release
}
}
}
export namespace NumberUtils {

@ -1,13 +0,0 @@
# RPC URLs
ETH_MAINNET_TEST_RPC=
# debug (debug events are logged to console)
DEBUG=
# use tor (torify tests)
TORIFY=
# tor port (regular = 9050, browser = 9150)
TOR_PORT=
# relayer DOMAIN (the example.xyz in https://example.xyz) for testing
TEST_RELAYER_DOMAIN=

@ -0,0 +1 @@
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.

@ -43,12 +43,13 @@
"fs-extra": "^11.1.0",
"mocha": "^10.2.0",
"prettier": "^2.3.0",
"rimraf": "^4.4.0",
"rimraf": "^5.0.0",
"source-map-support": "^0.5.19",
"ts-essentials": "^9.3.1",
"ts-node": "^10.9.1",
"tsc-alias": "^1.2.11",
"tsconfig-paths": "^4.1.2",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
},
"scripts": {
@ -60,7 +61,7 @@
"postversion": "git push --follow-tags && npm publish",
"lint": "eslint --ext ts,js --fix src",
"build-live": "tsc -w && tsc-alias -w",
"clean": "rm -rf --interactive=never cache/*",
"clean": "rm -rf --interactive=never docs/*",
"format": "prettier src/*.ts -w"
},
"files": [

@ -228,7 +228,7 @@ export class Relayer {
.catch((err) => {
throw ErrorUtils.ensureError(err)
})
.then((res) => res.data.prices[token])
.then((res) => BigNumber.from(res.data['ethPrices'][token]))
)
}
@ -259,7 +259,7 @@ export class Relayer {
finished = true
}
if (status == 'CONFIRMED') {
if (status === 'CONFIRMED') {
result.success = true
result.txHash = txHash
finished = true
@ -298,6 +298,10 @@ export class Relayer {
throw ErrorUtils.getError(`Relayer.fromCache: relayer ${options.url} isn't stored in cache.`)
})
await cache.close().catch((err) => {
throw ErrorUtils.ensureError(err)
})
return new Relayer(options, properties)
}
@ -305,7 +309,7 @@ export class Relayer {
* Cache relayer data into a PouchDB database in your cache folder. This will automatically fetch properties if they are not fetched.
*/
async remember(): Promise<void> {
if (!this._fetched) await this.fetchProperties()
this._propertiesFetched('remember')
const cache = new Cache.Base<Docs.Relayer>('Relayers')

@ -1,13 +0,0 @@
# RPC URLs
ETH_MAINNET_TEST_RPC=
# debug (debug events are logged to console)
DEBUG=
# use tor (torify tests)
TORIFY=
# tor port (regular = 9050, browser = 9150)
TOR_PORT=
# relayer DOMAIN (the example.xyz in https://example.xyz) for testing
TEST_RELAYER_DOMAIN=

@ -0,0 +1 @@
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.

@ -31,8 +31,10 @@
"eslint-plugin-prettier": "^4.2.1",
"ethers": "^5",
"prettier": "^2.3.0",
"rimraf": "^5.0.0",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.2",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
},
"main": "./build/index.js",
@ -43,7 +45,7 @@
"postversion": "git push --follow-tags && npm publish",
"lint": "eslint --ext ts,js --fix src",
"build-live": "tsc -w && tsc-alias -w",
"clean": "rm -rf --interactive=never cache/*",
"clean": "rm -rf --interactive=never docs/*",
"format": "prettier src/*.ts -w"
},
"files": [

@ -1,7 +1,9 @@
# tornado-sdk
# Tornado Cash SDK
SDK to integrate your protocol with Tornado Cash.
[Check out the docs here.](./docs/ABOUT.md)
Contributions are welcome, we are here for freedom after all!
See [HISTORY.md](./HISTORY.md) for a development log.

12
docs/ABOUT.md Normal file

@ -0,0 +1,12 @@
# Tornado Cash SDK
A collection of Typescript packages which enable you to easily use the Tornado Cash protocol.
* [Installation](./INSTALLATION.md)
* [Usage](./USAGE.md)
### Package list and short descriptions
* **@tornado/sdk** - this package bundles all of the below packages into one, which can then be imported into a project. It does not provide a default import, instead you may alias it as wished.
* **@tornado/sdk-core** - this package provides `Core` which bundles the main Tornado Cash Classic logic into one class. In short, you can build deposit & withdrawal transactions, synchronize deposit and event caches more easily, listen to new deposit or withdrawal events

19
docs/INSTALLATION.md Normal file

@ -0,0 +1,19 @@
# Installation
## Configuring Gitea
In order to use any of the packages, you have configure `npm` / `yarn` / `pnpm` to work with Gitea.
The [official guide is here](https://development.tornadocash.community/T-Hax/gitea-guide), you can also download it by doing:
```bash
git clone https://development.tornadocash.community/T-Hax/gitea-guide
```
If you want to skip doing the entire configuration, you can just add to your `.npmrc` (if using `npm`), the following, albeit this is limited and untested, but should work for `npm`:
```yaml
@tornado:registry=https://development.tornadocash.community/api/packages/T-Hax/npm/
```
## Installing the packages

1
docs/USAGE.md Normal file

@ -0,0 +1 @@
# Usage

@ -69,6 +69,7 @@
"format:crypto": "yarn workspace @tornado/sdk-crypto run format",
"format:chain": "yarn workspace @tornado/sdk-chain run format",
"format:utils": "yarn workspace @tornado/sdk-utils run format",
"docs": "yarn workspaces foreach run typedocs --out docs src/index.ts",
"compile": "yarn workspaces foreach run tsc",
"compile:sdk": "yarn workspace @tornado/sdk tsc",
"compile:core": "yarn workspace @tornado/sdk-core tsc",
@ -77,6 +78,14 @@
"compile:crypto": "yarn workspace @tornado/sdk-crypto tsc",
"compile:chain": "yarn workspace @tornado/sdk-chain tsc",
"compile:utils": "yarn workspace @tornado/sdk-utils tsc",
"prepare": "yarn workspaces foreach run format && yarn workspaces foreach run tsc",
"prepare:sdk": "yarn format:sdk && yarn compile:sdk",
"prepare:core": "yarn format:core && yarn compile:core",
"prepare:web": "yarn format:web && yarn compile:web",
"prepare:data": "yarn format:data && yarn compile:data",
"prepare:crypto": "yarn format:crypto && yarn compile:crypto",
"prepare:chain": "yarn format:chain && yarn compile:chain",
"prepare:utils": "yarn format:utils && yarn compile:utils",
"typechain": "yarn typechain:test && yarn typechain:core && yarn typechain:chain",
"typechain:test": "typechain --target \"ethers-v5\" --discriminate-types --glob \"./abis/*.json\" --out-dir=\"./test/deth\"",
"typechain:core": "typechain --target \"ethers-v5\" --discriminate-types --glob \"./abis/*.json\" --out-dir=\"./@tornado/sdk-core/src/deth\"",

@ -2,6 +2,7 @@ import chai from 'chai'
import * as ganache from 'ganache'
// External
import { once } from 'events'
import { solidity } from 'ethereum-waffle'
import { providers, BigNumber } from 'ethers'
import { parseUnits } from 'ethers/lib/utils'
@ -21,6 +22,10 @@ import eth01DepositsReference from './resources/deposits_eth_0.1.json'
import eth1DepositsReference from './resources/deposits_eth_1.json'
import eth10DepositsReference from './resources/deposits_eth_10.json'
import eth100DepositsReference from './resources/deposits_eth_100.json'
import dai100DepositsReference from './resources/deposits_dai_100.json'
import dai1000DepositsReference from './resources/deposits_dai_1000.json'
import dai10000DepositsReference from './resources/deposits_dai_10000.json'
import dai100KDepositsReference from './resources/deposits_dai_100000.json'
chai.use(solidity)
@ -61,10 +66,6 @@ describe('Core', () => {
const chain = new Chain(ganacheProvider)
it('Should print cache path to console', async () => {
console.log(await Files.getCachePath('anything'))
})
after(async function () {
this.timeout(0)
await Files.wipeCache()
@ -72,9 +73,8 @@ describe('Core', () => {
describe('namespace Contracts', () => {
it('getClassicInstance: should be able to get a tornado instance', async () => {
let instance = await Contracts.getInstance(String(1), 'eth', String(1), mainnetProvider)
let instance = Contracts.getInstance(String(1), 'eth', String(1), mainnetProvider)
expect(instance.address).to.equal('0x47CE0C6eD5B0Ce3d3A51fdb1C52DC66a7c3c2936')
await expect(instance.getLastRoot()).to.not.be.reverted
}).timeout(0)
})
@ -87,15 +87,19 @@ describe('Core', () => {
if (!denominations.length) throw ErrorUtils.getError('Instances entered were INVALID')
const depositReferences: { [key: string]: typeof eth01DepositsReference } = {
'1ETH0.1': eth01DepositsReference,
'1ETH1': eth1DepositsReference,
'1ETH10': eth10DepositsReference,
'1ETH100': eth100DepositsReference,
'1DAI100000': dai100KDepositsReference
}
let depositReferences: { [key: string]: typeof eth01DepositsReference } = {}
const core = new Core(mainnetProvider)
depositReferences['1ETH0.1'] = eth01DepositsReference
depositReferences['1ETH1'] = eth1DepositsReference
depositReferences['1ETH10'] = eth10DepositsReference
depositReferences['1ETH100'] = eth100DepositsReference
depositReferences['1DAI100'] = dai100DepositsReference
depositReferences['1DAI1000'] = dai1000DepositsReference
depositReferences['1DAI10000'] = dai10000DepositsReference
depositReferences['1DAI100000'] = dai100KDepositsReference
const core = new Core()
let instances: TornadoInstance[] = []
@ -110,7 +114,9 @@ describe('Core', () => {
before(async function () {
this.timeout(0)
const regexp = /([0-9]+)([A-Z]+)([0-9]+)/
await core.connect(mainnetProvider)
const regexp = /([0-9]+)([A-Za-z]+)([0-9.]+)/
const promises = denominations.map((denom) => {
const matches = denom.match(regexp)!.slice(2)
@ -133,7 +139,7 @@ describe('Core', () => {
// This is going to try syncing the entire range
await core.syncDeposits(instances[i], {
blockDivisor: 50,
blockDivisor: 40,
concurrencyLimit: 20,
msTimeout: 300
})
@ -144,6 +150,8 @@ describe('Core', () => {
expect(rows.length).to.be.gte(valid.length)
console.log('\n📄 Validating inputs for ' + denominations[i] + '\n')
for (let i = 0, len = valid.length; i < len; i++) {
const id = rows[i].id
const [bn, leafIndex, commitment] = parseIndexableString(id)
@ -160,31 +168,40 @@ describe('Core', () => {
describe('Forked (Ganache)', async () => {
describe('class Classic', async () => {
// Init sync objects
const core = new Core(ganacheProvider)
const core = new Core()
const needsMoney = ganacheProvider.getSigner()
const withdrawer = ganacheProvider.getSigner(2)
const daiWhaleSigner = ganacheProvider.getSigner(daiWhale)
const debugListener = (message: string) => console.debug(message)
let snapshotId: any
let needsMoneyAddress: string
let withdrawerAddress: string
let dai: ERC20
let smallestEth: TornadoInstance
let dai100K: TornadoInstance
before(async function () {
this.timeout(0)
// We need to connect core first
await core.connect(ganacheProvider)
// Get snapshot just in case
snapshotId = await ganacheProvider.send('evm_snapshot', [])
// Prep whale eth balance
await ganacheProvider.send('evm_setAccountBalance', [daiWhale, parseUnits('10').toHexString()])
// Init async objects
// Addresses
needsMoneyAddress = await needsMoney.getAddress()
withdrawerAddress = await withdrawer.getAddress()
daiAddress = await Onchain.getTokenAddress('1', 'dai')
dai = chain.getTokenContract(daiAddress).connect(daiWhaleSigner)
smallestEth = await core.getInstance('eth', 0.1)
dai100K = await core.getInstance('dai', 100000)
// Contracts
dai = chain.getTokenContract(daiAddress)
smallestEth = core.getInstance('eth', 0.1)
dai100K = core.getInstance('dai', 100000)
// Set debug
if (debug) core.on('debug', debugListener)
@ -194,7 +211,7 @@ describe('Core', () => {
await ganacheProvider.send('evm_revert', [snapshotId])
core.off('debug', debugListener)
})
afterEach(() => {
beforeEach(() => {
dai = dai.connect(daiWhaleSigner)
})
@ -202,31 +219,27 @@ describe('Core', () => {
const initBal = await needsMoney.getBalance()
// Build tx and load cache for this test
const tx = await core.buildDepositTransaction(smallestEth)
const cache = core.loadDepositCache('Deposits1ETH0.1')
const tx = core.buildDepositTransaction(smallestEth)
// Prep promise to only try withdrawing after cache has been updated
const putPromise = new Promise((resolve) => {
smallestEth.on(
smallestEth.filters.Deposit(null, null, null),
function (commitment, leafIndex, timestamp, event) {
resolve(cache.db.put(cache.buildDoc(event)))
}
)
})
// Listen to deposit events
core.listenForDeposits(smallestEth)
const listener = smallestEth.listeners(smallestEth.filters.Deposit(null, null, null))[0]
// Get the promise we need
const promise = once(core, 'deposit')
// Deposit and await cache updated
const response = await needsMoney.sendTransaction(tx.request)
await response.wait()
const endBal = await needsMoney.getBalance()
// Passing resolve as callback into put didn't work
await await putPromise
// Await deposit addition to cache
await promise
// Turn off listener (NEEDED OR WE'RE NOT RESOLVING)
smallestEth.off(smallestEth.filters.Deposit(null, null, null), listener)
// Remove listeners
core.clearListeners(smallestEth)
// Backup
await core.backupNote(smallestEth, tx)
// Check deposit predicates
expect(initBal).to.equal(parseUnits('1000'))
@ -234,24 +247,18 @@ describe('Core', () => {
}).timeout(0)
it('buildDepositProof: it should be able to build an eth proof', async () => {
// Get withdrawer, load cache, prep note for this test
const withdrawer = ganacheProvider.getSigner(2)
const cache = core.loadDepositCache('Deposits1ETH0.1')
// We need this to clean the cache, we want to have clean state
const doc = (await cache.db.allDocs({ include_docs: true, descending: true, limit: 1 })).rows[0].doc
// We are not transforming because we want to test this out
// Get all of the notes
const notes = await core.loadNotes()
// Build proof
let proof: any
try {
proof = await core.buildDepositProof(
smallestEth,
{
address: await withdrawer.getAddress()
address: withdrawerAddress
},
await needsMoney.getAddress(),
needsMoneyAddress,
notes[0],
{
// On by default but stating for visibility
@ -259,9 +266,6 @@ describe('Core', () => {
checkKnownRoot: true
}
)
} finally {
await cache.db.remove(doc?._id!, doc?._rev!)
}
// Substract the calculated fee from the received amount
const ethDelta = parseUnits('0.1').sub(proof[5])
@ -278,26 +282,22 @@ describe('Core', () => {
it('buildDepositTransaction: build a single token deposit tx and succeed', async () => {
// Prep deposit amount, proxy for approval, cache, bal for comp
const depositAmount = parseUnits('100000')
const proxy = await core.getProxy()
const cache = core.loadDepositCache('Deposits1DAI100000')
const proxy = core.getProxy()
const daiBalBef = await dai.balanceOf(dai100K.address)
// Prep promise to only try withdrawing after cache has been updated
const putPromise = new Promise((resolve) => {
dai100K.on(
dai100K.filters.Deposit(null, null, null),
function (commitment, leafIndex, timestamp, event) {
resolve(cache.db.put(cache.buildDoc(event)))
}
)
})
// We listen for deposits
core.listenForDeposits(dai100K)
const listener = dai100K.listeners()[0]
// We will wait for the event
const promise = once(core, 'deposit')
// Prep for deposit
await dai.transfer(needsMoneyAddress, depositAmount)
dai = dai.connect(needsMoney)
const tx = await core.buildDepositTransaction(dai100K)
const tx = core.buildDepositTransaction(dai100K)
// Approve dai for the proxy first (transferFrom)
await dai.approve(proxy.address, depositAmount)
@ -308,11 +308,14 @@ describe('Core', () => {
// Prep for check
const daiBalPost = await dai.balanceOf(dai100K.address)
// Passing resolve as callback into put didn't work
await await putPromise
// Passing resolve as callback into put didn't work.
await promise
// Off (otherwise no resolve)
dai100K.off(dai100K.filters.Deposit(null, null, null), listener)
// Have to clear the listeners
core.clearListeners(dai100K)
// Backup since we need it for later
await core.backupNote(dai100K, tx)
// Checks
expect(daiBalBef).to.equal(daiBalPost.sub(depositAmount))
@ -322,19 +325,14 @@ describe('Core', () => {
it('buildDepositProof: it should be able to build a token proof', async () => {
if (!process.env.TEST_RELAYER_DOMAIN) throw ErrorUtils.getError('core.test.ts: Need a relayer name')
// Get withdrawer, load cache, prep note for this test
const withdrawer = ganacheProvider.getSigner(2)
const cache = core.loadDepositCache('Deposits1DAI100000')
// We need this to clean the cache, we want to have clean state
const doc = (await cache.db.allDocs({ include_docs: true, descending: true, limit: 1 })).rows[0].doc
// We are not transforming because we want to test this out
// Get all of the notes
const notes = await core.loadNotes()
// We need to select last
const note = notes[notes.length - 1]
let properties: RelayerProperties = {
address: await withdrawer.getAddress(),
address: withdrawerAddress,
version: '2',
serviceFeePercent: 0.04,
miningFeePercent: 0.15,
@ -345,21 +343,12 @@ describe('Core', () => {
properties.prices.set('dai', BigNumber.from(10).pow(18).div(1800))
// Just set another address
properties.address = await withdrawer.getAddress()
// Build proof with relayer properties this time
let proof
try {
proof = await core.buildDepositProof(dai100K, properties, await needsMoney.getAddress(), note, {
const proof = await core.buildDepositProof(dai100K, properties, needsMoneyAddress, note, {
// On by default but stating for visibility
checkNotesSpent: true,
checkKnownRoot: true
})
} finally {
await cache.db.remove(doc?._id!, doc?._rev!)
}
// Calc balance diff again... it will be expressed in dai
const daiDelta = parseUnits('100000').sub(proof[5])
@ -371,58 +360,220 @@ describe('Core', () => {
).to.changeTokenBalance(dai, needsMoney, daiDelta)
}).timeout(0)
it.only('buildDepositTransactions: multiple eth deposits', async () => {
const instances = await core.getInstances(
it('buildDepositTransactions: multiple eth deposits', async () => {
const instances = core.getInstances(
[0.1, 1, 10, 100].map((el) => {
return { token: 'eth', denomination: el }
})
)
const txs = await core.buildDepositTransactions(instances, {
depositsPerInstance: [1, 1, 2, 1]
// That easy
instances.forEach((instance) => core.listenForDeposits(instance))
const depositsPer = [1, 1, 2, 1]
const txs = core.buildDepositTransactions(instances, {
depositsPerInstance: depositsPer
})
for (let i = 0, len = txs.length; i < len; i++) {
console.log('SENDING => ', i)
const promise = once(core, 'deposit')
const response = await needsMoney.sendTransaction(txs[i].request)
console.log('TX SENT => ', i)
await response.wait()
console.log('WAITING => ', i)
await promise
}
// That easy
instances.forEach((instance) => core.clearListeners(instance))
// And backup the notes
await Promise.all(
instances.map((instance, index) => core.backupNotes(instance, txs.splice(0, depositsPer[index])))
)
//for (let i = 0, len = instances.length; i < len; i++) {
// await core.backupNotes(instances[i], txs.splice(0, depositsPer[i]))
//}
expect(await needsMoney.getBalance()).to.be.lte(parseUnits('888.8'))
}).timeout(0)
it('buildDepositProofs: should be able to withdraw', async () => {
// ETH instances
const instances = core.getInstances(
[0.1, 1, 10, 100].map((el) => {
return { token: 'eth', denomination: el }
})
)
// Number deposits per instance
const depositsPer = [1, 1, 2, 1]
// Get all of the notes
let notes = await core.loadNotes()
// Handle all withdrawals
for (let i = 0, len = instances.length; i < len; i++) {
const proofs = await core.buildDepositProofs(
instances[i],
{
address: withdrawerAddress
},
new Array(depositsPer[i]).fill(needsMoneyAddress),
notes.splice(0, depositsPer[i]),
{
// On by default but stating for visibility
checkNotesSpent: true,
checkKnownRoot: true
}
)
for (let p = 0, plen = proofs.length; p < plen; p++) {
// Get proof
const proof = proofs[p]
// Substract the calculated fee from the received amount
const ethDelta = parseUnits('0.1')
.mul(10 ** i)
.sub(proof[5])
// Withdrawal time, let's see if it works
// The balance diff will be exact because withdrawer is paying for gas as relayer
await expect(() =>
instances[i]
.connect(withdrawer)
.withdraw(proof[0], proof[1], proof[2], proof[3], proof[4], proof[5], proof[6])
).to.changeEtherBalance(needsMoney, ethDelta)
}
}
}).timeout(0)
it('buildDepositTransactions: multiple token deposits', async () => {
const instances = await core.getInstances(
[100, 1000, 10000, 100000].map((el) => {
// Prepare contracts
const denoms = [100, 1000, 10000, 100000]
const proxy = core.getProxy()
const instances = core.getInstances(
denoms.map((el) => {
return { token: 'dai', denomination: el }
})
)
const proxy = await core.getProxy()
const depositAmount = parseUnits('432100')
// Prep the money
const depositsPer = [1, 2, 1, 2]
await dai.transfer(needsMoneyAddress, parseUnits('212100'))
await dai.transfer(needsMoneyAddress, parseUnits('432100'))
dai = dai.connect(needsMoney)
const txs = await core.buildDepositTransactions(instances, {
depositsPerInstance: [1, 2, 3, 4]
await dai.approve(proxy.address, parseUnits('212100'))
// Record the money
const daiBalancesBef = await Promise.all(instances.map((instance) => dai.balanceOf(instance.address)))
// Begin to listen
instances.forEach((instance) => core.listenForDeposits(instance))
// Build txs
const txs = core.buildDepositTransactions(instances, {
depositsPerInstance: depositsPer
})
await dai.approve(proxy.address, depositAmount)
// Send transactions
for (let i = 0, len = txs.length; i < len; i++) {
await expect(() => needsMoney.sendTransaction(txs[i].request)).to.not.be.reverted
const promise = once(core, 'deposit')
const resp = await needsMoney.sendTransaction(txs[i].request)
await resp.wait()
await promise
}
// Clear listeners
instances.forEach((instance) => core.clearListeners(instance))
// Backup notes
await Promise.all(
instances.map((instance, index) => core.backupNotes(instance, txs.splice(0, depositsPer[index])))
)
// Get new balances
const daiBalancesPost = await Promise.all(
instances.map((instance) => dai.balanceOf(instance.address))
)
// Check and done
for (let i = 0; i < 4; i++) {
expect(daiBalancesBef[i]).to.equal(
daiBalancesPost[i].sub(parseUnits('' + denoms[i] * depositsPer[i]))
)
}
expect(await dai.balanceOf(needsMoneyAddress)).to.equal(0)
}).timeout(0)
it('createInvoice: should be able to create an invoice', async () => {
const instance = await core.getInstance('dai', '1000')
const invoice = await core.createInvoice(instance)
console.log(invoice)
it('buildDepositProofs: multiple dai withdrawals', async () => {
// ETH instances
const denoms = [100, 1000, 10000, 100000]
const instances = core.getInstances(
denoms.map((el) => {
return { token: 'dai', denomination: el }
})
)
// Number deposits per instance
const depositsPer = [1, 2, 1, 2]
// Get all of the notes
let notes = await core.loadNotes()
// Fake relayer properties
let properties: RelayerProperties = {
address: withdrawerAddress,
version: '2',
serviceFeePercent: 0.04,
miningFeePercent: 0.15,
status: 'whatever',
chainId: 1,
prices: new Map<string, BigNumber>()
}
properties.prices.set('dai', BigNumber.from(10).pow(18).div(1800))
// Handle all withdrawals
for (let i = 0, len = instances.length; i < len; i++) {
const proofs = await core.buildDepositProofs(
instances[i],
properties,
new Array(depositsPer[i]).fill(needsMoneyAddress),
notes.splice(0, depositsPer[i]),
{
// On by default but stating for visibility
checkNotesSpent: true,
checkKnownRoot: true
}
)
for (let p = 0, plen = proofs.length; p < plen; p++) {
// Get proof
const proof = proofs[p]
// Substract the calculated fee from the received amount
const daiDelta = parseUnits("100")
.mul(10 ** i)
.sub(proof[5])
// Withdrawal time, let's see if it works
// The balance diff will be exact because withdrawer is paying for gas as relayer
await expect(() =>
instances[i]
.connect(withdrawer)
.withdraw(proof[0], proof[1], proof[2], proof[3], proof[4], proof[5], proof[6])
).to.changeTokenBalance(dai, needsMoney, daiDelta)
}
}
}).timeout(0)
})
})

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -24526,9 +24526,5 @@
"commitment": "0x05f2917b82f40a9b03c9cdaf1f2530817aa7c969e3c9dbdd45469ceb026aa513",
"leafIndex": 3503,
"timestamp": "1681468727"
},
{
"blockNumber": 17048787,
"transactionHash": null
}
]

@ -378334,9 +378334,5 @@
"commitment": "0x237c8901459592f4312e8fe108b86f72b946625dcf6ff9f5b5becb16c2f9638f",
"leafIndex": 54047,
"timestamp": "1681514471"
},
{
"blockNumber": 17048734,
"transactionHash": null
}
]

@ -331917,9 +331917,5 @@
"commitment": "0x1ceee9f2f5fc599df4d9b0ddf330e2e0faa3688cb8f38bc0bbf5ed4bd7872879",
"leafIndex": 47416,
"timestamp": "1681514255"
},
{
"blockNumber": 17048737,
"transactionHash": null
}
]

@ -221891,9 +221891,5 @@
"commitment": "0x2eb0ced50584cfa5fcf07106ada0d3361220615344e499ef11ea12c34a9614d8",
"leafIndex": 31698,
"timestamp": "1681441067"
},
{
"blockNumber": 17048741,
"transactionHash": null
}
]

128
yarn.lock

@ -1021,13 +1021,14 @@ __metadata:
mocha: ^10.2.0
pouchdb-collate: ^8.0.1
prettier: ^2.3.0
rimraf: ^4.4.0
rimraf: ^5.0.0
source-map-support: ^0.5.19
ts-essentials: ^9.3.1
ts-node: ^10.9.1
tsc-alias: ^1.2.11
tsconfig-paths: ^4.1.2
typechain: ^8.1.1
typedoc: ^0.24.6
typescript: ^5.0.4
dependenciesMeta:
tsconfig-paths@4.2.0:
@ -1069,7 +1070,7 @@ __metadata:
mocha: ^10.2.0
pouchdb-collate: ^8.0.1
prettier: ^2.3.0
rimraf: ^4.4.0
rimraf: ^5.0.0
snarkjs: "npm:@tornado/snarkjs@^0.1.20-p2"
source-map-support: ^0.5.19
ts-essentials: ^9.3.1
@ -1077,6 +1078,7 @@ __metadata:
tsc-alias: ^1.2.11
tsconfig-paths: ^4.1.2
typechain: ^8.1.1
typedoc: ^0.24.6
typescript: ^5.0.4
dependenciesMeta:
tsconfig-paths@4.2.0:
@ -1110,13 +1112,14 @@ __metadata:
fs-extra: ^11.1.0
mocha: ^10.2.0
prettier: ^2.3.0
rimraf: ^4.4.0
rimraf: ^5.0.0
snarkjs: "npm:@tornado/snarkjs@^0.1.20-p2"
source-map-support: ^0.5.19
ts-essentials: ^9.3.1
ts-node: ^10.9.1
tsc-alias: ^1.2.11
tsconfig-paths: ^4.1.2
typedoc: ^0.24.6
typescript: ^5.0.4
websnark: "npm:@tornado/websnark@^0.0.4-p1"
dependenciesMeta:
@ -1153,12 +1156,13 @@ __metadata:
pouchdb-adapter-memory: ^8.0.1
pouchdb-collate: ^8.0.1
prettier: ^2.3.0
rimraf: ^4.4.0
rimraf: ^5.0.0
source-map-support: ^0.5.19
ts-essentials: ^9.3.1
ts-node: ^10.9.1
tsc-alias: ^1.2.11
tsconfig-paths: ^4.1.2
typedoc: ^0.24.6
typescript: ^5.0.4
dependenciesMeta:
tsconfig-paths@4.2.0:
@ -1229,13 +1233,14 @@ __metadata:
fs-extra: ^11.1.0
mocha: ^10.2.0
prettier: ^2.3.0
rimraf: ^4.4.0
rimraf: ^5.0.0
snarkjs: "npm:@tornado/snarkjs@^0.1.20-p2"
source-map-support: ^0.5.19
ts-essentials: ^9.3.1
ts-node: ^10.9.1
tsc-alias: ^1.2.11
tsconfig-paths: ^4.1.2
typedoc: ^0.24.6
typescript: ^5.0.4
dependenciesMeta:
tsconfig-paths@4.2.0:
@ -1270,13 +1275,14 @@ __metadata:
fs-extra: ^11.1.0
mocha: ^10.2.0
prettier: ^2.3.0
rimraf: ^4.4.0
rimraf: ^5.0.0
socks-proxy-agent: ^7.0.0
source-map-support: ^0.5.19
ts-essentials: ^9.3.1
ts-node: ^10.9.1
tsc-alias: ^1.2.11
tsconfig-paths: ^4.1.2
typedoc: ^0.24.6
typescript: ^5.0.4
web3-providers-http: "npm:@tornado/web3-providers-http@^1.6.5-p1"
dependenciesMeta:
@ -1303,8 +1309,10 @@ __metadata:
eslint-plugin-prettier: ^4.2.1
ethers: ^5
prettier: ^2.3.0
rimraf: ^5.0.0
ts-node: ^10.9.1
tsconfig-paths: ^4.1.2
typedoc: ^0.24.6
typescript: ^5.0.4
dependenciesMeta:
tsconfig-paths@4.2.0:
@ -2235,6 +2243,13 @@ __metadata:
languageName: node
linkType: hard
"ansi-sequence-parser@npm:^1.1.0":
version: 1.1.0
resolution: "ansi-sequence-parser@npm:1.1.0"
checksum: 75f4d3a4c555655a698aec05b5763cbddcd16ccccdbfd178fb0aa471ab74fdf98e031b875ef26e64be6a95cf970c89238744b26de6e34af97f316d5186b1df53
languageName: node
linkType: hard
"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1":
version: 3.2.1
resolution: "ansi-styles@npm:3.2.1"
@ -5457,18 +5472,6 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:^9.2.0":
version: 9.3.5
resolution: "glob@npm:9.3.5"
dependencies:
fs.realpath: ^1.0.0
minimatch: ^8.0.2
minipass: ^4.2.4
path-scurry: ^1.6.1
checksum: 94b093adbc591bc36b582f77927d1fb0dbf3ccc231828512b017601408be98d1fe798fc8c0b19c6f2d1a7660339c3502ce698de475e9d938ccbb69b47b647c84
languageName: node
linkType: hard
"global-modules@npm:^0.2.3":
version: 0.2.3
resolution: "global-modules@npm:0.2.3"
@ -6501,6 +6504,13 @@ __metadata:
languageName: node
linkType: hard
"jsonc-parser@npm:^3.2.0":
version: 3.2.0
resolution: "jsonc-parser@npm:3.2.0"
checksum: 946dd9a5f326b745aa326d48a7257e3f4a4b62c5e98ec8e49fa2bdd8d96cef7e6febf1399f5c7016114fd1f68a1c62c6138826d5d90bc650448e3cf0951c53c7
languageName: node
linkType: hard
"jsonfile@npm:^4.0.0":
version: 4.0.0
resolution: "jsonfile@npm:4.0.0"
@ -7015,6 +7025,13 @@ __metadata:
languageName: node
linkType: hard
"lunr@npm:^2.3.9":
version: 2.3.9
resolution: "lunr@npm:2.3.9"
checksum: 176719e24fcce7d3cf1baccce9dd5633cd8bdc1f41ebe6a180112e5ee99d80373fe2454f5d4624d437e5a8319698ca6837b9950566e15d2cae5f2a543a3db4b8
languageName: node
linkType: hard
"make-error@npm:^1.1.1":
version: 1.3.6
resolution: "make-error@npm:1.3.6"
@ -7069,6 +7086,15 @@ __metadata:
languageName: node
linkType: hard
"marked@npm:^4.3.0":
version: 4.3.0
resolution: "marked@npm:4.3.0"
bin:
marked: bin/marked.js
checksum: 0db6817893952c3ec710eb9ceafb8468bf5ae38cb0f92b7b083baa13d70b19774674be04db5b817681fa7c5c6a088f61300815e4dd75a59696f4716ad69f6260
languageName: node
linkType: hard
"matched@npm:^0.4.1":
version: 0.4.4
resolution: "matched@npm:0.4.4"
@ -7343,15 +7369,6 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^8.0.2":
version: 8.0.4
resolution: "minimatch@npm:8.0.4"
dependencies:
brace-expansion: ^2.0.1
checksum: 2e46cffb86bacbc524ad45a6426f338920c529dd13f3a732cc2cf7618988ee1aae88df4ca28983285aca9e0f45222019ac2d14ebd17c1edadd2ee12221ab801a
languageName: node
linkType: hard
"minimatch@npm:^9.0.0":
version: 9.0.0
resolution: "minimatch@npm:9.0.0"
@ -7449,7 +7466,7 @@ __metadata:
languageName: node
linkType: hard
"minipass@npm:^4.0.0, minipass@npm:^4.2.4":
"minipass@npm:^4.0.0":
version: 4.2.8
resolution: "minipass@npm:4.2.8"
checksum: 7f4914d5295a9a30807cae5227a37a926e6d910c03f315930fde52332cf0575dfbc20295318f91f0baf0e6bb11a6f668e30cde8027dea7a11b9d159867a3c830
@ -8276,7 +8293,7 @@ __metadata:
languageName: node
linkType: hard
"path-scurry@npm:^1.6.1, path-scurry@npm:^1.7.0":
"path-scurry@npm:^1.7.0":
version: 1.7.0
resolution: "path-scurry@npm:1.7.0"
dependencies:
@ -9101,17 +9118,6 @@ __metadata:
languageName: node
linkType: hard
"rimraf@npm:^4.4.0":
version: 4.4.1
resolution: "rimraf@npm:4.4.1"
dependencies:
glob: ^9.2.0
bin:
rimraf: dist/cjs/src/bin.js
checksum: b786adc02651e2e24bbedb04bbdea80652fc9612632931ff2d9f898c5e4708fe30956186597373c568bd5230a4dc2fadfc816ccacba8a1daded3a006a6b74f1a
languageName: node
linkType: hard
"rimraf@npm:^5.0.0":
version: 5.0.0
resolution: "rimraf@npm:5.0.0"
@ -9399,6 +9405,18 @@ __metadata:
languageName: node
linkType: hard
"shiki@npm:^0.14.1":
version: 0.14.2
resolution: "shiki@npm:0.14.2"
dependencies:
ansi-sequence-parser: ^1.1.0
jsonc-parser: ^3.2.0
vscode-oniguruma: ^1.7.0
vscode-textmate: ^8.0.0
checksum: f2a14302b1803617e3ff1b751a5c87b4af4ad15214dc00e9215402e42940a84a0b956cf55d628f25dbf1296b18e277b8529571cd9359b971ac599a0ab11303e7
languageName: node
linkType: hard
"side-channel@npm:^1.0.4":
version: 1.0.4
resolution: "side-channel@npm:1.0.4"
@ -10397,6 +10415,22 @@ __metadata:
languageName: node
linkType: hard
"typedoc@npm:^0.24.6":
version: 0.24.6
resolution: "typedoc@npm:0.24.6"
dependencies:
lunr: ^2.3.9
marked: ^4.3.0
minimatch: ^9.0.0
shiki: ^0.14.1
peerDependencies:
typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x
bin:
typedoc: bin/typedoc
checksum: 3911ef6a7736ce6655a4b22fabc5be6df8812412e209d730fd168bfa7797847897f05fed797bc16558f11ce647b20746453748ff8afe85b1375c3efe2d7b57df
languageName: node
linkType: hard
"typescript@npm:^5.0.4":
version: 5.0.4
resolution: "typescript@npm:5.0.4"
@ -10686,6 +10720,20 @@ __metadata:
languageName: node
linkType: hard
"vscode-oniguruma@npm:^1.7.0":
version: 1.7.0
resolution: "vscode-oniguruma@npm:1.7.0"
checksum: 53519d91d90593e6fb080260892e87d447e9b200c4964d766772b5053f5699066539d92100f77f1302c91e8fc5d9c772fbe40fe4c90f3d411a96d5a9b1e63f42
languageName: node
linkType: hard
"vscode-textmate@npm:^8.0.0":
version: 8.0.0
resolution: "vscode-textmate@npm:8.0.0"
checksum: 127780dfea89559d70b8326df6ec344cfd701312dd7f3f591a718693812b7852c30b6715e3cfc8b3200a4e2515b4c96f0843c0eacc0a3020969b5de262c2a4bb
languageName: node
linkType: hard
"vuvuzela@npm:1.0.3":
version: 1.0.3
resolution: "vuvuzela@npm:1.0.3"