import { exec, execSync } from "child_process"; import { newBarretenbergApiSync } from "@aztec/bb.js/dest/node/index.js"; import { Fr } from "@aztec/bb.js/dest/node/types/index.js"; import inquirer from "inquirer"; import { generateUTXO, generateDataToml, generateTestTransaction, generateTreeProof, generateTestPublish, randomBytesFr, readToml, getSolidityHash, treeConfig, getInputs, getInputsOrdered, getPublicInputsOrdered, } from "./utils/helpers.js"; import { MerkleTree } from "./utils/MerkleTree.mjs"; import fs from "fs"; import { ethers, JsonRpcProvider, ContractFactory } from "ethers"; import PrivateTransferData from "../deps/TechnicalPreview.json" assert { type: "json" }; import { Sequelize, DataTypes, Model } from "sequelize"; import "dotenv/config"; class UTXOdb extends Model { secret; amount; assetType; spent; } let sequelize = new Sequelize({ dialect: "sqlite", storage: "txs.db", logging: false, }); UTXOdb.init( { secret: { type: DataTypes.STRING, allowNull: false, }, amount: { type: DataTypes.STRING, allowNull: false, }, assetType: { type: DataTypes.INTEGER, }, spent: { type: DataTypes.BOOLEAN, }, id: { type: DataTypes.STRING, allowNull: false, primaryKey: true, }, }, { sequelize: sequelize, tableName: "UTXOs", } ); await sequelize.sync(); const fetchUTXOs = async () => await UTXOdb.findAll({ where: { spent: false } }); const fetchSpentUTXOs = async () => await UTXOdb.findAll({ where: { spent: true } }); const spendUTXO = async (UTXO) => await UTXOdb.update({ spent: true }, { where: { id: UTXO } }); const saveUTXO = async (UTXO) => { await UTXOdb.upsert({ secret: UTXO.secret.toString(), amount: UTXO.amount.toString(), assetType: 0, id: UTXO.id.toString(), spent: false, }); }; const provider = new JsonRpcProvider(process.env.PROVIDER); const privateKey = process.env.PRIVATE_KEY; const wallet = new ethers.Wallet(privateKey, provider); const technicalPreviewContract = new ethers.Contract( "0x8162d56A21D0ee799Eef055C3acC2b4F776f693a", PrivateTransferData.abi, wallet ); export async function balance() { const api = await newBarretenbergApiSync(); let utxos = await fetchUTXOs(); let nullifierArray = []; for (let i = 0; i < utxos.length; i++) { nullifierArray.push(api.pedersenPlookupCompress([ Fr.fromString(utxos[i].dataValues.secret), Fr.fromString(utxos[i].dataValues.secret) ]).toString()); } let spentArray = await technicalPreviewContract.getSpentNullifiers(nullifierArray); for (let i = 0; i < spentArray.length; i++) { if (spentArray[i] === true) { await spendUTXO(utxos[i].dataValues.id); utxos.splice(i, 1); } } if (utxos.length === 0) return ethers.formatEther(BigInt(0)); else { let utxosBigInt = utxos.map((res) => BigInt(res.dataValues.amount)); return ethers.formatEther(utxosBigInt.reduce((a, b) => a + b)); } } export const deposit = async (args) => { console.log(`** Compiling Transaction Circuit **`); execSync(`nargo check`); execSync(`nargo compile`); let recipient, recipient_new; const api = await newBarretenbergApiSync(); let trees = { utxo_tree: new MerkleTree(4, api), tx_tree: new MerkleTree(4, api), historic_tree: new MerkleTree(9, api), utxoTreeOld: new MerkleTree(4, api), txTreeOld: new MerkleTree(4, api), newHistoricRoot: "", }; let utxoIn = []; let utxoOut = []; let treeProof = []; let validRoots = await technicalPreviewContract.getValidRoots(); let oldRoot, newRoot; if (validRoots.length === 1) { oldRoot = validRoots[0]; newRoot = validRoots[0]; } else { oldRoot = validRoots[validRoots.length - 2]; newRoot = validRoots[validRoots.length - 1]; } let data = generateDataToml(oldRoot, newRoot, trees, api); let amountPublic = { amountIn: BigInt(0), amountOut: BigInt(0), }; recipient = `0x` + `dEaD`.padStart(64, "0"); console.log("** Populating UTXO tree.. **"); console.log("** Generating a batch of zkXFT UTXOs... **"); let batchSize0 = 1; let secret0 = []; for (let s = 0; s < batchSize0; s++) { secret0.push(randomBytesFr(32)); } let amountsOutUTXO = new Array(batchSize0).fill(BigInt(0)); amountsOutUTXO[0] = BigInt(args.amount); utxoOut = generateUTXO(batchSize0, amountsOutUTXO, secret0, api); amountPublic.amountIn = BigInt(args.amount); generateTestTransaction( utxoIn, utxoOut, trees, treeProof, amountPublic, data, recipient, api, technicalPreviewContract ); console.log("** Creating Deposit and Publisher Proof... **"); generateTestPublish(trees, data, api); execSync(`nargo prove`); let proof = fs.readFileSync("./proofs/main.proof").toString(); proof = `0x` + proof; let public_inputs = readToml("./Verifier.toml"); let tx = await technicalPreviewContract.publish(proof, public_inputs, { value: 0, }); utxoIn = utxoOut; let txReceipt = await provider.getTransactionReceipt(tx.hash); while (txReceipt === null) { txReceipt = await provider.getTransactionReceipt(tx.hash); } let utxoId = api.pedersenPlookupCompress([ utxoIn[0].owner, utxoIn[0].amount, utxoIn[0].assetType, ]); utxoOut[0].id = utxoId; await saveUTXO(utxoOut[0]); return [trees, data]; }; export const transact = async (args) => { const api = await newBarretenbergApiSync(); let amount = BigInt(args.amount); let trees = { utxo_tree: new MerkleTree(4, api), tx_tree: new MerkleTree(4, api), historic_tree: new MerkleTree(9, api), utxoTreeOld: new MerkleTree(4, api), txTreeOld: new MerkleTree(4, api), newHistoricRoot: "", }; console.log("** Populating Historic tree.. **"); let validRoots = await technicalPreviewContract.getValidRoots(); for (let i = 0; i < validRoots.length; i++) { trees.historic_tree.insert(validRoots[i]); } let spending = []; let spendingSum = BigInt(0); let utxo_leaves = (await fetchUTXOs()).map((utxo) => { let utxo_out = { secret: Fr.fromString(utxo.dataValues.secret), owner: api.pedersenPlookupCompress([ Fr.fromString(utxo.dataValues.secret), ]), amount: Fr.fromString(utxo.dataValues.amount), assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), "hex")), spent: utxo.dataValues.spent, id: utxo.dataValues.id, }; return utxo_out; }); let utxoOut = []; let changeUTXO = { secret: Fr.fromString("0x00"), owner: api.pedersenPlookupCompress([Fr.fromString("0x00")]), amount: Fr.fromString("0x00"), assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), "hex")), }; for (let i = 0; i < utxo_leaves.length; i++) { if (spendingSum < amount) { spending.push(utxo_leaves[i]); spendingSum += BigInt(utxo_leaves[i].amount.toString()); } if (spendingSum > amount) { let change = spendingSum - amount; let secret = randomBytesFr(32); changeUTXO = { secret: secret, owner: api.pedersenPlookupCompress([secret]), amount: new Fr(change), assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), "hex")), }; changeUTXO.id = api.pedersenPlookupCompress([ changeUTXO.owner, changeUTXO.amount, changeUTXO.assetType, ]); utxoOut.push(changeUTXO); break; } } let treeProofs = []; for (let i = 0; i < spending.length; i++) { let proofForUtxo = await generateTreeProof( spending[i], api, technicalPreviewContract ); treeProofs.push(proofForUtxo); } let batchSize1 = 1; let oldRoot, newRoot; if (validRoots.length === 1) { oldRoot = validRoots[0]; newRoot = validRoots[0]; } else { oldRoot = validRoots[validRoots.length - 2]; newRoot = validRoots[validRoots.length - 1]; } let data = generateDataToml(oldRoot, newRoot, trees, api); let amountPublic = { amountIn: BigInt(0), amountOut: BigInt(args.amount), }; let recipientPrompt = await inquirer.prompt({ type: "input", name: "recipient", message: "Enter the destination address: ", }); let recipient_new = `0x` + `${recipientPrompt.recipient.replace("0x", "")}`.padStart(64, "0"); await generateTestTransaction( spending, utxoOut, trees, treeProofs, amountPublic, data, recipient_new, api, technicalPreviewContract ); generateTestPublish(trees, data, api); console.log(`** Compiling Transaction Circuit **`); execSync(`nargo check`); execSync(`nargo compile`); console.log("** Generating transaction Parameters... **"); execSync(`nargo prove`); let proof = fs.readFileSync("./proofs/main.proof").toString(); proof = `0x` + proof; let public_inputs = readToml("./Verifier.toml"); let tx = await technicalPreviewContract.publish(proof, public_inputs, { value: 0, }); let txReceipt = await provider.getTransactionReceipt(tx.hash); while (txReceipt === null) { console.log("Waiting for tx confirmation...") txReceipt = await provider.getTransactionReceipt(tx.hash); } for (let i = 0; i < spending.length; i++) { spendUTXO(spending[i].id); } await saveUTXO(changeUTXO); };