const inquirer = require('inquirer'); const { toBN, toWei } = require('web3-utils') global.serviceController = { error: "", deposit: async (args) => { if (args) { if (!args.shifter) { console.log("No shifter specified"); return; } let { shifter } = args; let shifterInstance = cryptoController.shifters[shifter]; let password = helpers.randomHex(32); depositObject = await serviceController.prepareDeposit(shifter, cryptoController.sender, password); await shifterInstance.methods.deposit(helpers.toFixedHex(depositObject.commitment), depositObject.encryptedNote, depositObject.passwordHash) .send({ from: cryptoController.sender, gasLimit: config.gasLimit, value: await shifterInstance.methods.ethDenomination().call() }) await sqlite.saveNote({ note: depositObject.saveNote, shifter: shifter }); console.log(`Deposit successful.`); await cryptoController.refBalance(shifter); } else { let args = {}; let allShifters = cryptoController.shifterList; let shifterChoices = []; shifterChoices.push(new inquirer.Separator(" ")); for (let i = 0; i < allShifters.length; i++) { let shifterInstance = cryptoController.shifters[allShifters[i]]; let shifterToken = cryptoController.shifterTokens[allShifters[i]]; let tokenName = await cryptoController.tokens[shifterToken].methods.name().call(); let tokenDenomination = await shifterInstance.methods.denomination().call(); shifterChoices.push( { value: allShifters[i], name: `${tokenName} - ${helpers.fromWei(tokenDenomination)} (${allShifters[i]})`, }); } shifterChoices.push(new inquirer.Separator(" ")); shifterChoices.push({ value: "exit", name: "Exit", }); shifterChoices.push(new inquirer.Separator(" ")); let answers = await inquirer.prompt({ type: 'list', name: 'shifter', message: 'Select a shifter:', choices: shifterChoices }) if (answers.shifter == "exit") return; args = { shifter: answers.shifter } await serviceController.deposit(args); } }, simpleShift: async (args) => { if (args) { if (!args.shifter) { console.log("No shifter specified"); return; } if (!args.amount) { console.log("No amount specified"); return; } let shifterInstance = cryptoController.shifters[args.shifter]; let { amount, shifter } = args; let shifterToken = await cryptoController.shifters[shifter].methods.token().call(); let tokenInstance = cryptoController.tokens[shifterToken]; const tokenBalance = await tokenInstance.methods.balanceOf(cryptoController.sender).call(); if (toBN(tokenBalance) < (toBN(amount))) { errorMsg = "Insufficient balance"; serviceController.error = errorMsg; console.log(errorMsg); return; } console.log(`Shifting ${amount} ${await tokenInstance.methods.symbol().call()} to ${cryptoController.sender}.`) await shifterInstance.methods.simpleShift(amount, cryptoController.sender).send({ from: cryptoController.sender, gasLimit: config.gasLimit }) await cryptoController.refBalance(args.shifter); } else { let args = {}; let allTokens = Object.keys(cryptoController.tokens); let tokenShifters = {}; for (let i = 0; i < allTokens.length; i++) { for (let j = 0; j < cryptoController.shifterList.length; j++) { let thisShifter = cryptoController.shifters[cryptoController.shifterList[j]]; let thisToken = await thisShifter.methods.token().call(); if (thisToken === allTokens[i]) { tokenShifters[allTokens[i]] = { shifter: cryptoController.shifterList[j], name: await cryptoController.tokens[allTokens[i]].methods.name().call(), } break; } } } let shifterChoices = []; for (let i = 0; i < Object.keys(tokenShifters).length; i++) { shifterChoices.push({ value: tokenShifters[Object.keys(tokenShifters)[i]].shifter, name: tokenShifters[Object.keys(tokenShifters)[i]].name, }) } shifterChoices.push({ value: "Exit", name: "Exit" }); let answers = await inquirer.prompt({ type: 'list', name: 'shifter', message: 'Select a token:', pageSize: shifterChoices.length, choices: shifterChoices }) if (answers.shifter === "Exit") return; let amountPrompt = await inquirer.prompt({ type: 'input', name: 'amount', message: 'Enter amount to shift:', }) if (!isNaN(amountPrompt.amount)) { let errorMsg = "No amount specified"; serviceController.error = errorMsg; console.log(errorMsg); return; } args = { shifter: answers.shifter, amount: toWei(amountPrompt.amount) } await serviceController.simpleShift(args); } }, withdraw: async (args) => { if (args) { if (!args.shifter) { let errorMsg = "No shifter specified"; serviceController.error = errorMsg; console.log(errorMsg); return; } if (!args.note) { let errorMsg = "No note specified"; serviceController.error = errorMsg; console.log(errorMsg); return; } if (!args.address) { let errorMsg = "No address specified"; serviceController.error = errorMsg; console.log(errorMsg); return; } let { note, address, shifter } = args; let shifterInstance = cryptoController.shifters[shifter]; const { proof, withdrawArgs } = await cryptoController.generateProof(shifterInstance, address, note); console.log("Withdrawing aUSD.") let relayerEndpoint = config.relayUrl + "/relay"; const transaction = await fetch(relayerEndpoint, { method: "POST", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ proof: proof, shifter: shifter, args: withdrawArgs }) }) depositObject = null; await cryptoController.refBalance(shifter); } else { let args = {}; let allNotes = await sqlite.fetchNotes(); let noteChoices = []; for (let i = 0; i < allNotes.length; i++) { let shifterInstance = cryptoController.shifters[allNotes[i].shifter]; let shifterToken = cryptoController.shifterTokens[allNotes[i].shifter]; let tokenName = await cryptoController.tokens[shifterToken].methods.name().call(); let tokenDenomination = await shifterInstance.methods.denomination().call(); let parsed = cryptoController.parseSaveNote(allNotes[i].note); isSpent = await shifterInstance.methods.isSpent(parsed.nullifierHex).call(); if (isSpent) { await sqlite.spentNote(allNotes[i].note); continue; } let parsedNote = cryptoController.parseSaveNote(allNotes[i].note); let noteHex = parsedNote.commitmentHex.slice(0, 6) + "..." + parsedNote.commitmentHex.slice(-6); allNotes[i].hex = noteHex; allNotes[i].display = `${noteHex} (${tokenName} ${helpers.fromWei(tokenDenomination)})` noteChoices.push(allNotes[i]); } noteChoices = noteChoices.map((note) => { return { name: note.display, value: note } }) noteChoices.push(new inquirer.Separator(" ")); noteChoices.push({ value: "exit", name: "Exit", }); noteChoices.push(new inquirer.Separator(" ")); let answers = await inquirer.prompt({ type: 'list', name: 'note', message: 'Select a note:', choices: noteChoices }) if (answers.note == "exit") return; let withdrawalAddress = await inquirer.prompt({ type: 'input', name: 'address', message: 'Enter address to withdraw to:', }) if (!helpers.isAddress(withdrawalAddress.address)) { let errorMsg = "Invalid address specified"; serviceController.error = errorMsg; console.log(errorMsg); return; } args = { note: answers.note.note, shifter: answers.note.shifter, address: withdrawalAddress.address } await serviceController.withdraw(args); } }, viewBalances: async () => { let allTokens = Object.keys(cryptoController.tokens); let balances = []; await cryptoController.refBalance(); for (let i = 0; i < allTokens.length; i++) { let token = allTokens[i]; let tokenName = await cryptoController.tokens[token].methods.name().call(); let tokenBalance = await cryptoController.tokens[token].methods.balanceOf(cryptoController.sender).call(); balances.push({ "name": `${tokenName} - ${helpers.fromWei(tokenBalance)}`, "value": tokenBalance }); }; balances.push({ value: "exit", name: "Exit", }); await inquirer.prompt({ type: 'list', name: 'balance', message: 'Token Balances:', choices: balances }) return; }, prepareDeposit: async (shifter, address, password) => { depositObject = { secret: helpers.rbigint(31), nullifier: helpers.rbigint(31) } const saveNote = `0x${depositObject.nullifier.toString(16, 'hex').padStart(62, '0')}${depositObject.secret.toString(16, 'hex').padStart(62, '0')}`; const preimage = Buffer.concat([depositObject.nullifier.leInt2Buff(31), depositObject.secret.leInt2Buff(31)]) depositObject.commitment = helpers.pedersenHash(preimage); depositObject.buffNullifierHash = helpers.pedersenHash(depositObject.nullifier.leInt2Buff(31)); depositObject.saveNote = saveNote depositObject.encryptedNote = await cryptoController.xcrypt(saveNote, password, shifter, address, null); depositObject.passwordHash = helpers.keccak256(password); return depositObject }, getAggregateDeposits: async (shifters, address, decrypted) => { const password = cryptoController.getOrSetPasswordLocally(); let encryptedNotes = []; let decryptedNotes = []; let spentNotes = []; for (let _shifter in shifters) { shifter = shifters[_shifter]; if (password) { let passwordHash = helpers.keccak256(password) let shifterNotes = await xftAnon.methods.getDeposits(shifter, address, passwordHash); shifterNotes = shifterNotes.map((note) => { return { "note": note, "shifter": shifter } }); try { encryptedNotes = encryptedNotes.concat(shifterNotes); } catch (e) { console.log(e) }; } else { throw ("No password set"); } } let nullHashArray; let boolSpentArray = []; let decryptedUnspentNotes; if (decrypted) { for (let idx in encryptedNotes) { let thisNote = encryptedNotes[idx]; let thisDecryptedNote = (await cryptoController.xcrypt(thisNote.note, password)); let deposited = await serviceController._getShifterContract(thisNote.shifter).methods.commitments(parseSaveNote(thisDecryptedNote).commitmentHex).call() let spent = await serviceController._getShifterContract(thisNote.shifter).methods.isSpent(parseSaveNote(thisDecryptedNote).nullifierHex).call(); if (!spent && deposited) decryptedNotes.push({ "note": thisDecryptedNote, "shifter": thisNote.shifter }) else if (spent && deposited) spentNotes.push({ "note": thisDecryptedNote, "shifter": thisNote.shifter }) } decryptedNotes = decryptedNotes .map((note, idx) => { return { "note": note.note, "shifter": note.shifter, "commitment": parseSaveNote(note.note).commitmentHex } }); spentNotes = spentNotes .map((note, idx) => { return { "note": note.note, "shifter": note.shifter, "commitment": parseSaveNote(note.note).commitmentHex } }); } return (decrypted ? { decryptedNotes, spentNotes } : encryptedNotes); } }