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:
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