From 6e603b41d7d886de8c4a6cee357d59ff482a5fba Mon Sep 17 00:00:00 2001 From: Tim Liner Date: Fri, 23 Aug 2024 15:50:41 -0600 Subject: [PATCH] create scripts for interacting with Tokenized Ballots --- scripts/CastVote.ts | 86 ++++++++++++++++++++++++++++++ scripts/DelegateVotes.ts | 73 +++++++++++++++++++++++++ scripts/DeployMyToken.ts | 53 +++++++++++++++++++ scripts/DeployTokenizedBallot.ts | 91 ++++++++++++++++++++++++++++++++ scripts/GrantMinterRole.ts | 75 ++++++++++++++++++++++++++ scripts/MintTokens.ts | 76 ++++++++++++++++++++++++++ scripts/TransferTokens.ts | 76 ++++++++++++++++++++++++++ scripts/ViewProposalStatus.ts | 83 +++++++++++++++++++++++++++++ scripts/ViewTokenStatus.ts | 74 ++++++++++++++++++++++++++ scripts/ViewVotingStatus.ts | 88 ++++++++++++++++++++++++++++++ 10 files changed, 775 insertions(+) create mode 100644 scripts/CastVote.ts create mode 100644 scripts/DelegateVotes.ts create mode 100644 scripts/DeployMyToken.ts create mode 100644 scripts/DeployTokenizedBallot.ts create mode 100644 scripts/GrantMinterRole.ts create mode 100644 scripts/MintTokens.ts create mode 100644 scripts/TransferTokens.ts create mode 100644 scripts/ViewProposalStatus.ts create mode 100644 scripts/ViewTokenStatus.ts create mode 100644 scripts/ViewVotingStatus.ts diff --git a/scripts/CastVote.ts b/scripts/CastVote.ts new file mode 100644 index 0000000..c91c5af --- /dev/null +++ b/scripts/CastVote.ts @@ -0,0 +1,86 @@ +// npx ts-node --files ./scripts/CastVote.ts CONTRACT_ADDRESS PROPOSAL_INDEX AMOUNT + +import { createPublicClient, http, createWalletClient, hexToString } from "viem"; + +import { privateKeyToAccount } from "viem/accounts"; +import { sepolia } from "viem/chains"; +import { abi } from "../artifacts/contracts/TokenizedBallot.sol/TokenizedBallot.json"; +import * as dotenv from "dotenv"; +dotenv.config(); + +const providerApiKey = process.env.ALCHEMY_API_KEY || ""; +const voterPrivateKey = process.env.PRIVATE_KEY || ""; + +function validateParameters(parameters: string[]) { + if (!parameters || parameters.length < 3) + throw new Error("Parameters not provided"); + + const contractAddress = parameters[0] as `0x${string}`; + if (!contractAddress) throw new Error("Contract address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(contractAddress)) + throw new Error("Invalid contract address"); + + const proposalIndex = parameters[1]; + if (isNaN(Number(proposalIndex))) throw new Error("Invalid proposal index"); + + const amount = parameters[2]; + if (isNaN(Number(amount))) throw new Error("Invalid amount"); + + return { contractAddress, proposalIndex, amount }; +} + +async function main() { + console.log("\n"); + const { contractAddress, proposalIndex, amount } = validateParameters(process.argv.slice(2)); + + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + + const proposal = (await publicClient.readContract({ + address: contractAddress, + abi, + functionName: "proposals", + args: [BigInt(proposalIndex)], + })) as any[]; + const proposalName = hexToString(proposal[0], { size: 32 }); + + const account = privateKeyToAccount(`0x${voterPrivateKey}`); + const sender = createWalletClient({ + account, + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + + console.log(`Voting for proposal '${proposalName}' with ${amount} votes`); + console.log("Confirm? (Y/n)"); + + const stdin = process.stdin; + stdin.on("data", async function (d) { + if (d.toString().trim() == "Y") { + const hash = await sender.writeContract({ + address: contractAddress, + abi, + functionName: "vote", + args: [proposalIndex, amount], + }); + console.log("Transaction hash:", hash); + console.log("Waiting for confirmations..."); + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + console.log(`Transaction confirmed: ${receipt.status}`); + } else { + console.log("Operation cancelled"); + } + process.exit(); + }); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); \ No newline at end of file diff --git a/scripts/DelegateVotes.ts b/scripts/DelegateVotes.ts new file mode 100644 index 0000000..15273f5 --- /dev/null +++ b/scripts/DelegateVotes.ts @@ -0,0 +1,73 @@ +// npx ts-node --files ./scripts/DelegateVotes.ts CONTRACT_ADDRESS DELEGATE_ADDRESS + +import { createPublicClient, http, createWalletClient } from "viem"; + +import { privateKeyToAccount } from "viem/accounts"; +import { sepolia } from "viem/chains"; +import { abi } from "../artifacts/contracts/MyToken.sol/MyToken.json"; +import * as dotenv from "dotenv"; +dotenv.config(); + +const providerApiKey = process.env.ALCHEMY_API_KEY || ""; +const delegatorPrivateKey = process.env.PRIVATE_KEY || ""; + +function validateParameters(parameters: string[]) { + if (!parameters || parameters.length < 2) + throw new Error("Parameters not provided"); + + const contractAddress = parameters[0] as `0x${string}`; + if (!contractAddress) throw new Error("Contract address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(contractAddress)) + throw new Error("Invalid contract address"); + + const delegateAddress = parameters[1] as `0x${string}`; + if (!delegateAddress) throw new Error("Delegate address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(delegateAddress)) + throw new Error("Invalid delegate address"); + + return { contractAddress, delegateAddress } +} + +async function main() { + console.log("\n"); + const { contractAddress, delegateAddress } = validateParameters(process.argv.slice(2)); + + const account = privateKeyToAccount(`0x${delegatorPrivateKey}`); + const sender = createWalletClient({ + account, + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + + console.log(`Delegating from ${account.address} tokens to account: ${delegateAddress}`); + console.log("Confirm? (Y/n)"); + + const stdin = process.stdin; + stdin.on("data", async function (d) { + if (d.toString().trim() == "Y") { + const hash = await sender.writeContract({ + address: contractAddress, + abi, + functionName: "delegate", + args: [delegateAddress], + }); + console.log("Transaction hash:", hash); + console.log("Waiting for confirmations..."); + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + console.log(`Transaction confirmed: ${receipt.status}`); + console.log(`Block: ${receipt.blockNumber}`) + } else { + console.log("Operation cancelled"); + } + process.exit(); + }); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); \ No newline at end of file diff --git a/scripts/DeployMyToken.ts b/scripts/DeployMyToken.ts new file mode 100644 index 0000000..b7d6d93 --- /dev/null +++ b/scripts/DeployMyToken.ts @@ -0,0 +1,53 @@ +// npx ts-node --files ./scripts/DeployMyToken.ts + +import { createPublicClient, http, createWalletClient, formatEther } from "viem"; + +import { privateKeyToAccount } from "viem/accounts"; +import { sepolia } from "viem/chains"; +import { abi, bytecode } from "../artifacts/contracts/MyToken.sol/MyToken.json"; +import * as dotenv from "dotenv"; +dotenv.config(); + +const providerApiKey = process.env.ALCHEMY_API_KEY || ""; +const deployerPrivateKey = process.env.PRIVATE_KEY || ""; + +async function main() { + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + const blockNumber = await publicClient.getBlockNumber(); + console.log("Last block number:", blockNumber); + + const account = privateKeyToAccount(`0x${deployerPrivateKey}`); + const deployer = createWalletClient({ + account, + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + console.log("Deployer address:", deployer.account.address); + const balance = await publicClient.getBalance({ + address: deployer.account.address, + }); + console.log( + "Deployer balance:", + formatEther(balance), + deployer.chain.nativeCurrency.symbol + ); + + console.log("\nDeploying Token contract"); + const hash = await deployer.deployContract({ + abi, + bytecode: bytecode as `0x${string}`, + }); + console.log("Transaction hash:", hash); + console.log("Waiting for confirmations..."); + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + const contractAddress = receipt.contractAddress; + console.log("Token contract deployed to:", contractAddress); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); \ No newline at end of file diff --git a/scripts/DeployTokenizedBallot.ts b/scripts/DeployTokenizedBallot.ts new file mode 100644 index 0000000..cd15782 --- /dev/null +++ b/scripts/DeployTokenizedBallot.ts @@ -0,0 +1,91 @@ +// npx ts-node --files ./scripts/DeployTokenizedBallot.ts TOKEN_CONTRACT TARGET_BLOCK_NUMBER PROPOSAL_NAMES + +import { createPublicClient, http, createWalletClient, formatEther, toHex } from "viem"; + +import { privateKeyToAccount } from "viem/accounts"; +import { sepolia } from "viem/chains"; +import { abi, bytecode } from "../artifacts/contracts/TokenizedBallot.sol/TokenizedBallot.json"; +import * as dotenv from "dotenv"; +dotenv.config(); + +const providerApiKey = process.env.ALCHEMY_API_KEY || ""; +const deployerPrivateKey = process.env.PRIVATE_KEY || ""; + +function validateParameters(parameters: string[]) { + if (!parameters || parameters.length < 3) + throw new Error("Parameters not provided"); + + const tokenAddress = parameters[0] as `0x${string}`; + if (!tokenAddress) throw new Error("Token address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(tokenAddress)) + throw new Error("Invalid token address"); + + const targetBlockNumber = parameters[1]; + if (isNaN(Number(targetBlockNumber))) throw new Error("Invalid target block number"); + + const proposals = parameters.slice(2); + if (!proposals || proposals.length < 1) + throw new Error("Proposals not provided"); + + return { tokenAddress, targetBlockNumber, proposals } +} + +async function main() { + console.log("\n"); + const { tokenAddress, targetBlockNumber, proposals } = validateParameters(process.argv.slice(2)); + + console.log(`Deploying ballot with proposals: ${proposals}`); + console.log("Confirm? (Y/n)"); + + const stdin = process.stdin; + stdin.on("data", async function (d) { + if (d.toString().trim() == "Y") { + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + const blockNumber = await publicClient.getBlockNumber(); + console.log("Last block number:", blockNumber); + + const account = privateKeyToAccount(`0x${deployerPrivateKey}`); + const deployer = createWalletClient({ + account, + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + console.log("Deployer address:", deployer.account.address); + const balance = await publicClient.getBalance({ + address: deployer.account.address, + }); + console.log( + "Deployer balance:", + formatEther(balance), + deployer.chain.nativeCurrency.symbol + ); + + console.log("\nDeploying Ballot contract"); + const hash = await deployer.deployContract({ + abi, + bytecode: bytecode as `0x${string}`, + args: [ + proposals.map((prop) => toHex(prop, { size: 32 })), + tokenAddress, + targetBlockNumber + ] + }); + console.log("Transaction hash:", hash); + console.log("Waiting for confirmations..."); + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + const contractAddress = receipt.contractAddress; + console.log("Ballot contract deployed to:", contractAddress); + } else { + console.log("Operation cancelled"); + } + process.exit(); + }); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); \ No newline at end of file diff --git a/scripts/GrantMinterRole.ts b/scripts/GrantMinterRole.ts new file mode 100644 index 0000000..7714309 --- /dev/null +++ b/scripts/GrantMinterRole.ts @@ -0,0 +1,75 @@ +// npx ts-node --files ./scripts/GrantMinterRole.ts TOKEN_ADDRESS MINTER_ADDRESS + +import { createPublicClient, http, createWalletClient, keccak256, toHex } from "viem"; + +import { privateKeyToAccount } from "viem/accounts"; +import { sepolia } from "viem/chains"; +import { abi } from "../artifacts/contracts/MyToken.sol/MyToken.json"; +import * as dotenv from "dotenv"; +dotenv.config(); + +const providerApiKey = process.env.ALCHEMY_API_KEY || ""; +const delegatorPrivateKey = process.env.PRIVATE_KEY || ""; + +const MINTER_ROLE = keccak256(toHex("MINTER_ROLE"));; + +function validateParameters(parameters: string[]) { + if (!parameters || parameters.length < 2) + throw new Error("Parameters not provided"); + + const tokenAddress = parameters[0] as `0x${string}`; + if (!tokenAddress) throw new Error("Token address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(tokenAddress)) + throw new Error("Invalid token address"); + + const minterAddress = parameters[1] as `0x${string}`; + if (!minterAddress) throw new Error("Minter address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(minterAddress)) + throw new Error("Invalid minter address"); + + return { tokenAddress, minterAddress } +} + +async function main() { + console.log("\n"); + const { tokenAddress, minterAddress } = validateParameters(process.argv.slice(2)); + + const account = privateKeyToAccount(`0x${delegatorPrivateKey}`); + const sender = createWalletClient({ + account, + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + + console.log(`Granting minter role to ${minterAddress}`); + console.log("Confirm? (Y/n)"); + + const stdin = process.stdin; + stdin.on("data", async function (d) { + if (d.toString().trim() == "Y") { + const hash = await sender.writeContract({ + address: tokenAddress, + abi, + functionName: "grantRole", + args: [MINTER_ROLE, minterAddress], + }); + console.log("Transaction hash:", hash); + console.log("Waiting for confirmations..."); + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + console.log(`Transaction confirmed: ${receipt.status}`); + console.log(`Block: ${receipt.blockNumber}`) + } else { + console.log("Operation cancelled"); + } + process.exit(); + }); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); \ No newline at end of file diff --git a/scripts/MintTokens.ts b/scripts/MintTokens.ts new file mode 100644 index 0000000..e7860b9 --- /dev/null +++ b/scripts/MintTokens.ts @@ -0,0 +1,76 @@ +// npx ts-node --files ./scripts/MintTokens.ts CONTRACT_ADDRESS TO_ADDRESS AMOUNT + +import { createPublicClient, http, createWalletClient } from "viem"; + +import { privateKeyToAccount } from "viem/accounts"; +import { sepolia } from "viem/chains"; +import { abi } from "../artifacts/contracts/MyToken.sol/MyToken.json"; +import * as dotenv from "dotenv"; +dotenv.config(); + +const providerApiKey = process.env.ALCHEMY_API_KEY || ""; +const minterPrivateKey = process.env.PRIVATE_KEY || ""; + +function validateParameters(parameters: string[]) { + if (!parameters || parameters.length < 3) + throw new Error("Parameters not provided"); + + const contractAddress = parameters[0] as `0x${string}`; + if (!contractAddress) throw new Error("Contract address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(contractAddress)) + throw new Error("Invalid contract address"); + + const toAddress = parameters[1] as `0x${string}`; + if (!toAddress) throw new Error("To address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(toAddress)) + throw new Error("Invalid to address"); + + const amount = parameters[2]; + if (isNaN(Number(amount))) throw new Error("Invalid amount"); + + return { contractAddress, toAddress, amount } +} + +async function main() { + console.log("\n"); + const { contractAddress, toAddress, amount } = validateParameters(process.argv.slice(2)); + + const account = privateKeyToAccount(`0x${minterPrivateKey}`); + const minter = createWalletClient({ + account, + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + + console.log(`Minting ${amount} tokens to account: ${toAddress}`); + console.log("Confirm? (Y/n)"); + + const stdin = process.stdin; + stdin.on("data", async function (d) { + if (d.toString().trim() == "Y") { + const hash = await minter.writeContract({ + address: contractAddress, + abi, + functionName: "mint", + args: [toAddress, BigInt(amount)], + }); + console.log("Transaction hash:", hash); + console.log("Waiting for confirmations..."); + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + console.log(`Transaction confirmed: ${receipt.status}`); + console.log(`Block: ${receipt.blockNumber}`) + } else { + console.log("Operation cancelled"); + } + process.exit(); + }); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); \ No newline at end of file diff --git a/scripts/TransferTokens.ts b/scripts/TransferTokens.ts new file mode 100644 index 0000000..f14d670 --- /dev/null +++ b/scripts/TransferTokens.ts @@ -0,0 +1,76 @@ +// npx ts-node --files ./scripts/TransferTokens.ts CONTRACT_ADDRESS TO_ADDRESS AMOUNT + +import { createPublicClient, http, createWalletClient } from "viem"; + +import { privateKeyToAccount } from "viem/accounts"; +import { sepolia } from "viem/chains"; +import { abi } from "../artifacts/contracts/MyToken.sol/MyToken.json"; +import * as dotenv from "dotenv"; +dotenv.config(); + +const providerApiKey = process.env.ALCHEMY_API_KEY || ""; +const senderPrivateKey = process.env.PRIVATE_KEY || ""; + +function validateParameters(parameters: string[]) { + if (!parameters || parameters.length < 3) + throw new Error("Parameters not provided"); + + const contractAddress = parameters[0] as `0x${string}`; + if (!contractAddress) throw new Error("Contract address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(contractAddress)) + throw new Error("Invalid contract address"); + + const toAddress = parameters[1] as `0x${string}`; + if (!toAddress) throw new Error("To address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(toAddress)) + throw new Error("Invalid to address"); + + const amount = parameters[2]; + if (isNaN(Number(amount))) throw new Error("Invalid amount"); + + return { contractAddress, toAddress, amount } +} + +async function main() { + console.log("\n"); + const { contractAddress, toAddress, amount } = validateParameters(process.argv.slice(2)); + + const account = privateKeyToAccount(`0x${senderPrivateKey}`); + const sender = createWalletClient({ + account, + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + + console.log(`Transferring ${amount} tokens to account: ${toAddress}`); + console.log("Confirm? (Y/n)"); + + const stdin = process.stdin; + stdin.on("data", async function (d) { + if (d.toString().trim() == "Y") { + const hash = await sender.writeContract({ + address: contractAddress, + abi, + functionName: "transfer", + args: [toAddress, BigInt(amount)], + }); + console.log("Transaction hash:", hash); + console.log("Waiting for confirmations..."); + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + console.log(`Transaction confirmed: ${receipt.status}`); + console.log(`Block: ${receipt.blockNumber}`) + } else { + console.log("Operation cancelled"); + } + process.exit(); + }); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); \ No newline at end of file diff --git a/scripts/ViewProposalStatus.ts b/scripts/ViewProposalStatus.ts new file mode 100644 index 0000000..c440376 --- /dev/null +++ b/scripts/ViewProposalStatus.ts @@ -0,0 +1,83 @@ +// npx ts-node --files ./scripts/ViewProposalStatus.ts CONTRACT_ADDRESS + +import { createPublicClient, http, hexToString, ContractFunctionExecutionError } from "viem"; + +import { sepolia } from "viem/chains"; +import { abi } from "../artifacts/contracts/TokenizedBallot.sol/TokenizedBallot.json"; +import * as dotenv from "dotenv"; +dotenv.config(); + +const providerApiKey = process.env.ALCHEMY_API_KEY || ""; + +function validateParameters(parameters: string[]) { + if (!parameters || parameters.length < 1) + throw new Error("Parameters not provided"); + + const contractAddress = parameters[0] as `0x${string}`; + if (!contractAddress) throw new Error("Contract address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(contractAddress)) + throw new Error("Invalid contract address"); + + return { contractAddress } +} + +async function fetchProposals(publicClient: any, contractAddress: string) { + let proposals: object[] = []; + let proposalIndex = 0; + let moreProposals = true; + + while (moreProposals) { + try { + const proposal = (await publicClient.readContract({ + address: contractAddress, + abi, + functionName: "proposals", + args: [BigInt(proposalIndex)], + })) as any[]; + + const name = hexToString(proposal[0], { size: 32 }); + const votes = proposal[1]; + proposals.push({index: proposalIndex, name, votes}); + + proposalIndex++; + } catch (error) { + moreProposals = false; + + if (!(error instanceof ContractFunctionExecutionError)) { + console.error('An unexpected error occurred:', error); + } + } + } + + return proposals; +} + +async function main() { + console.log("\n"); + const { contractAddress } = validateParameters(process.argv.slice(2)); + + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + + const proposals = await fetchProposals(publicClient, contractAddress); + console.log(`${proposals.length} proposals found:`); + for (let index = 0; index < proposals.length; index++) { + console.log(proposals[index]); + } + + const winnerName = (await publicClient.readContract({ + address: contractAddress, + abi, + functionName: "winnerName", + })) as `0x${string}`; + console.log(`\nWinner: ${hexToString(winnerName, { size: 32 })}`); + + process.exit(); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); \ No newline at end of file diff --git a/scripts/ViewTokenStatus.ts b/scripts/ViewTokenStatus.ts new file mode 100644 index 0000000..7de1bfb --- /dev/null +++ b/scripts/ViewTokenStatus.ts @@ -0,0 +1,74 @@ +// npx ts-node --files ./scripts/ViewTokenStatus.ts CONTRACT_ADDRESS ACCOUNT_ADDRESS + +import { createPublicClient, http } from "viem"; + +import { sepolia } from "viem/chains"; +import { abi } from "../artifacts/contracts/MyToken.sol/MyToken.json"; +import * as dotenv from "dotenv"; +dotenv.config(); + +const providerApiKey = process.env.ALCHEMY_API_KEY || ""; + +function validateParameters(parameters: string[]) { + if (!parameters || parameters.length < 2) + throw new Error("Parameters not provided"); + + const contractAddress = parameters[0] as `0x${string}`; + if (!contractAddress) throw new Error("Contract address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(contractAddress)) + throw new Error("Invalid contract address"); + + const accountAddress = parameters[1] as `0x${string}`; + if (!accountAddress) throw new Error("Account address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(accountAddress)) + throw new Error("Invalid account address"); + + return { contractAddress, accountAddress } +} + +async function main() { + console.log("\n"); + const { contractAddress, accountAddress } = validateParameters(process.argv.slice(2)); + + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + const balance = (await publicClient.readContract({ + address: contractAddress, + abi, + functionName: "balanceOf", + args: [accountAddress], + })) as BigInt; + + const votes = (await publicClient.readContract({ + address: contractAddress, + abi, + functionName: "getVotes", + args: [accountAddress], + })) as BigInt; + + const delegate = (await publicClient.readContract({ + address: contractAddress, + abi, + functionName: "delegates", + args: [accountAddress], + })) as String; + + const blockNumber = await publicClient.getBlockNumber(); + + console.log( + `Account: ${accountAddress}\n`, + `Tokens: ${balance.toString()}\n`, + `Votes: ${votes.toString()}\n`, + `Delegate: ${delegate}\n`, + `Block: ${blockNumber}` + ); + + process.exit(); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); \ No newline at end of file diff --git a/scripts/ViewVotingStatus.ts b/scripts/ViewVotingStatus.ts new file mode 100644 index 0000000..a7ad528 --- /dev/null +++ b/scripts/ViewVotingStatus.ts @@ -0,0 +1,88 @@ +// npx ts-node --files ./scripts/ViewVotingStatus.ts CONTRACT_ADDRESS ACCOUNT_ADDRESS + +import { createPublicClient, http } from "viem"; + +import { sepolia } from "viem/chains"; +import { abi } from "../artifacts/contracts/TokenizedBallot.sol/TokenizedBallot.json"; +import { abi as tokenAbi } from "../artifacts/contracts/MyToken.sol/MyToken.json"; +import * as dotenv from "dotenv"; +dotenv.config(); + +const providerApiKey = process.env.ALCHEMY_API_KEY || ""; + +function validateParameters(parameters: string[]) { + if (!parameters || parameters.length < 2) + throw new Error("Parameters not provided"); + + const contractAddress = parameters[0] as `0x${string}`; + if (!contractAddress) throw new Error("Contract address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(contractAddress)) + throw new Error("Invalid contract address"); + + const accountAddress = parameters[1] as `0x${string}`; + if (!accountAddress) throw new Error("Account address not provided"); + if (!/^0x[a-fA-F0-9]{40}$/.test(accountAddress)) + throw new Error("Invalid account address"); + + return { contractAddress, accountAddress } +} + +async function main() { + console.log("\n"); + const { contractAddress, accountAddress } = validateParameters(process.argv.slice(2)); + + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(`https://eth-sepolia.g.alchemy.com/v2/${providerApiKey}`), + }); + + const tokenAddress = (await publicClient.readContract({ + address: contractAddress, + abi, + functionName: "tokenContract", + })) as `0x${string}`; + + const targetBlockNumber = (await publicClient.readContract({ + address: contractAddress, + abi, + functionName: "targetBlockNumber", + })) as BigInt; + + const fullVotePower = (await publicClient.readContract({ + address: tokenAddress, + abi: tokenAbi, + functionName: "getPastVotes", + args: [accountAddress, targetBlockNumber], + })) as BigInt; + + const spentVotePower = (await publicClient.readContract({ + address: contractAddress, + abi, + functionName: "spentVotePower", + args: [accountAddress], + })) as BigInt; + + const remainingVotePower = (await publicClient.readContract({ + address: contractAddress, + abi, + functionName: "getVotePower", + args: [accountAddress], + })) as BigInt; + + const blockNumber = await publicClient.getBlockNumber(); + + console.log( + `Account: ${accountAddress}\n`, + `Full Vote Power: ${fullVotePower.toString()}\n`, + `Spent Votes: ${spentVotePower.toString()}\n`, + `Remaining Vote Power: ${remainingVotePower.toString()}\n`, + `Block: ${blockNumber}` + ); + + process.exit(); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); \ No newline at end of file