zhereh-frontend

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit d316eff034543def36e2a8ec43fd7bb1c0a83b47
parent 9d7bae2a4b828f502c0a7f0406f8eaa10c07b5c5
Author: William Muli <willi.wambu@gmail.com>
Date:   Fri, 23 Jun 2023 00:58:04 +0300

Added current proposal and create proposal

Diffstat:
Mpackage-lock.json | 41++++++++++++++++++++++++++++++++++++++---
Mpackage.json | 1+
Msrc/app.css | 10+++++++---
Msrc/components/Nav.svelte | 2+-
Msrc/components/NetworkForm.svelte | 2+-
Asrc/components/ProposalForm.svelte | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/ProposalView.svelte | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/options/nav-options.ts | 19++++++++-----------
Msrc/routes/+layout.svelte | 49+++++++++++--------------------------------------
Msrc/routes/+page.svelte | 30++++++++++++++++--------------
Asrc/routes/create-proposal/+page.svelte | 8++++++++
Asrc/routes/current-proposal/+page.svelte | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/routes/edit-chain/+page.svelte | 2+-
Msrc/shared/types.ts | 40++++++++++++++++++++++++++++++++++++++--
Asrc/shared/wala.ts | 26++++++++++++++++++++++++++
Asrc/utils/copy-clipboard.ts | 3+++
Asrc/utils/proposal.ts | 30++++++++++++++++++++++++++++++
17 files changed, 532 insertions(+), 74 deletions(-)

