From b2c82b06bdada4e33bdb06a6c56afeb6038fe19e Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Wed, 21 Dec 2022 17:44:47 +0100 Subject: [PATCH] Improve vesting discovery and error messages When loading a ETH address as vesting contract we show a cryptic error message, which non tech-savy end users don't understand, this commit changes it to something more human readable. We allow users to enter and load a contract that probably isn't a vesting contract. The `getInfo` function in the `vesting` folder is very prone to possible errors, and benefits from some parallelization between all the method calls. We allow hereby to enter a contract address in search and once on the contract profile page we provide a link to the detailed vesting information if available. Signed-off-by: Sebastian Martinez --- src/App.svelte | 2 +- src/components/Address.svelte | 44 ++------ src/components/Badge.svelte | 1 + src/components/Icon.svelte | 164 +++++++++++++++++++++--------- src/components/TextInput.svelte | 2 +- src/lib/utils.ts | 20 +++- src/lib/vesting.ts | 161 +++++++++++++++++++++-------- src/views/profiles/Profile.svelte | 141 +++++++++++++++++++++++-- src/views/vesting/Form.svelte | 31 +++--- src/views/vesting/Routes.svelte | 12 +-- src/views/vesting/View.svelte | 160 ----------------------------- src/views/vesting/Withdraw.svelte | 72 +++++++++++++ 12 files changed, 489 insertions(+), 321 deletions(-) delete mode 100644 src/views/vesting/View.svelte create mode 100644 src/views/vesting/Withdraw.svelte diff --git a/src/App.svelte b/src/App.svelte index e51f0dbfb..0bdaf9b5e 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -105,7 +105,7 @@ session={$session} activeRoute={$activeRouteStore} /> {:else if $activeRouteStore.resource === "vesting"} - + {:else if $activeRouteStore.resource === "projects"} {:else if $activeRouteStore.resource === "profile"} diff --git a/src/components/Address.svelte b/src/components/Address.svelte index ac7f4cb3b..8db8c5676 100644 --- a/src/components/Address.svelte +++ b/src/components/Address.svelte @@ -9,7 +9,6 @@ import Link from "@app/components/Link.svelte"; import { AddressType, - explorerLink, formatAddress, identifyAddress, parseEnsLabel, @@ -65,12 +64,6 @@ align-items: center; height: 100%; } - .address a { - color: var(--color-foreground-6); - } - .address a:hover { - color: var(--color-foreground); - } .highlight { color: var(--color-foreground-6); font-weight: var(--font-weight-bold); @@ -96,37 +89,18 @@ {/if} {/if}
- {#if addressType === AddressType.Org} - - {addressLabel} - - {#if !noBadge} + + {addressLabel} + + + {#if !noBadge} + {#if addressType === AddressType.Org} org - {/if} - {:else if addressType === AddressType.Contract} - - {addressLabel} - - {#if !noBadge} + {:else if addressType === AddressType.Vesting} + vesting contract + {:else if addressType === AddressType.Contract} contract {/if} - {:else if addressType === AddressType.EOA} - - {addressLabel} - - {:else} - - - {addressLabel} - {/if}
diff --git a/src/components/Badge.svelte b/src/components/Badge.svelte index ea0cad990..975ec64c9 100644 --- a/src/components/Badge.svelte +++ b/src/components/Badge.svelte @@ -16,6 +16,7 @@ line-height: 1.6; height: var(--button-tiny-height); display: flex; + white-space: nowrap; } .foreground { color: var(--color-foreground-6); diff --git a/src/components/Icon.svelte b/src/components/Icon.svelte index 376e8ec07..e43a57c9d 100644 --- a/src/components/Icon.svelte +++ b/src/components/Icon.svelte @@ -15,6 +15,7 @@ | "gear" | "chevron-down" | "chevron-up" + | "etherscan" | "url"; @@ -59,6 +60,42 @@ 14.7859 16.9725C14.8779 16.9315 14.9607 16.8724 15.0293 16.7987L19.2793 12.5487C19.4198 12.4081 19.4987 12.2175 19.4987 12.0187C19.4987 11.82 19.4198 11.6293 19.2793 11.4887L15.0293 7.23871V7.23871Z" /> + {:else if name === "etherscan"} + {:else if name === "clipboard"} {:else if name === "github"} + clip-rule="evenodd" + fill-rule="evenodd" + d="m7.3295 4.5202c-0.26315-0.090455-0.55523 0.13567-0.52819 + 0.41659l0.22799 3.0462c-0.34577 0.3331-1.0298 1.2253-1.0298 2.873 0 1.7391 + 0.87721 2.9043 2.1775 3.6113 0.30568 0.16625 0.63459 0.3073 0.98108 + 0.42571l-0.49322 + 0.9844-1.2994-0.10729c-0.4524-0.037363-0.87865-0.22746-1.2087-0.53908l-1.0828-1.0224c-0.16064-0.15169-0.41382-0.14441-0.5655 + 0.01616-0.15168 0.16065-0.14442 0.41387 0.016209 0.56548l1.0828 1.0225c0.46207 + 0.43628 1.0588 0.70246 1.6922 0.7547l0.97925 0.08089c-0.18373 0.37987-0.27927 + 0.79662-0.27927 1.219v0.63245c0 0.2209-0.1791 0.40003-0.40003 + 0.40003h-0.40003c-0.22093 0-0.40003 0.17905-0.40003 0.40003 0 0.2209 0.1791 + 0.40003 0.40003 0.40003h0.40003c0.6628 0 1.2001-0.53732 + 1.2001-1.2001v-0.63245c0-0.31106 0.072566-0.61789 + 0.21192-0.89599l0.92927-1.8546c0.40628 0.09553 0.82889 0.16585 1.2603 + 0.21426l-0.3999 4.7353c-0.01859 0.2201 0.1448 0.41363 0.36495 0.43228 0.22016 + 0.01856 0.4137-0.14481 0.43226-0.36499l0.40003-4.7369 8e-5 -9.6e-4c0.53228 + 0.02768 1.0706 0.02768 1.6028 0l8e-5 9.6e-4 0.40003 4.7369c0.01856 0.22018 + 0.2121 0.38355 0.43228 0.36499 0.2201-0.01864 0.38355-0.21218 + 0.36491-0.43228l-0.39987-4.7353c0.39443-0.04424 0.7815-0.10681 + 1.1555-0.19026l0.99776 1.8113c0.16289 0.29554 0.24826 0.62757 0.24826 + 0.96504v0.58277c0 0.66277 0.53732 1.2001 1.2001 1.2001h0.40003c0.2209 0 + 0.40003-0.17914 0.40003-0.40003 + 0-0.22098-0.17913-0.40003-0.40003-0.40003h-0.40003c-0.2209 + 0-0.40003-0.17913-0.40003-0.40003v-0.58277c0-0.47244-0.11953-0.93728-0.34755-1.3511l-0.90431-1.6416c0.38083-0.12465 + 0.74142-0.27586 1.0745-0.45692 1.3003-0.70702 2.1775-1.8722 2.1775-3.6113 + 0-1.6477-0.68398-2.5399-1.0298-2.873l0.22794-3.0454c0.027042-0.27529-0.26266-0.50894-0.52844-0.41737l-3.0828 + 0.98573c-0.74062-0.18142-1.5924-0.27064-2.3875-0.27064-0.79502 0-1.6469 + 0.089215-2.3874 0.27064zm0.5121 3.5965c0.012201 0.12941-0.037451 + 0.25373-0.13439 0.3386-0.1738 0.1528-0.32207 0.31325-0.44444 0.51351-0.22285 + 0.36466-0.46318 0.96791-0.46318 1.8872 0 3.1088 3.5475 3.7601 6.0005 3.7601 + 2.4529 0 6.0005-0.65125 6.0005-3.7601 + 0-1.7223-0.81855-2.3326-0.87087-2.372-0.11889-0.082175-0.18474-0.22163-0.17121-0.36685l0.1989-2.6568-2.6353 + 0.8426c-0.07233 0.023138-0.14977 0.025194-0.2233 + 0.00593-0.68854-0.1805-1.5179-0.27343-2.2987-0.27343s-1.6102 0.092928-2.2987 + 0.27343c-0.07348 0.019266-0.15092 0.017209-0.22327-0.00593l-2.6353-0.8426z" /> {:else if name === "moon"} {:else if name === "url"} + clip-rule="evenodd" + fill-rule="evenodd" + d="m12.671 11.04c-0.67865-0.74752-1.0922-1.7401-1.0922-2.8293 0-2.3254 + 1.8851-4.2105 4.2105-4.2105 2.3254 0 4.2105 1.8851 4.2105 4.2105 0 + 2.3254-1.8851 4.2105-4.2105 4.2105-0.93263 + 0-1.7944-0.30324-2.4921-0.81642l-1.6927 1.6927c0.51318 0.69777 0.81642 1.5595 + 0.81642 2.4921 0 2.3254-1.8851 4.2105-4.2105 4.2105-2.3254 + 0-4.2105-1.8851-4.2105-4.2105 0-2.3254 1.8851-4.2105 4.2105-4.2105 1.0892 0 + 2.0818 0.41356 2.8293 1.0922zm-0.2501-2.8293c0-1.8603 1.5081-3.3684 + 3.3684-3.3684 1.8603 0 3.3684 1.5081 3.3684 3.3684 0 1.8603-1.5081 + 3.3684-3.3684 3.3684-0.69962 + 0-1.3495-0.2133-1.888-0.57844l1.2291-1.2291c0.16446-0.16443 0.16446-0.43102 + 0-0.59545-0.16438-0.16444-0.43099-0.16444-0.59545 0l-1.2675 + 1.2676c-0.52674-0.59443-0.84648-1.3764-0.84648-2.233zm-3.2451 6.3245 + 1.2676-1.2675c-0.59443-0.52674-1.3764-0.84648-2.233-0.84648-1.8603 0-3.3684 + 1.5081-3.3684 3.3684 0 1.8603 1.5081 3.3684 3.3684 3.3684 1.8603 0 + 3.3684-1.5081 3.3684-3.3684 0-0.69962-0.2133-1.3495-0.57844-1.888l-1.2291 + 1.2291c-0.16443 0.16446-0.43102 0.16446-0.59545 + 0-0.16444-0.16438-0.16444-0.43099 0-0.59545z" /> {:else if name === "checkmark"} {:else if name === "twitter"} + clip-rule="evenodd" + fill-rule="evenodd" + d="m5.5228 5.4809c0.13886 0.00675 0.26343 0.087463 0.32634 0.21144 1.08 + 2.1284 2.6203 3.5913 4.862 4.2088v-0.47702c0-1.8169 1.4729-3.2898 + 3.2898-3.2898 1.1961 0 2.2689 0.56288 2.8469 1.5482h2.7652c0.16263 0 0.30793 + 0.1017 0.36366 0.25453 0.05566 0.15283 0.0098 0.32417-0.11464 0.42881l-2.5734 + 2.1625c-0.04079 1.6914-0.6455 3.5129-1.7259 4.9881-1.2652 1.7277-3.2027 + 3.0031-5.6713 3.0031-2.4028 + 0-3.8426-0.92007-4.6769-1.8739-0.41221-0.47133-0.66961-0.94259-0.82448-1.2974-0.077555-0.17765-0.12983-0.3272-0.16319-0.43441-0.016689-0.05364-0.028687-0.09676-0.036769-0.12772-0.00405-0.01548-0.00711-0.02794-0.0093-0.03716l-0.00265-0.0113-8.515e-4 + -0.0038-3.096e-4 -0.0014-1.316e-4 -6.19e-4c-5.42e-5 -2.33e-4 -1.084e-4 + -4.65e-4 0.3781-0.08267l-0.37821 0.08221c-0.024863-0.11441 0.00341-0.23393 + 0.076873-0.32511 0.073475-0.09111 0.18426-0.14413 + 0.30134-0.14413h2.3072c-0.87844-0.64588-1.7455-1.5204-2.2917-2.612-0.80618-1.6109-0.88662-3.6435 + 0.60706-5.9904 0.074644-0.11729 0.20646-0.18553 0.34532-0.17877zm-0.42433 + 9.5552c3.561e-4 7.74e-4 7.044e-4 0.0016 0.00106 0.0024 0.13017 0.29833 0.34822 + 0.69791 0.69767 1.0974 0.68907 0.78785 1.9174 1.6095 4.0942 1.6095 2.1745 0 + 3.8969-1.1162 5.0467-2.6864 1.0242-1.3984 1.5785-3.1389 1.5785-4.713 0-0.11431 + 0.05055-0.22277 0.13802-0.29631l1.8961-1.5933h-1.9347c-0.14847 + 0-0.28385-0.084955-0.34849-0.21865-0.39749-0.82249-1.2464-1.3295-2.2666-1.3295-1.3894 + 0-2.5157 1.1263-2.5157 2.5157v0.96759c0 0.21372-0.17332 0.38704-0.38704 + 0.38704-0.01664 + 0-0.03298-0.0011-0.04908-0.0031-0.01757-0.0013-0.03522-0.0039-0.05295-0.0077-2.5186-0.54599-4.2771-2.0072-5.5042-4.1128-0.9927 + 1.8701-0.84393 3.4204-0.22929 4.6486 0.6925 1.3838 2.004 2.4102 3.1211 3.0036 + 0.15696 0.08337 0.23697 0.26295 0.19401 0.43534-0.042953 0.17246-0.19784 + 0.29353-0.37557 0.29353z" /> {:else} {unreachable(name)} {/if} diff --git a/src/components/TextInput.svelte b/src/components/TextInput.svelte index f0af1d5fe..0db7ce397 100644 --- a/src/components/TextInput.svelte +++ b/src/components/TextInput.svelte @@ -31,7 +31,7 @@ }); function handleKeydown(event: KeyboardEvent) { - if (event.key === "Enter") { + if (event.key === "Enter" && valid) { dispatch("submit"); } } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index ffc9d62c7..dae6d88b7 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -16,10 +16,12 @@ import { base } from "@app/lib/router"; import { config } from "@app/lib/config"; import { getAddress, getResolver } from "@app/lib/registrar"; import { getAvatar, getSeed, getRegistration } from "@app/lib/registrar"; +import { getInfo } from "@app/lib/vesting"; export enum AddressType { Contract, Org, + Vesting, EOA, } @@ -127,6 +129,11 @@ export function formatAddress(input: string): string { ); } +// Returns a shortened Ethereum transaction hash +export function formatTx(input: string): string { + return input.substring(0, 20) + "…"; +} + export function formatCommit(oid: string): string { return oid.substring(0, 7); } @@ -288,12 +295,13 @@ export function isFulfilled( return input.status === "fulfilled"; } -// Get the explorer link of an address, eg. Etherscan. -export function explorerLink(addr: string, wallet: Wallet): string { +// Get the explorer link of an address or tx, eg. Etherscan. +export function explorerLink(addrOrTx: string, wallet: Wallet): string { + const type = isAddress(addrOrTx) ? "address" : "tx"; if (wallet.network.name === "goerli") { - return `https://goerli.etherscan.io/address/${addr}`; + return `https://goerli.etherscan.io/${type}/${addrOrTx}`; } - return `https://etherscan.io/address/${addr}`; + return `https://etherscan.io/${type}/${addrOrTx}`; } // Format a name. @@ -349,6 +357,10 @@ export async function identifyAddress( const bytes = ethers.utils.arrayify(code); if (bytes.length > 0) { + const info = await getInfo(address, wallet); + if (info) { + return AddressType.Vesting; + } return AddressType.Contract; } return AddressType.EOA; diff --git a/src/lib/vesting.ts b/src/lib/vesting.ts index d0115142d..792e8efe9 100644 --- a/src/lib/vesting.ts +++ b/src/lib/vesting.ts @@ -1,12 +1,13 @@ import type { Wallet } from "@app/lib/wallet"; +import type { TransactionResponse } from "@ethersproject/abstract-provider"; -import { ethers } from "ethers"; +import { BigNumber, ethers } from "ethers"; import { writable } from "svelte/store"; +import * as cache from "@app/lib/cache"; import * as session from "@app/lib/session"; import * as utils from "@app/lib/utils"; import ethereumContractAbis from "@app/lib/ethereum/contractAbis.json"; -import { assert } from "@app/lib/error"; export interface VestingInfo { token: string; @@ -20,15 +21,24 @@ export interface VestingInfo { vestingPeriod: string; } -export const state = writable< - "idle" | "loading" | "withdrawingSign" | "withdrawing" | "withdrawn" ->("idle"); +export type VestingState = + | { type: "idle" } + | { type: "loading" } + | { type: "error"; error: string } + | { type: "withdrawingSign" } + | { type: "withdrawing"; tx: TransactionResponse } + | { type: "withdrawn" }; + +export const state = writable({ type: "idle" }); export async function withdrawVested( address: string, wallet: Wallet, ): Promise { - assert(wallet.signer); + if (!wallet.signer) { + state.set({ type: "error", error: "No signer available" }); + return; + } const contract = new ethers.Contract( address, @@ -37,50 +47,119 @@ export async function withdrawVested( ); const signer = wallet.signer; - state.set("withdrawingSign"); + state.set({ type: "withdrawingSign" }); - const tx = await contract.connect(signer).withdrawVested(); + try { + const tx: TransactionResponse = await contract + .connect(signer) + .withdrawVested(); - state.set("withdrawing"); - await tx.wait(); + state.set({ type: "withdrawing", tx }); + await tx.wait(); + } catch (e) { + handleEtherErrorState(e, "Unknown error, check the dev console"); + return; + } session.state.refreshBalance(wallet); - state.set("withdrawn"); + state.set({ type: "withdrawn" }); } -export async function getInfo( - address: string, - wallet: Wallet, -): Promise { - const contract = new ethers.Contract( +export const getInfo = cache.cached( + async (address: string, wallet: Wallet): Promise => { + const contract = getVestingContract(address, wallet); + + let vestingInfo: + | [ + string, + string, + BigNumber, + BigNumber, + BigNumber, + string, + string, + string, + ] + | undefined = undefined; + let token: string | undefined = undefined; + + try { + token = await contract.token(); + if (!token) { + return undefined; + } + + const tokenContract = new ethers.Contract( + token, + ethereumContractAbis.token, + wallet.provider, + ); + + vestingInfo = await Promise.all([ + tokenContract.symbol(), + contract.beneficiary(), + contract.withdrawableBalance(), + contract.withdrawn(), + contract.totalVestingAmount(), + contract.vestingStartTime(), + contract.vestingPeriod(), + contract.cliffPeriod(), + ]); + } catch (e) { + console.warn(e); + return undefined; + } + + const [ + symbol, + beneficiary, + withdrawable, + withdrawn, + total, + vestingStartTime, + vestingPeriod, + cliffPeriod, + ] = vestingInfo; + + return { + token, + symbol, + beneficiary, + withdrawableBalance: utils.formatBalance(withdrawable), + withdrawn: utils.formatBalance(withdrawn), + totalVesting: utils.formatBalance(total), + vestingStartTime, + vestingPeriod, + cliffPeriod, + }; + }, + address => address, + { max: 1000 }, +); + +export function parseVestingPeriods(...timestamps: string[]): string { + const sum = timestamps + .map(timestamp => parseInt(timestamp)) + .reduce((prev, curr) => prev + curr, 0); + return new Date(sum * 1000).toDateString(); +} + +export function getVestingContract(address: string, wallet: Wallet) { + return new ethers.Contract( address, ethereumContractAbis.vesting, wallet.provider, ); - const token = await contract.token(); - const beneficiary = await contract.beneficiary(); - const withdrawable = await contract.withdrawableBalance(); - const withdrawn = await contract.withdrawn(); - const total = await contract.totalVestingAmount(); - const vestingStartTime = await contract.vestingStartTime(); - const vestingPeriod = await contract.vestingPeriod(); - const cliffPeriod = await contract.cliffPeriod(); +} - const tokenContract = new ethers.Contract( - token, - ethereumContractAbis.token, - wallet.provider, - ); - const symbol = await tokenContract.symbol(); +export function handleEtherErrorState(e: unknown, message: string): void { + const error = + typeof e === "object" && + e !== null && + "reason" in e && + typeof e.reason === "string" + ? e.reason + : message; - return { - token, - symbol, - beneficiary, - totalVesting: utils.formatBalance(total), - withdrawableBalance: utils.formatBalance(withdrawable), - withdrawn: utils.formatBalance(withdrawn), - vestingStartTime, - vestingPeriod, - cliffPeriod, - }; + state.set({ type: "error", error }); + console.warn(e); } diff --git a/src/views/profiles/Profile.svelte b/src/views/profiles/Profile.svelte index 8aaea4672..68cc5a256 100644 --- a/src/views/profiles/Profile.svelte +++ b/src/views/profiles/Profile.svelte @@ -3,6 +3,8 @@ import type { Wallet } from "@app/lib/wallet"; import type { Seed, Stats } from "@app/lib/seed"; import type { ProjectInfo } from "@app/lib/project"; + import type { VestingInfo } from "@app/lib/vesting"; + import * as utils from "@app/lib/utils"; import Address from "@app/components/Address.svelte"; import Async from "@app/components/Async.svelte"; @@ -18,20 +20,31 @@ import RadicleId from "@app/components/RadicleId.svelte"; import SeedAddress from "@app/components/SeedAddress.svelte"; import SetName from "./SetName.svelte"; + import Withdraw from "@app/views/vesting/Withdraw.svelte"; import { MissingReverseRecord, NotFoundError } from "@app/lib/error"; import { User, Profile, ProfileType } from "@app/lib/profile"; import { defaultNodePort } from "@app/lib/seed"; + import { + getInfo, + getVestingContract, + handleEtherErrorState, + parseVestingPeriods, + } from "@app/lib/vesting"; + import { onMount } from "svelte"; import { session } from "@app/lib/session"; export let wallet: Wallet; export let addressOrName: string; - export let action: string | null = null; - let setNameForm: typeof SvelteComponent | null = - action === "setName" ? SetName : null; + let vestingInfo: VestingInfo | undefined = undefined; + let setNameForm: typeof SvelteComponent | undefined = undefined; + let withdrawVestingModal: typeof SvelteComponent | undefined = undefined; const setName = () => { setNameForm = SetName; }; + const withdrawVesting = () => { + withdrawVestingModal = Withdraw; + }; const getProjectsAndStats = async ( seed: Seed, @@ -45,6 +58,49 @@ return { stats, projects }; }; + // Refresh vestingInfo and close modal if addressOrName changes + $: { + vestingInfo = undefined; + withdrawVestingModal = undefined; + getInfo(addressOrName, wallet) + .then(info => { + vestingInfo = info; + }) + .catch(() => { + console.warn("Not able to get vesting contract info"); + }); + } + + onMount(async () => { + const addressType = await utils.identifyAddress(addressOrName, wallet); + if (addressType === utils.AddressType.Contract) { + try { + vestingInfo = await getInfo(addressOrName, wallet); + } catch (e) { + handleEtherErrorState(e, "Not able to get vesting contract info"); + } + } + }); + + const vestingContract = getVestingContract(addressOrName, wallet); + + wallet.provider.on("block", async () => { + if (vestingInfo) { + try { + const updatedAmounts = await Promise.all([ + vestingContract.withdrawableBalance(), + vestingContract.withdrawn(), + ]); + vestingInfo.withdrawableBalance = utils.formatBalance( + updatedAmounts[0], + ); + vestingInfo.withdrawn = utils.formatBalance(updatedAmounts[1]); + } catch (e) { + handleEtherErrorState(e, "Not able to update the balance"); + } + } + }); + $: isUserAuthorized = (address: string): boolean | null => { return $session && utils.isAddressEqual(address, $session.address); }; @@ -75,7 +131,7 @@ } .fields { display: grid; - grid-template-columns: 5rem 4fr 2fr; + grid-template-columns: max-content 4fr 2fr; gap: 1rem 2rem; margin-bottom: 1rem; } @@ -116,7 +172,7 @@ padding: 1.5rem; } .fields { - grid-template-columns: 5rem auto; + grid-template-columns: max-content auto; } } @@ -169,6 +225,14 @@ {/if} + {#if utils.isAddress(profile.address)} + + + + {/if} @@ -255,6 +319,64 @@ {/if} {/if} + {#if vestingInfo} +
Vesting Beneficiary
+
+
+
+
+
Allocation
+
+ {vestingInfo.totalVesting} + {vestingInfo.symbol} +
+
+
Withdrawn
+
+ {vestingInfo.withdrawn} + {vestingInfo.symbol} +
+
+
Withdrawable
+
+ {vestingInfo.withdrawableBalance} + {vestingInfo.symbol} +
+
+ {#if isUserAuthorized(vestingInfo.beneficiary) && parseFloat(vestingInfo.withdrawableBalance) > 0} + + {/if} +
+
Start Time
+
+ + {parseVestingPeriods(vestingInfo.vestingStartTime)} + +
+
+
Cliff Period End
+
+ + {parseVestingPeriods( + vestingInfo.vestingStartTime, + vestingInfo.cliffPeriod, + )} + +
+
+
Vesting Period End
+
+ + {parseVestingPeriods( + vestingInfo.vestingStartTime, + vestingInfo.vestingPeriod, + )} + +
+
+ {/if}
{#if profile.seed?.valid} @@ -268,11 +390,18 @@ {/if} + (withdrawVestingModal = undefined)} /> + (setNameForm = null)} /> + on:close={() => (setNameForm = undefined)} /> {:catch err} {#if err instanceof NotFoundError} { - state.set("loading"); + state.set({ type: "loading" }); try { const info = await getInfo(contractAddress, wallet); - router.push({ - resource: "vesting", - params: { - view: { - resource: "view", - params: { contract: contractAddress, info }, - }, - }, - }); + if (info) { + router.push({ + resource: "profile", + params: { addressOrName: contractAddress }, + }); + } else { + validationMessage = "No vesting account found under this address."; + } } catch (error) { validationMessage = "Couldn't load contract, check dev console for details."; - console.error(error); + console.warn(error); } - state.set("idle"); + state.set({ type: "idle" }); }; @@ -75,16 +74,16 @@ placeholder="Enter vesting contract address" {valid} {validationMessage} - loading={$state === "loading"} - disabled={$state === "loading"} + loading={$state.type === "loading"} + disabled={$state.type === "loading"} on:submit={loadContract} bind:value={contractAddress} />
diff --git a/src/views/vesting/Routes.svelte b/src/views/vesting/Routes.svelte index 0735f133c..664a8057d 100644 --- a/src/views/vesting/Routes.svelte +++ b/src/views/vesting/Routes.svelte @@ -1,22 +1,16 @@ {#if activeRoute.params.view.resource === "form"}
{:else if activeRoute.params.view.resource === "view"} - + {/if} diff --git a/src/views/vesting/View.svelte b/src/views/vesting/View.svelte deleted file mode 100644 index 75979112e..000000000 --- a/src/views/vesting/View.svelte +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - Radicle – Vesting - - -{#if error} - router.pop()} /> -{:else if $state === "loading"} - -{:else if info} - {@const isBeneficiary = - session && utils.isAddressEqual(info.beneficiary, session.address)} - - - {contractAddress} - - - - {#if $state === "withdrawn"} - Tokens successfully withdrawn to {utils.formatAddress( - info.beneficiary, - )}. - {:else} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Beneficiary -
-
Allocation - {info.totalVesting} - {info.symbol} -
Withdrawn - {info.withdrawn} - {info.symbol} -
Withdrawable - {info.withdrawableBalance} - {info.symbol} -
Start Time - - {parseVestingPeriods([info.vestingStartTime])} - -
Cliff Period End - - {parseVestingPeriods([info.vestingStartTime, info.cliffPeriod])} - -
Vesting Period End - - {parseVestingPeriods([ - info.vestingStartTime, - info.vestingPeriod, - ])} - -
- {/if} -
- - - {#if isBeneficiary} - {#if $state === "withdrawingSign"} - - {:else if $state === "withdrawing"} - - {:else if $state === "idle"} - - {/if} - {/if} - - -
-{/if} diff --git a/src/views/vesting/Withdraw.svelte b/src/views/vesting/Withdraw.svelte new file mode 100644 index 000000000..0da7a4790 --- /dev/null +++ b/src/views/vesting/Withdraw.svelte @@ -0,0 +1,72 @@ + + + + +{#if $state.type === "error"} + dispatch("close")} /> +{:else} + + + {utils.formatAddress(contractAddress)} + + + + {#if $state.type === "withdrawingSign"} + Waiting for a signature… + {:else if $state.type === "withdrawing"} + Waiting for confirmation… + {/if} + + + + {#if $state.type === "withdrawn"} + + Tokens have been withdrawn to + {utils.formatAddress(info.beneficiary)} + + + {:else} + + {/if} + + + + + + +{/if}