application.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991
  1. /* eslint-disable camelcase */
  2. /* eslint-disable no-console, import/order */
  3. import Web3 from 'web3'
  4. import networkConfig from '@/networkConfig'
  5. import { cachedEventsLength, eventsType } from '@/constants'
  6. import MulticallABI from '@/abis/Multicall.json'
  7. import InstanceABI from '@/abis/Instance.abi.json'
  8. import TornadoProxyABI from '@/abis/TornadoProxy.abi.json'
  9. import { ACTION, ACTION_GAS } from '@/constants/variables'
  10. import { graph, treesInterface, EventsFactory } from '@/services'
  11. import {
  12. randomBN,
  13. parseNote,
  14. toFixedHex,
  15. saveAsFile,
  16. isEmptyArray,
  17. decimalPlaces,
  18. parseHexNote,
  19. checkCommitments,
  20. buffPedersenHash
  21. } from '@/utils'
  22. import { buildGroth16, download, getTornadoKeys } from './snark'
  23. let groth16
  24. const websnarkUtils = require('websnark/src/utils')
  25. const { toWei, numberToHex, toBN, isAddress } = require('web3-utils')
  26. const getStatisticStore = (acc, { tokens }) => {
  27. Object.entries(tokens).forEach(([currency, { instanceAddress }]) => {
  28. acc[currency] = Object.assign({}, acc[currency])
  29. Object.keys(instanceAddress).forEach((amount) => {
  30. if (!acc[currency][amount]) {
  31. acc[currency][amount] = {
  32. latestDeposits: [],
  33. nextDepositIndex: null,
  34. anonymitySet: null
  35. }
  36. }
  37. })
  38. })
  39. return acc
  40. }
  41. const defaultStatistics = Object.values(networkConfig).reduce(getStatisticStore, {})
  42. const state = () => {
  43. return {
  44. note: null,
  45. commitment: null,
  46. prefix: null,
  47. notes: {},
  48. statistic: defaultStatistics,
  49. ip: {},
  50. selectedInstance: { currency: 'eth', amount: 0.1 },
  51. selectedStatistic: { currency: 'eth', amount: 0.1 },
  52. withdrawType: 'relayer',
  53. ethToReceive: '20000000000000000',
  54. defaultEthToReceive: '20000000000000000',
  55. withdrawNote: ''
  56. }
  57. }
  58. const mutations = {
  59. SAVE_DEPOSIT(state, { note, commitment, prefix }) {
  60. state.note = note
  61. state.commitment = commitment
  62. state.prefix = prefix
  63. },
  64. SAVE_PROOF(state, { proof, args, note }) {
  65. this._vm.$set(state.notes, note, { proof, args })
  66. },
  67. REMOVE_PROOF(state, { note }) {
  68. this._vm.$delete(state.notes, note)
  69. },
  70. SAVE_LAST_INDEX(state, { nextDepositIndex, anonymitySet, currency, amount }) {
  71. const currentState = state.statistic[currency][amount]
  72. this._vm.$set(state.statistic[currency], `${amount}`, { ...currentState, nextDepositIndex, anonymitySet })
  73. },
  74. SAVE_LAST_EVENTS(state, { latestDeposits, currency, amount }) {
  75. const currentState = state.statistic[currency][amount]
  76. this._vm.$set(state.statistic[currency], `${amount}`, { ...currentState, latestDeposits })
  77. },
  78. SET_SELECTED_INSTANCE(state, selectedInstance) {
  79. state.selectedInstance = selectedInstance
  80. },
  81. SET_SELECTED_STATISTIC(state, selectedStatistic) {
  82. state.selectedStatistic = selectedStatistic
  83. },
  84. SET_WITHDRAW_TYPE(state, { withdrawType }) {
  85. this._vm.$set(state, 'withdrawType', withdrawType)
  86. },
  87. SAVE_ETH_TO_RECEIVE(state, { ethToReceive }) {
  88. this._vm.$set(state, 'ethToReceive', ethToReceive)
  89. },
  90. SAVE_DEFAULT_ETH_TO_RECEIVE(state, { ethToReceive }) {
  91. this._vm.$set(state, 'defaultEthToReceive', ethToReceive)
  92. },
  93. SET_WITHDRAW_NOTE(state, withdrawNote) {
  94. state.withdrawNote = withdrawNote
  95. }
  96. }
  97. const getters = {
  98. eventsInterface: (state, getters, rootState, rootGetters) => {
  99. const netId = rootGetters['metamask/netId']
  100. const { url } = rootState.settings[`netId${netId}`].rpc
  101. return new EventsFactory(url)
  102. },
  103. instanceContract: (state, getters, rootState) => ({ currency, amount, netId }) => {
  104. const config = networkConfig[`netId${netId}`]
  105. const { url } = rootState.settings[`netId${netId}`].rpc
  106. const address = config.tokens[currency].instanceAddress[amount]
  107. const web3 = new Web3(url)
  108. return new web3.eth.Contract(InstanceABI, address)
  109. },
  110. multicallContract: (state, getters, rootState) => ({ netId }) => {
  111. const config = networkConfig[`netId${netId}`]
  112. const { url } = rootState.settings[`netId${netId}`].rpc
  113. const web3 = new Web3(url)
  114. return new web3.eth.Contract(MulticallABI, config.multicall)
  115. },
  116. tornadoProxyContract: (state, getters, rootState) => ({ netId }) => {
  117. const {
  118. 'tornado-proxy.contract.tornadocash.eth': tornadoProxy,
  119. 'tornado-router.contract.tornadocash.eth': tornadoRouter,
  120. 'tornado-proxy-light.contract.tornadocash.eth': tornadoProxyLight
  121. } = networkConfig[`netId${netId}`]
  122. const proxyContract = tornadoRouter || tornadoProxy || tornadoProxyLight
  123. const { url } = rootState.settings[`netId${netId}`].rpc
  124. const web3 = new Web3(url)
  125. return new web3.eth.Contract(TornadoProxyABI, proxyContract)
  126. },
  127. currentContract: (state, getters) => (params) => {
  128. return getters.tornadoProxyContract(params)
  129. },
  130. withdrawGas: (state, getters) => {
  131. let action = ACTION.WITHDRAW_WITH_EXTRA
  132. if (getters.hasEnabledLightProxy) {
  133. action = ACTION.WITHDRAW
  134. }
  135. if (getters.isOptimismConnected) {
  136. action = ACTION.OP_WITHDRAW
  137. }
  138. if (getters.isArbitrumConnected) {
  139. action = ACTION.ARB_WITHDRAW
  140. }
  141. return ACTION_GAS[action]
  142. },
  143. networkFee: (state, getters, rootState, rootGetters) => {
  144. const gasPrice = rootGetters['gasPrices/gasPrice']
  145. const networkFee = toBN(gasPrice).mul(toBN(getters.withdrawGas))
  146. if (getters.isOptimismConnected) {
  147. const l1Fee = rootGetters['gasPrices/l1Fee']
  148. return networkFee.add(toBN(l1Fee))
  149. }
  150. return networkFee
  151. },
  152. relayerFee: (state, getters, rootState, rootGetters) => {
  153. const { currency, amount } = rootState.application.selectedStatistic
  154. const { decimals } = rootGetters['metamask/networkConfig'].tokens[currency]
  155. const nativeCurrency = rootGetters['metamask/nativeCurrency']
  156. const total = toBN(rootGetters['token/fromDecimals'](amount.toString()))
  157. const fee = rootState.relayer.selectedRelayer.tornadoServiceFee
  158. const decimalsPoint = decimalPlaces(fee)
  159. const roundDecimal = 10 ** decimalsPoint
  160. const aroundFee = toBN(parseInt(fee * roundDecimal, 10))
  161. const tornadoServiceFee = total.mul(aroundFee).div(toBN(roundDecimal * 100))
  162. const ethFee = getters.networkFee
  163. switch (currency) {
  164. case nativeCurrency: {
  165. return ethFee.add(tornadoServiceFee)
  166. }
  167. default: {
  168. const tokenFee = ethFee.mul(toBN(10 ** decimals)).div(toBN(rootState.price.prices[currency]))
  169. return tokenFee.add(tornadoServiceFee)
  170. }
  171. }
  172. },
  173. ethToReceiveInToken: (state, getters, rootState, rootGetters) => {
  174. const { currency } = rootState.application.selectedStatistic
  175. const { decimals } = rootGetters['metamask/networkConfig'].tokens[currency]
  176. const price = rootState.price.prices[currency]
  177. const ethToReceive = toBN(state.ethToReceive)
  178. return ethToReceive.mul(toBN(10 ** decimals)).div(toBN(price))
  179. },
  180. isNotEnoughTokens: (state, getters, rootState, rootGetters) => {
  181. const { amount, currency } = rootState.application.selectedStatistic
  182. let total = toBN(rootGetters['token/fromDecimals'](amount.toString()))
  183. if (state.withdrawType === 'relayer') {
  184. const relayerFee = getters.relayerFee
  185. const nativeCurrency = rootGetters['metamask/nativeCurrency']
  186. if (currency === nativeCurrency) {
  187. total = total.sub(relayerFee)
  188. } else {
  189. const ethToReceiveInToken = getters.ethToReceiveInToken
  190. total = total.sub(relayerFee).sub(ethToReceiveInToken)
  191. }
  192. }
  193. return total.isNeg()
  194. },
  195. maxEthToReceive: (state, getters, rootState, rootGetters) => {
  196. const { currency, amount } = rootState.application.selectedStatistic
  197. const { decimals } = rootGetters['metamask/networkConfig'].tokens[currency]
  198. const total = toBN(rootGetters['token/fromDecimals'](amount.toString()))
  199. const price = rootState.price.prices[currency]
  200. const relayerFee = getters.relayerFee
  201. return total
  202. .sub(relayerFee)
  203. .mul(toBN(price))
  204. .div(toBN(10 ** decimals))
  205. },
  206. selectedCurrency: (state, getters, rootState, rootGetters) => {
  207. const tokens = rootGetters['metamask/networkConfig'].tokens
  208. return tokens[state.selectedInstance.currency].symbol
  209. },
  210. selectedStatisticCurrency: (state, getters, rootState, rootGetters) => {
  211. const tokens = rootGetters['metamask/networkConfig'].tokens
  212. return tokens[state.selectedStatistic.currency].symbol
  213. },
  214. lastEventIndex: (state) => ({ currency, amount }) => {
  215. return state.statistic[currency][amount].anonymitySet
  216. },
  217. latestDeposits: (state) => {
  218. const { currency, amount } = state.selectedStatistic
  219. return state.statistic[currency][amount].latestDeposits
  220. },
  221. hasEnabledLightProxy: (state, getters, rootState, rootGetters) => {
  222. return Boolean(rootGetters['metamask/networkConfig']['tornado-proxy-light.contract.tornadocash.eth'])
  223. },
  224. isOptimismConnected: (state, getters, rootState, rootGetters) => {
  225. const netId = rootGetters['metamask/netId']
  226. return Number(netId) === 10
  227. },
  228. isArbitrumConnected: (state, getters, rootState, rootGetters) => {
  229. const netId = rootGetters['metamask/netId']
  230. return Number(netId) === 42161
  231. }
  232. }
  233. const actions = {
  234. setAndUpdateStatistic({ dispatch, commit }, { currency, amount }) {
  235. commit('SET_SELECTED_STATISTIC', { currency, amount })
  236. dispatch('updateSelectEvents')
  237. },
  238. async updateSelectEvents({ dispatch, commit, state, rootGetters, getters }) {
  239. const netId = rootGetters['metamask/netId']
  240. const { currency, amount } = state.selectedStatistic
  241. const eventService = getters.eventsInterface.getService({ netId, amount, currency })
  242. const graphEvents = await eventService.getEventsFromGraph({ methodName: 'getStatistic' })
  243. let statistic = graphEvents?.events
  244. if (!statistic || !statistic.length) {
  245. const fresh = await eventService.getStatisticsRpc({ eventsCount: 10 })
  246. statistic = fresh || []
  247. }
  248. const { nextDepositIndex, anonymitySet } = await dispatch('getLastDepositIndex', {
  249. currency,
  250. amount,
  251. netId
  252. })
  253. statistic = statistic.sort((a, b) => a.leafIndex - b.leafIndex)
  254. const latestDeposits = []
  255. for (const event of statistic.slice(-10)) {
  256. latestDeposits.unshift({
  257. index: event.leafIndex,
  258. depositTime: this.$moment.unix(event.timestamp).fromNow()
  259. })
  260. }
  261. commit('SAVE_LAST_EVENTS', {
  262. amount,
  263. currency,
  264. latestDeposits
  265. })
  266. commit('SAVE_LAST_INDEX', {
  267. amount,
  268. currency,
  269. anonymitySet,
  270. nextDepositIndex
  271. })
  272. },
  273. async updateEvents({ getters, rootGetters }, payload) {
  274. try {
  275. const eventService = getters.eventsInterface.getService(payload)
  276. const freshEvents = await eventService.updateEvents(payload.type)
  277. return freshEvents
  278. } catch (err) {
  279. throw new Error(`Method updateEvents has error: ${err.message}`)
  280. }
  281. },
  282. async updateCurrentEvents({ dispatch, rootGetters }, { amount, currency, lastEvent, type, netId }) {
  283. let lastBlock = lastEvent
  284. const nativeCurrency = rootGetters['metamask/nativeCurrency']
  285. const { deployedBlock } = networkConfig[`netId${netId}`]
  286. if (currency === nativeCurrency && !lastEvent) {
  287. lastBlock = await this.$indexedDB(netId).getFromIndex({
  288. indexName: 'name',
  289. storeName: 'lastEvents',
  290. key: `${type}s_${currency}_${amount}`
  291. })
  292. }
  293. const params = {
  294. type,
  295. netId,
  296. amount,
  297. currency,
  298. fromBlock: lastBlock ? lastBlock.blockNumber + 1 : deployedBlock
  299. }
  300. const events = await dispatch('updateEvents', params)
  301. return events
  302. },
  303. async getLastDepositIndex({ getters }, params) {
  304. try {
  305. const contractInstance = getters.instanceContract(params)
  306. const nextDepositIndex = await contractInstance.methods.nextIndex().call()
  307. return {
  308. nextDepositIndex,
  309. anonymitySet: toBN(nextDepositIndex)
  310. }
  311. } catch (err) {
  312. throw new Error(`Method getLastDepositIndex has error: ${err.message}`)
  313. }
  314. },
  315. async loadEncryptedEvents(_, { netId }) {
  316. try {
  317. const module = await download({
  318. contentType: 'string',
  319. name: `events/encrypted_notes_${netId}.json.zip`
  320. })
  321. if (module) {
  322. const events = JSON.parse(module)
  323. return {
  324. events,
  325. lastBlock: events[events.length - 1].blockNumber
  326. }
  327. }
  328. } catch (err) {
  329. throw new Error(`Method loadCachedEvents has error: ${err.message}`)
  330. }
  331. },
  332. prepareDeposit({ getters, dispatch, commit, rootGetters }, { prefix }) {
  333. try {
  334. const [, currency, amount, netId] = prefix.split('-')
  335. const contractInstance = getters.instanceContract({ currency, amount, netId })
  336. const secret = randomBN(31)
  337. const nullifier = randomBN(31)
  338. const preimage = Buffer.concat([nullifier.toBuffer('le', 31), secret.toBuffer('le', 31)])
  339. const commitment = buffPedersenHash(preimage)
  340. const commitmentHex = toFixedHex(commitment)
  341. const note = `0x${preimage.toString('hex')}`
  342. const isEnabled = rootGetters['encryptedNote/isEnabledSaveFile']
  343. if (isEnabled) {
  344. setTimeout(() => {
  345. try {
  346. dispatch('saveFile', { prefix, note })
  347. } catch (err) {
  348. console.warn('NoteAccount backup as a file is not supported on this device', err)
  349. }
  350. }, 1000)
  351. }
  352. commit('SAVE_DEPOSIT', { note, commitment: commitmentHex, prefix })
  353. if (!contractInstance._address) {
  354. throw new Error(this.app.i18n.t('networkIsNotSupported'))
  355. }
  356. } catch (e) {
  357. console.error('prepareDeposit', e)
  358. }
  359. },
  360. saveFile(_, { prefix, note }) {
  361. try {
  362. const data = new Blob([`${prefix}-${note}`], { type: 'text/plain;charset=utf-8' })
  363. saveAsFile(data, `backup-${prefix}-${note.slice(0, 10)}.txt`)
  364. } catch (err) {
  365. console.error('saveFile', err.message)
  366. }
  367. },
  368. async getEncryptedEventsFromDb(_, { netId }) {
  369. try {
  370. const idb = this.$indexedDB(netId)
  371. if (idb.isBlocked) {
  372. return []
  373. }
  374. const cachedEvents = await idb.getAll({ storeName: 'encrypted_events' })
  375. return cachedEvents
  376. } catch (err) {
  377. console.warn(`Method getEventsFromDb has error: ${err.message}`)
  378. }
  379. },
  380. async getEncryptedNotes({ rootState, rootGetters, dispatch, getters }) {
  381. try {
  382. const { netId } = rootState.metamask
  383. const rpc = rootGetters['settings/currentRpc']
  384. let { ENCRYPTED_NOTES_BLOCK: deployedBlock } = networkConfig[`netId${netId}`].constants
  385. const contractInstance = getters.tornadoProxyContract({ netId })
  386. let cachedEvents = await dispatch('getEncryptedEventsFromDb', { netId })
  387. const networksWithCache = {
  388. 1: cachedEventsLength.mainnet.ENCRYPTED_NOTES,
  389. 5: cachedEventsLength.goerli.ENCRYPTED_NOTES,
  390. 56: cachedEventsLength.bsc.ENCRYPTED_NOTES
  391. }
  392. const LENGTH_CACHE = networksWithCache[Number(netId)]
  393. if (
  394. ((isEmptyArray(cachedEvents) || !cachedEvents) && networksWithCache[Number(netId)]) ||
  395. cachedEvents.length < LENGTH_CACHE
  396. ) {
  397. ;({ events: cachedEvents } = await dispatch('loadEncryptedEvents', { netId }))
  398. }
  399. const hasCache = Boolean(cachedEvents && cachedEvents.length)
  400. if (hasCache) {
  401. const [lastEvent] = cachedEvents.sort((a, b) => a.blockNumber - b.blockNumber).slice(-1)
  402. deployedBlock = lastEvent.blockNumber + 1
  403. }
  404. const web3 = this.$provider.getWeb3(rpc.url)
  405. const currentBlockNumber = await web3.eth.getBlockNumber()
  406. let events = []
  407. const { events: graphEvents, lastSyncBlock } = await graph.getAllEncryptedNotes({
  408. netId,
  409. fromBlock: deployedBlock
  410. })
  411. if (lastSyncBlock) {
  412. deployedBlock = lastSyncBlock
  413. }
  414. const blockDifference = Math.ceil(currentBlockNumber - deployedBlock)
  415. const divisor = hasCache ? 2 : 10
  416. let blockRange = blockDifference > divisor ? blockDifference / divisor : blockDifference
  417. if (Number(netId) === 56) {
  418. blockRange = 4950
  419. }
  420. let numberParts = blockDifference === 0 ? 1 : Math.ceil(blockDifference / blockRange)
  421. const part = Math.ceil(blockDifference / numberParts)
  422. let fromBlock = deployedBlock
  423. let toBlock = deployedBlock + part
  424. if (toBlock >= currentBlockNumber || toBlock === deployedBlock) {
  425. toBlock = 'latest'
  426. numberParts = 1
  427. }
  428. for (let i = 0; i < numberParts; i++) {
  429. const partOfEvents = await contractInstance.getPastEvents('EncryptedNote', {
  430. toBlock,
  431. fromBlock
  432. })
  433. if (partOfEvents) {
  434. events = events.concat(partOfEvents)
  435. }
  436. fromBlock = toBlock
  437. toBlock += part
  438. }
  439. if (events && events.length) {
  440. events = events
  441. .filter((i) => i.returnValues.encryptedNote)
  442. .map((e) => ({
  443. txHash: e.transactionHash,
  444. transactionHash: e.transactionHash,
  445. blockNumber: Number(e.blockNumber),
  446. encryptedNote: e.returnValues.encryptedNote
  447. }))
  448. }
  449. const allEvents = [].concat(cachedEvents, graphEvents, events)
  450. await dispatch('saveEncryptedEventsToDB', { events: allEvents, netId })
  451. return allEvents
  452. } catch (err) {
  453. console.log('getEncryptedNotes', err)
  454. }
  455. },
  456. async saveEncryptedEventsToDB(_, { events, netId }) {
  457. const idb = this.$indexedDB(netId)
  458. if (!events || !events.length || idb.isBlocked) {
  459. return
  460. }
  461. await idb.createMultipleTransactions({
  462. data: events,
  463. storeName: `encrypted_events`
  464. })
  465. },
  466. async sendDeposit({ state, rootState, getters, rootGetters, dispatch, commit }, { isEncrypted }) {
  467. try {
  468. const { commitment, note, prefix } = state
  469. // eslint-disable-next-line prefer-const
  470. let [, currency, amount, netId] = prefix.split('-')
  471. const config = networkConfig[`netId${netId}`]
  472. const contractInstance = getters.tornadoProxyContract({ netId })
  473. if (!state.commitment) {
  474. throw new Error(this.app.i18n.t('failToGenerateNote'))
  475. }
  476. const { nextDepositIndex: index } = await dispatch('getLastDepositIndex', { netId, currency, amount })
  477. const { ethAccount } = rootState.metamask
  478. const nativeCurrency = rootGetters['metamask/nativeCurrency']
  479. const isNative = currency === nativeCurrency
  480. const value = isNative ? toWei(amount, 'ether') : '0'
  481. const instance = config.tokens[currency].instanceAddress[amount]
  482. let params = [instance, commitment, []]
  483. if (isEncrypted) {
  484. const encryptedNote = await dispatch(
  485. 'encryptedNote/getEncryptedNote',
  486. { data: `${instance}-${note}` },
  487. { root: true }
  488. )
  489. params = [instance, commitment, encryptedNote]
  490. }
  491. const data = contractInstance.methods.deposit(...params).encodeABI()
  492. const gas = await contractInstance.methods.deposit(...params).estimateGas({ from: ethAccount, value })
  493. const callParams = {
  494. method: 'eth_sendTransaction',
  495. params: {
  496. to: contractInstance._address,
  497. gas: numberToHex(gas + 50000),
  498. value: numberToHex(value),
  499. data
  500. },
  501. watcherParams: {
  502. title: { path: 'depositing', amount, currency },
  503. successTitle: {
  504. path: 'depositedValue',
  505. amount,
  506. currency
  507. },
  508. storeType: isEncrypted ? 'encryptedTxs' : 'txs'
  509. },
  510. isAwait: false
  511. }
  512. const txHash = await dispatch('metamask/sendTransaction', callParams, { root: true })
  513. // there may be a race condition, you need to request an index and a timestamp of the deposit after tx is mined
  514. const timestamp = Math.round(new Date().getTime() / 1000)
  515. const { nullifierHex, commitmentHex } = parseHexNote(state.note)
  516. const storeType = isEncrypted ? 'encryptedTxs' : 'txs'
  517. const accounts = rootGetters['encryptedNote/accounts']
  518. const tx = {
  519. txHash,
  520. type: 'Deposit',
  521. note,
  522. amount,
  523. storeType,
  524. prefix,
  525. netId,
  526. timestamp,
  527. index,
  528. nullifierHex,
  529. commitmentHex,
  530. currency
  531. }
  532. console.log('tx', tx)
  533. if (isEncrypted) {
  534. tx.note = params[2]
  535. tx.owner = isAddress(accounts.encrypt) ? accounts.encrypt : ''
  536. tx.backupAccount = isAddress(accounts.backup) ? accounts.backup : ''
  537. }
  538. commit('txHashKeeper/SAVE_TX_HASH', tx, { root: true })
  539. } catch (e) {
  540. console.error('sendDeposit', e)
  541. return false
  542. }
  543. },
  544. async checkSpentEventFromNullifier({ getters, dispatch }, parsedNote) {
  545. try {
  546. const isSpent = await dispatch('loadEvent', {
  547. note: parsedNote,
  548. eventName: 'nullifierHash',
  549. type: eventsType.WITHDRAWAL,
  550. methodName: 'getAllWithdrawals',
  551. eventToFind: parsedNote.nullifierHex
  552. })
  553. return Boolean(isSpent)
  554. } catch (err) {
  555. console.error(`Method checkSpentEventFromNullifier has error: ${err.message}`)
  556. }
  557. },
  558. async checkRoot({ getters }, { root, parsedNote }) {
  559. const contractInstance = getters.instanceContract(parsedNote)
  560. const isKnownRoot = await contractInstance.methods.isKnownRoot(root).call()
  561. if (!isKnownRoot) {
  562. throw new Error(this.app.i18n.t('invalidRoot'))
  563. }
  564. },
  565. async buildTree({ dispatch }, { currency, amount, netId, commitmentHex }) {
  566. const treeInstanceName = `${currency}_${amount}`
  567. const params = { netId, amount, currency }
  568. const treeService = treesInterface.getService({
  569. ...params,
  570. commitment: commitmentHex,
  571. instanceName: treeInstanceName
  572. })
  573. const [cachedTree, eventsData] = await Promise.all([
  574. treeService.getTree(),
  575. dispatch('updateEvents', { ...params, type: eventsType.DEPOSIT })
  576. ])
  577. const commitments = eventsData.events.map((el) => el.commitment.toString(10))
  578. let tree = cachedTree
  579. if (tree) {
  580. const newLeaves = commitments.slice(tree.elements.length)
  581. tree.bulkInsert(newLeaves)
  582. } else {
  583. console.log('events', eventsData)
  584. checkCommitments(eventsData.events)
  585. tree = treeService.createTree({ events: commitments })
  586. }
  587. const root = toFixedHex(tree.root)
  588. await dispatch('checkRoot', { root, parsedNote: params })
  589. await treeService.saveTree({ tree })
  590. return { tree, root }
  591. },
  592. async createSnarkProof(
  593. { rootGetters, rootState, state, getters },
  594. { root, note, tree, recipient, leafIndex }
  595. ) {
  596. const { pathElements, pathIndices } = tree.path(leafIndex)
  597. console.log('pathElements, pathIndices', pathElements, pathIndices)
  598. const nativeCurrency = rootGetters['metamask/nativeCurrency']
  599. const withdrawType = state.withdrawType
  600. let relayer = BigInt(recipient)
  601. let fee = BigInt(0)
  602. let refund = BigInt(0)
  603. if (withdrawType === 'relayer') {
  604. let totalRelayerFee = getters.relayerFee
  605. relayer = BigInt(rootState.relayer.selectedRelayer.address)
  606. if (note.currency !== nativeCurrency) {
  607. refund = BigInt(state.ethToReceive.toString())
  608. totalRelayerFee = totalRelayerFee.add(getters.ethToReceiveInToken)
  609. }
  610. fee = BigInt(totalRelayerFee.toString())
  611. }
  612. const input = {
  613. // public
  614. fee,
  615. root,
  616. refund,
  617. relayer,
  618. recipient: BigInt(recipient),
  619. nullifierHash: note.nullifierHash,
  620. // private
  621. pathIndices,
  622. pathElements,
  623. secret: note.secret,
  624. nullifier: note.nullifier
  625. }
  626. const { circuit, provingKey } = await getTornadoKeys()
  627. if (!groth16) {
  628. groth16 = await buildGroth16()
  629. }
  630. console.log('Start generating SNARK proof', input)
  631. console.time('SNARK proof time')
  632. const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, provingKey)
  633. const { proof } = websnarkUtils.toSolidityInput(proofData)
  634. const args = [
  635. toFixedHex(input.root),
  636. toFixedHex(input.nullifierHash),
  637. toFixedHex(input.recipient, 20),
  638. toFixedHex(input.relayer, 20),
  639. toFixedHex(input.fee),
  640. toFixedHex(input.refund)
  641. ]
  642. return { args, proof }
  643. },
  644. async prepareWithdraw({ dispatch, getters, commit }, { note, recipient }) {
  645. commit('REMOVE_PROOF', { note })
  646. try {
  647. const parsedNote = parseNote(note)
  648. const { tree, root } = await dispatch('buildTree', parsedNote)
  649. const isSpent = await dispatch('checkSpentEventFromNullifier', parsedNote)
  650. if (isSpent) {
  651. throw new Error(this.app.i18n.t('noteHasBeenSpent'))
  652. }
  653. const { proof, args } = await dispatch('createSnarkProof', {
  654. root,
  655. tree,
  656. recipient,
  657. note: parsedNote,
  658. leafIndex: tree.indexOf(parsedNote.commitmentHex)
  659. })
  660. console.timeEnd('SNARK proof time')
  661. commit('SAVE_PROOF', { proof, args, note })
  662. } catch (e) {
  663. console.error('prepareWithdraw', e)
  664. throw new Error(e.message)
  665. }
  666. },
  667. async withdraw({ state, rootState, dispatch, getters }, { note }) {
  668. try {
  669. const [, currency, amount, netId] = note.split('-')
  670. const config = networkConfig[`netId${netId}`]
  671. const { proof, args } = state.notes[note]
  672. const { ethAccount } = rootState.metamask
  673. const contractInstance = getters.tornadoProxyContract({ netId })
  674. const instance = config.tokens[currency].instanceAddress[amount]
  675. const params = [instance, proof, ...args]
  676. const data = contractInstance.methods.withdraw(...params).encodeABI()
  677. const gas = await contractInstance.methods
  678. .withdraw(...params)
  679. .estimateGas({ from: ethAccount, value: args[5] })
  680. const callParams = {
  681. method: 'eth_sendTransaction',
  682. params: {
  683. data,
  684. value: args[5],
  685. to: contractInstance._address,
  686. gas: numberToHex(gas + 200000)
  687. },
  688. watcherParams: {
  689. title: { path: 'withdrawing', amount, currency },
  690. successTitle: {
  691. amount,
  692. currency,
  693. path: 'withdrawnValue'
  694. },
  695. onSuccess: (txHash) => {
  696. dispatch('txHashKeeper/updateDeposit', { amount, currency, netId, note, txHash }, { root: true })
  697. }
  698. },
  699. isAwait: false,
  700. isSaving: false
  701. }
  702. await dispatch('metamask/sendTransaction', callParams, { root: true })
  703. } catch (e) {
  704. console.error(e)
  705. throw new Error(e.message)
  706. }
  707. },
  708. loadAllNotesData({ dispatch, rootGetters }) {
  709. const { tokens } = rootGetters['metamask/networkConfig']
  710. for (const [currency, { instanceAddress }] of Object.entries(tokens)) {
  711. for (const amount in instanceAddress) {
  712. if (instanceAddress[amount]) {
  713. dispatch('updateLastIndex', { currency, amount })
  714. }
  715. }
  716. }
  717. },
  718. async updateLastIndex({ dispatch, commit, rootState }, { currency, amount }) {
  719. const netId = rootState.metamask.netId
  720. const { nextDepositIndex, anonymitySet } = await dispatch('getLastDepositIndex', {
  721. currency,
  722. netId,
  723. amount
  724. })
  725. commit('SAVE_LAST_INDEX', {
  726. amount,
  727. currency,
  728. anonymitySet,
  729. nextDepositIndex
  730. })
  731. },
  732. async loadEvent({ getters, rootGetters }, { note, type, eventName, eventToFind }) {
  733. try {
  734. const eventService = getters.eventsInterface.getService(note)
  735. const foundEvent = await eventService.findEvent({ eventName, eventToFind, type })
  736. return foundEvent
  737. } catch (err) {
  738. console.error(`Method loadEvent has error: ${err.message}`)
  739. }
  740. },
  741. async loadDepositEvent({ state, dispatch }, { withdrawNote }) {
  742. try {
  743. const note = parseNote(withdrawNote)
  744. const lastEvent = await dispatch('loadEvent', {
  745. note,
  746. eventName: 'commitment',
  747. type: eventsType.DEPOSIT,
  748. methodName: 'getAllDeposits',
  749. eventToFind: note.commitmentHex
  750. })
  751. if (lastEvent) {
  752. const { nextDepositIndex } = state.statistic[note.currency][note.amount]
  753. const depositsPast = nextDepositIndex - lastEvent.leafIndex - 1
  754. const isSpent = await dispatch('checkSpentEventFromNullifier', note)
  755. return {
  756. isSpent,
  757. depositsPast,
  758. timestamp: lastEvent.timestamp,
  759. leafIndex: lastEvent.leafIndex,
  760. txHash: lastEvent.transactionHash,
  761. depositBlock: lastEvent.blockNumber
  762. }
  763. }
  764. } catch (err) {
  765. console.error(`Method loadDepositEvent has error: ${err.message}`)
  766. }
  767. },
  768. async loadWithdrawalEvent({ dispatch }, { withdrawNote }) {
  769. try {
  770. const note = parseNote(withdrawNote)
  771. const lastEvent = await dispatch('loadEvent', {
  772. note,
  773. eventName: 'nullifierHash',
  774. type: eventsType.WITHDRAWAL,
  775. methodName: 'getAllWithdrawals',
  776. eventToFind: note.nullifierHex
  777. })
  778. if (lastEvent) {
  779. return {
  780. to: lastEvent.to,
  781. fee: lastEvent.fee,
  782. txHash: lastEvent.transactionHash,
  783. blockNumber: lastEvent.blockNumber
  784. }
  785. }
  786. } catch (err) {
  787. console.error(`Method loadWithdrawalEvent has error: ${err.message}`)
  788. }
  789. },
  790. async loadWithdrawalData({ commit, dispatch, rootGetters }, { withdrawNote }) {
  791. try {
  792. const toDecimals = rootGetters['token/toDecimals']
  793. const { currency, amount } = parseNote(withdrawNote)
  794. const { fee, txHash, blockNumber, to } = await dispatch('loadWithdrawalEvent', { withdrawNote })
  795. const decimals = rootGetters['metamask/networkConfig'].tokens[currency].decimals
  796. const withdrawalAmount = toBN(rootGetters['token/fromDecimals'](amount.toString(), decimals)).sub(
  797. toBN(fee)
  798. )
  799. return {
  800. to,
  801. txHash,
  802. withdrawalBlock: blockNumber,
  803. fee: toDecimals(fee, decimals, 4),
  804. amount: toDecimals(withdrawalAmount, decimals, 4)
  805. }
  806. } catch (e) {
  807. console.error(`Method loadWithdrawalData has error: ${e}`)
  808. }
  809. },
  810. calculateEthToReceive({ commit, state, rootGetters }, { currency }) {
  811. const gasLimit = rootGetters['metamask/networkConfig'].tokens[currency].gasLimit
  812. const gasPrice = toBN(rootGetters['gasPrices/gasPrice'])
  813. const ethToReceive = gasPrice
  814. .mul(toBN(gasLimit))
  815. .mul(toBN(2))
  816. .toString()
  817. return ethToReceive
  818. },
  819. async setDefaultEthToReceive({ dispatch, commit }, { currency }) {
  820. const ethToReceive = await dispatch('calculateEthToReceive', { currency })
  821. commit('SAVE_ETH_TO_RECEIVE', { ethToReceive })
  822. commit('SAVE_DEFAULT_ETH_TO_RECEIVE', { ethToReceive })
  823. },
  824. setNativeCurrency({ commit }, { netId }) {
  825. const currency = networkConfig[`netId${netId}`].nativeCurrency
  826. const amounts = Object.keys(networkConfig[`netId${netId}`].tokens[currency].instanceAddress)
  827. const amount = Math.min(...amounts)
  828. commit('SET_SELECTED_INSTANCE', { currency, amount })
  829. commit('SET_SELECTED_STATISTIC', { currency, amount })
  830. },
  831. async aggregateMulticall({ rootGetters, getters }, { params }) {
  832. try {
  833. const netId = rootGetters['metamask/netId']
  834. const multicallContract = getters.multicallContract({ netId })
  835. const result = await multicallContract.methods.aggregate(params).call()
  836. return result.returnData
  837. } catch (err) {
  838. console.log('err', err.message)
  839. }
  840. }
  841. }
  842. export default {
  843. namespaced: true,
  844. state,
  845. getters,
  846. mutations,
  847. actions
  848. }