diff --git a/package-lock.json b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@wagmi/chains": "^1.1.0", "ethers": "^6.5.1", + "svelte-french-toast": "^1.0.4", "viem": "^1.0.7" }, "devDependencies": { @@ -3459,7 +3460,6 @@ "version": "3.59.1", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.1.tgz", "integrity": "sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==", - "dev": true, "engines": { "node": ">= 8" } @@ -3536,6 +3536,17 @@ "node": ">=4.0" } }, + "node_modules/svelte-french-toast": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/svelte-french-toast/-/svelte-french-toast-1.0.4.tgz", + "integrity": "sha512-ryRvVSZFXCKLehoYp8Z1PFFs+Kexuk7Pmt51z+OQ/MD04fCtXnF9G57C2wZbpEV79s93RCgLaDH/YGACuBs1hw==", + "dependencies": { + "svelte-writable-derived": "^3.0.1" + }, + "peerDependencies": { + "svelte": "^3.57.0" + } + }, "node_modules/svelte-hmr": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.2.tgz", @@ -3622,6 +3633,17 @@ "node": ">=12" } }, + "node_modules/svelte-writable-derived": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/svelte-writable-derived/-/svelte-writable-derived-3.1.0.tgz", + "integrity": "sha512-cTvaVFNIJ036vSDIyPxJYivKC7ZLtcFOPm1Iq6qWBDo1fOHzfk6ZSbwaKrxhjgy52Rbl5IHzRcWgos6Zqn9/rg==", + "funding": { + "url": "https://ko-fi.com/pixievoltno1" + }, + "peerDependencies": { + "svelte": "^3.2.1 || ^4.0.0-next.1" + } + }, "node_modules/tailwindcss": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", @@ -6428,8 +6450,7 @@ "svelte": { "version": "3.59.1", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.1.tgz", - "integrity": "sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==", - "dev": true + "integrity": "sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==" }, "svelte-check": { "version": "3.4.3", @@ -6476,6 +6497,14 @@ } } }, + "svelte-french-toast": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/svelte-french-toast/-/svelte-french-toast-1.0.4.tgz", + "integrity": "sha512-ryRvVSZFXCKLehoYp8Z1PFFs+Kexuk7Pmt51z+OQ/MD04fCtXnF9G57C2wZbpEV79s93RCgLaDH/YGACuBs1hw==", + "requires": { + "svelte-writable-derived": "^3.0.1" + } + }, "svelte-hmr": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.2.tgz", @@ -6507,6 +6536,12 @@ } } }, + "svelte-writable-derived": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/svelte-writable-derived/-/svelte-writable-derived-3.1.0.tgz", + "integrity": "sha512-cTvaVFNIJ036vSDIyPxJYivKC7ZLtcFOPm1Iq6qWBDo1fOHzfk6ZSbwaKrxhjgy52Rbl5IHzRcWgos6Zqn9/rg==", + "requires": {} + }, "tailwindcss": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", diff --git a/package.json b/package.json @@ -39,6 +39,7 @@ "dependencies": { "@wagmi/chains": "^1.1.0", "ethers": "^6.5.1", + "svelte-french-toast": "^1.0.4", "viem": "^1.0.7" } } diff --git a/src/app.css b/src/app.css @@ -3,9 +3,13 @@ @tailwind components; @tailwind utilities; -body { - background: linear-gradient(180deg,#fff 22%,#d7caec 100%); - overflow: scroll; + +html, body { + background: linear-gradient(180deg,#fff 3%,#d7caec 100%); + background-size: cover; + background-repeat: no-repeat; overscroll-behavior-y: none; + min-height: 100vh; + margin: 0; } diff --git a/src/components/Nav.svelte b/src/components/Nav.svelte @@ -14,7 +14,7 @@ let connectedChainId: string = '' $: ({ name, symbol, decimals, balance: erc20Balance } = $voteToken) - $: ({ isConnected, userAddress } = $connectionDetails) + $: ({ userAddress } = $connectionDetails) $: ({ contractAddress, erc20ContractAddress } = $votingConfig) $: { diff --git a/src/components/NetworkForm.svelte b/src/components/NetworkForm.svelte @@ -88,7 +88,7 @@ <div class="flex justify-center items-center w-full py-10"> <div class="flex w-full items-center justify-center"> - <div class="card w-full md:w-2/5 m-2 md:m-0 shadow-xl bg-white"> + <div class="card w-full sm:mx-2 md:w-4/5 lg:w-3/5 xl:w-2/5 m-2 md:m-0 shadow-xl bg-white"> <div class="card-body"> <h2 class="text-gray-900 text-center w-full font-semibold text-xl">Network Details</h2> <form diff --git a/src/components/ProposalForm.svelte b/src/components/ProposalForm.svelte @@ -0,0 +1,185 @@ +<script lang="ts"> + import { goto } from '$app/navigation'; + import { PUBLIC_VOTE_CONTRACT_ADDRESS } from '$env/static/public' + import { configuredChain, connectionDetails } from '../store' + import { publicClient, walletClient } from '../client' + import { voteContractAbi } from '../shared/token-vote-abi' + import { Routes } from '../shared/types' + import { uploadTextToWala } from '../shared/wala' + + type ProposalFormData = { + description: string + targetVote: number + blockWait: number + options: string[] + } + + const formData: ProposalFormData = { + description: '', + targetVote: 0, + blockWait: 0, + options: [] + } + + let submitting: boolean = false + + const onSubmit = async () => { + const { description, targetVote, blockWait, options } = formData + if (description === '' || targetVote === 0 || blockWait === 0) return + + try { + const optionsValid = options.some(option => option.trim() !== '') && options.length > 0 + submitting = true + if (optionsValid) { + await createWithOptions(description, blockWait, targetVote, options) + } else { + await createWithoutOptions(description, blockWait, targetVote) + } + submitting = false + goto(Routes.Home) + } catch (error) { + console.error(error) + } + } + + const createWithoutOptions = async(description: string, blockWait: number, targetVote: number) => { + const descriptionHash = await uploadTextToWala(description) + const { request } = await publicClient($configuredChain).simulateContract({ + address: PUBLIC_VOTE_CONTRACT_ADDRESS as `0x${string}`, + abi: voteContractAbi, + functionName: 'propose', + args: [`0x${descriptionHash}`, BigInt(blockWait), targetVote], + account: $connectionDetails.userAddress + }) + const hash = await walletClient($configuredChain).writeContract(request) + const receipt = await publicClient($configuredChain).waitForTransactionReceipt({ + hash + }) + + return receipt + } + + const createWithOptions = async(description: string, blockWait: number, targetVote: number, options: string[]) => { + const descriptionHash = await uploadTextToWala(description) + const promises = options.map(option => uploadTextToWala(option)) + const optionHashes = (await Promise.all(promises)).map(optionHash => `0x${optionHash}`) + const { request } = await publicClient($configuredChain).simulateContract({ + address: PUBLIC_VOTE_CONTRACT_ADDRESS as `0x${string}`, + abi: voteContractAbi, + functionName: 'proposeMulti', + args: [`0x${descriptionHash}`, optionHashes as `0x${string}`[], BigInt(blockWait), targetVote], + account: $connectionDetails.userAddress + }) + const hash = await walletClient($configuredChain).writeContract(request) + const receipt = await publicClient($configuredChain).waitForTransactionReceipt({ + hash + }) + + return receipt + } + + const removeOption = (index: number) => { + const options = formData.options.filter((_option, idx) => idx !== index) + formData.options = [ ...options ] + } + + const addOption = () => { + formData.options = [ ...formData.options, ''] + } +</script> + +<div class="flex justify-center items-center w-full py-10"> + <div class="flex w-full items-center justify-center"> + <div class="card w-full sm:mx-2 md:w-3/5 lg:w-3/5 xl:w-2/5 m-2 md:m-0 shadow-xl bg-white"> + <div class="card-body"> + <h2 class="text-gray-900 text-center w-full font-semibold text-xl">Create Proposal</h2> + <form + on:submit|preventDefault={onSubmit} + class="flex flex-col justify-center w-full flex-1" + > + <div class="form-control w-full"> + <label for="description" class="label"> + <span class="label-text text-gray-800">Description*</span> + </label> + <textarea + name="description" + class="textarea textarea-bordered" + placeholder="Enter proposal description" + bind:value={formData.description} + /> + </div> + + <div class="flex flex-col md:flex-row w-full gap-3 mt-3"> + <div class="form-control w-full"> + <label for="blockWait" class="label"> + <span class="label-text text-gray-800">Block Wait*</span> + </label> + <input + name="blockWait" + type="number" + placeholder="Enter block wait" + class="input input-bordered w-full" + bind:value={formData.blockWait} + /> + </div> + + <div class="form-control w-full"> + <label for="targetVote" class="label"> + <span class="label-text text-gray-800">Target Vote*</span> + </label> + <input + name="targetVote" + type="number" + placeholder="Enter target vote" + class="input input-bordered w-full" + bind:value={formData.targetVote} + /> + </div> + </div> + <h2 class="text-gray-900 text-center w-full font-medium text-md mt-6">Options for voters to choose</h2> + <div class="form-control w-full"> + {#each formData.options as option, i } + <div class="flex justify-between items-center gap-5"> + <div class="w-full"> + <label for={`option-${i}`} class="label"> + <span class="label-text text-gray-800">Option {i + 1}</span> + </label> + <input + name={`option-${i}`} + type="text" + placeholder="Enter option text" + class="input input-bordered w-full" + autocomplete="off" + bind:value={formData.options[i]} + /> + </div> + <button on:click={() => removeOption(i)} type="button" class="btn btn-error btn-sm btn-circle mt-[35px]"> + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"> + <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15" /> + </svg> + </button> + </div> + {/each} + </div> + <div class="flex justify-end mt-4"> + <button on:click={addOption} type="button" class="btn btn-primary font-light text-white btn-sm normal-case"> + Add option + </button> + </div> + + <p class="text-gray-600 text-xs mt-8">* Required</p> + + <div class="flex justify-between"> + <button type="submit" class="btn w-40 btn-primary self-center mt-6 normal-case text-white"> + Submit + {#if submitting} + <span class="loading loading-spinner" /> + {/if} + </button> + <a href="/" type="button" class="btn btn-secondary mt-6 normal-case text-white">Cancel</a> + </div> + </form> + </div> + </div> + </div> +</div> +\ No newline at end of file diff --git a/src/components/ProposalView.svelte b/src/components/ProposalView.svelte @@ -0,0 +1,107 @@ +<script lang="ts"> + import { formatUnits, hexToString } from "viem" + import type { Proposal } from "../shared/types" + import { configuredChain, voteToken } from "../store" + import { copyToClipboard } from "../utils/copy-clipboard" + import { onMount } from "svelte" + import { publicClient } from "../client" + import { getProposalStateDescription } from "../utils/proposal" + import { getText } from "../shared/wala" + + export let proposal: Proposal + let description: string | undefined + let options: (string | undefined) [] = [] + export let title: string | undefined = undefined + let blockNumber: bigint + + $: ({ decimals, symbol } = $voteToken) + + onMount(async () => { + blockNumber = await publicClient($configuredChain).getBlockNumber() + }) + + const copyAddress = async (text: string) => await copyToClipboard(text) + + onMount(async () => { + if(proposal.description) { + description = await getText(proposal.description) + } + + if(proposal.options) { + const promises = proposal.options.map(option => getText(option)) + options = await Promise.all(promises) + } + }) +</script> + +{#if proposal} + <div class="card w-full sm:mx-2 md:w-4/5 lg:w-3/5 xl:w-2/5 m-2 md:m-0 shadow-xl bg-white"> + <div class="card-body"> + <h2 class="text-gray-900 text-center w-full font-semibold text-xl">{title || 'Proposal Details'}</h2> + <div class="flex flex-col"> + <p class="text-gray-500 text-sm">Description</p> + <p class="text-gray-900 text-sm mt-1"> + {description} + </p> + + <p class="text-gray-500 text-sm mt-4">Options</p> + {#if options.length > 0} + <ul class="list-disc list-inside"> + {#each options as option} + <li class="text-gray-900 text-sm mt-1"> + {option} + </li> + {/each} + </ul> + {:else} + <p class="text-gray-900 text-sm mt-1 italic">No voting options</p> + {/if} + + <p class="text-gray-500 text-sm mt-4">Supply</p> + <p class="text-gray-900 text-sm mt-1"> + {formatUnits(proposal.supply, decimals)} {symbol} + </p> + + <p class="text-gray-500 text-sm mt-4">Total</p> + <p class="text-gray-900 text-sm mt-1"> + {proposal.total} + </p> + + <p class="text-gray-500 text-sm mt-4">Proposed By</p> + <p class="text-gray-900 text-sm mt-1 lowercase flex align-center gap-1 italic"> + {proposal.proposer} + <button on:click={() => copyAddress(proposal.proposer)}> + <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 text-secondary"> + <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" /> + </svg> + </button> + </p> + + <p class="text-gray-500 text-sm mt-4">Target Vote(Min. participation in Parts per million)</p> + <p class="text-gray-900 text-sm mt-1"> + {proposal.targetVotePpm} ({proposal.targetVotePpm/1000000 * 100}%) + </p> + + <p class="text-gray-500 text-sm mt-4">Block Deadline</p> + <p class="text-gray-900 text-sm mt-1"> + {proposal.blockDeadline} + </p> + + <p class="text-gray-500 text-sm mt-4">Current Block Number</p> + <p class="text-gray-900 text-sm mt-1"> + {blockNumber} + </p> + + <p class="text-gray-500 text-sm mt-4">Status</p> + <p class="text-gray-900 text-sm mt-1"> + {getProposalStateDescription(proposal.state)} + </p> + + <p class="text-gray-500 text-sm mt-4">Scan cursor</p> + <p class="text-gray-900 text-sm mt-1"> + {proposal.scanCursor} + </p> + </div> + </div> + </div> +{/if} +\ No newline at end of file diff --git a/src/options/nav-options.ts b/src/options/nav-options.ts @@ -1,20 +1,17 @@ -export type NavOption = { - name: string - url: string -} +import { Routes, type NavOption } from "../shared/types"; export const NavOptions: NavOption[] = [ { - - name: 'Proposals', - url: 'proposals' + name: 'Configure Network', + url: Routes.ConfigureNetwork }, { - name: 'Activity', - url: 'activity' + + name: 'Current Proposal', + url: Routes.CurrentProposal }, { - name: 'Edit chain', - url: 'edit-chain' + name: 'Create Proposal', + url: Routes.CreateProposal } ] diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte @@ -1,47 +1,23 @@ <script lang="ts"> - import { getContract, parseEther, stringToHex } from 'viem' + import { parseEther } from 'viem' import { walletClient, publicClient } from '../client' import Nav from '../components/Nav.svelte' import '../app.css' - import { PUBLIC_ERC20_CONTRACT_ADDRESS, PUBLIC_VOTE_CONTRACT_ADDRESS } from '$env/static/public' - import { voteContractAbi } from '../shared/token-vote-abi' import { configuredChain, connectionDetails } from '../store' const sendTransaction = async () => { if (!$connectionDetails.userAddress || !window.ethereum) return - await walletClient($configuredChain).sendTransaction({ + const hash = await walletClient($configuredChain).sendTransaction({ account: $connectionDetails.userAddress, - to: '0x0000000000000000000000000000000000000000', - value: parseEther('0.000001') + to: '0x30897b75eFaFa279059B241aF56998672cc1E7d9', + value: parseEther('1') }) - } - - const createProposal = async () => { - const description = stringToHex('Testing proposal creation', { size: 32 }) - const blockWait = 100 - const targetVote = 500000 - const { request } = await publicClient($configuredChain).simulateContract({ - address: PUBLIC_VOTE_CONTRACT_ADDRESS as `0x${string}`, - abi: voteContractAbi, - functionName: 'propose', - args: [description, BigInt(blockWait), targetVote], - account: $connectionDetails.userAddress - }) - const result = await walletClient($configuredChain).writeContract(request) - console.log(result) + const receipt = await publicClient($configuredChain).waitForTransactionReceipt({ + hash + }) + console.log(receipt) } - - const getCurrentProposal = async () => { - const contract = getContract({ - address: PUBLIC_ERC20_CONTRACT_ADDRESS as `0x${string}`, - publicClient: publicClient($configuredChain), - walletClient: walletClient($configuredChain), - abi: voteContractAbi - }) - const result = await contract.read.getProposal([BigInt(1)]) - console.log(result) - } </script> <svelte:head> @@ -53,10 +29,7 @@ </svelte:head> <Nav /> -{#if $connectionDetails.isConnected} - <!-- <button class="btn mt-1" on:click={sendTransaction}>Send transaction</button> - <button class="btn mt-1" on:click={createProposal}>Create proposal</button> - <button class="btn mt-1" on:click={getCurrentProposal}>Get current proposal</button> --> -{/if} -<slot /> +<div class="m-5"> + <slot /> +</div> diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte @@ -1,18 +1,20 @@ <script lang="ts"> - import { onMount } from 'svelte' - import { publicClient } from '../client' - import NetworkForm from '../components/NetworkForm.svelte' - import { configuredChain } from '../store' - - let connectedChainId: number; - - onMount(() => { - publicClient($configuredChain).getChainId() - .then(chainId => connectedChainId = chainId) - }) - + import { Routes } from "../shared/types" </script> -<div class="flex w-full min-h-screen"> - + +<div class="flex flex-row items-center w-full mt-20"> + <div class="hero"> + <div class="hero-content text-center"> + <div class="max-w-xl"> + <h1 class="text-5xl font-bold">Welcome to Token Vote</h1> + <p class="py-6">This Dapp enables to vote on propositions with zero or more options using ERC20 tokens.</p> + <div class="flex justify-center gap-5 items-center"> + <a href={Routes.ConfigureNetwork} class="btn btn-sm text-white normal-case btn-secondary">Configure Network</a> + <a href="/create-proposal" class="btn btn-sm text-white normal-case btn-secondary">Create Proposal</a> + <a href="/current-proposal" class="btn btn-sm text-white normal-case btn-secondary">View Current Proposal</a> + </div> + </div> + </div> + </div> </div> \ No newline at end of file diff --git a/src/routes/create-proposal/+page.svelte b/src/routes/create-proposal/+page.svelte @@ -0,0 +1,7 @@ +<script lang="ts"> + import ProposalForm from "../../components/ProposalForm.svelte" + +</script> +<div class="flex w-full"> + <ProposalForm /> +</div> +\ No newline at end of file diff --git a/src/routes/current-proposal/+page.svelte b/src/routes/current-proposal/+page.svelte @@ -0,0 +1,48 @@ +<script lang="ts"> + import { getContract, hexToString } from 'viem' + import { onMount } from 'svelte' + import { walletClient, publicClient } from '../../client' + import { PUBLIC_VOTE_CONTRACT_ADDRESS } from '$env/static/public' + import { voteContractAbi } from '../../shared/token-vote-abi' + import { configuredChain } from '../../store' + import { Routes, type Proposal } from '../../shared/types' + import ProposalView from '../../components/ProposalView.svelte' + + let currentProposal: Proposal | undefined + let loading: boolean = true + + const getCurrentProposal = async () => { + const contract = getContract({ + address: PUBLIC_VOTE_CONTRACT_ADDRESS as `0x${string}`, + publicClient: publicClient($configuredChain), + walletClient: walletClient($configuredChain), + abi: voteContractAbi + }) + const result = await contract.read.getCurrentProposal() + return result + } + + onMount(async () => { + try { + currentProposal = await getCurrentProposal() + } catch (error) { + currentProposal = undefined + console.error(error) + } + loading = false + }) +</script> + +<div class="flex w-full justify-center mt-10"> + {#if loading && !currentProposal} + <span class="loading loading-spinner loading-lg"></span> + {:else if currentProposal} + <ProposalView proposal={currentProposal} /> + {:else} + <div class="flex flex-col gap-5"> + <p>Current contract not found</p> + <a href={Routes.CreateProposal} class="btn btn-sm text-white normal-case btn-primary ">Create proposal</a> + </div> + {/if} + +</div> +\ No newline at end of file diff --git a/src/routes/edit-chain/+page.svelte b/src/routes/edit-chain/+page.svelte @@ -2,6 +2,6 @@ import NetworkForm from '../../components/NetworkForm.svelte' </script> -<div class="flex w-full min-h-screen"> +<div class="flex w-full"> <NetworkForm /> </div> \ No newline at end of file diff --git a/src/shared/types.ts b/src/shared/types.ts @@ -13,4 +13,41 @@ export interface VoteToken { name: string symbol: string decimals: number -} -\ No newline at end of file +} + +export interface Proposal { + description: `0x${string}`; + options: readonly `0x${string}`[]; + optionVotes: readonly bigint[]; + cancelVotes: bigint; + supply: bigint; + total: bigint; + blockDeadline: bigint; + targetVotePpm: number; + proposer: `0x${string}`; + state: number; + scanCursor: number; +} + +export enum ProposalState { + STATE_INIT = 1, + STATE_FINAL = 2, + STATE_SCANNED = 4, + STATE_INSUFFICIENT = 8, + STATE_TIED = 16, + STATE_SUPPLYCHANGE = 32, + STATE_IMMEDIATE = 64, + STATE_CANCELLED = 128 +} + +export enum Routes { + Home = '/', + ConfigureNetwork = '/edit-chain', + CurrentProposal = '/current-proposal', + CreateProposal = '/create-proposal' +} + +export type NavOption = { + name: string + url: Routes +} diff --git a/src/shared/wala.ts b/src/shared/wala.ts @@ -0,0 +1,26 @@ +import { PUBLIC_WALA_URL } from "$env/static/public"; + +export const uploadTextToWala = async (text: string) => { + try { + const response = await fetch(PUBLIC_WALA_URL, { + method: 'PUT', + headers: { + 'Content-Type': 'text/plain' + }, + body: text + }) + return response.text() + } catch (error) { + console.error(error) + } +} + +export const getText = async (hash: string) => { + try { + const h = hash.startsWith('0x') ? hash.substring(2) : hash + const response = await fetch(`${PUBLIC_WALA_URL}/${h}`) + return response.text() + } catch (error) { + console.error(error) + } +} diff --git a/src/utils/copy-clipboard.ts b/src/utils/copy-clipboard.ts @@ -0,0 +1,3 @@ +export const copyToClipboard = async (text: string) => { + await navigator.clipboard.writeText(text) +} diff --git a/src/utils/proposal.ts b/src/utils/proposal.ts @@ -0,0 +1,29 @@ +import { ProposalState } from "../shared/types"; + +export const getProposalStateDescription = (proposalState: number) => { + switch(proposalState) { + case ProposalState.STATE_INIT: + return 'Proposal initiated' + + case ProposalState.STATE_FINAL: + return 'Proposal finalized' + + case ProposalState.STATE_SCANNED: + return 'Proposal votes have been scanned ' + + case ProposalState.STATE_INSUFFICIENT: + return 'proposal did not attract minimum participation before deadline' + + case ProposalState.STATE_TIED: + return 'Two or more proposal options have the same amount of votes' + + case ProposalState.STATE_SUPPLYCHANGE: + return 'Supply changed while voting was underway' + + case ProposalState.STATE_CANCELLED: + return 'Vote to cancel the proposal has the majority' + + default: + return '' + } +} +\ No newline at end of file