diff --git a/package-lock.json b/package-lock.json index d7bc4fd40..b4e05ee83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "@snowpack/plugin-typescript": "^1.2.1", "ethers": "^5.0.31", "svelte": "^3.32.3", - "svelte-routing": "^1.5.0" + "svelte-routing": "^1.6.0" }, "devDependencies": { "@snowpack/plugin-svelte": "^3.5.2", @@ -852,6 +852,11 @@ "node": ">= 8" } }, + "node_modules/dedent-js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz", + "integrity": "sha1-vuX7fJ5yfYXf+iRZDRDsGrElUwU=" + }, "node_modules/detect-indent": { "version": "6.0.0", "dev": true, @@ -1148,6 +1153,14 @@ "version": "0.5.7", "license": "MIT" }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "license": "MIT" @@ -1191,6 +1204,15 @@ "dev": true, "license": "MIT" }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "dev": true, @@ -1256,6 +1278,15 @@ "node": ">=6" } }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "dev": true, @@ -1527,12 +1558,29 @@ } }, "node_modules/svelte-routing": { - "version": "1.5.0", - "license": "MIT", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/svelte-routing/-/svelte-routing-1.6.0.tgz", + "integrity": "sha512-+DbrSGttLA6lan7oWFz1MjyGabdn3tPRqn8Osyc471ut2UgCrzM5x1qViNMc2gahOP6fKbKK1aNtZMJEQP2vHQ==", + "dependencies": { + "svelte2tsx": "^0.1.157" + }, "peerDependencies": { "svelte": "^3.20.x" } }, + "node_modules/svelte2tsx": { + "version": "0.1.189", + "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.1.189.tgz", + "integrity": "sha512-Mo4Sei1tNYthzSZx6biGSK7pI6/vj7nGvvmSevmLIiws/o1hyj1UIHn+AwqogeA9L46fcvy6WU3t7HxDg+LbLg==", + "dependencies": { + "dedent-js": "^1.0.1", + "pascal-case": "^3.1.1" + }, + "peerDependencies": { + "svelte": "^3.24", + "typescript": "^4.1.2" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "dev": true, @@ -1544,6 +1592,11 @@ "node": ">=8.0" } }, + "node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + }, "node_modules/typescript": { "version": "4.2.4", "license": "Apache-2.0", @@ -2033,6 +2086,11 @@ "which": "^2.0.1" } }, + "dedent-js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz", + "integrity": "sha1-vuX7fJ5yfYXf+iRZDRDsGrElUwU=" + }, "detect-indent": { "version": "6.0.0", "dev": true @@ -2222,6 +2280,14 @@ "js-sha3": { "version": "0.5.7" }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "requires": { + "tslib": "^2.0.3" + } + }, "merge-stream": { "version": "2.0.0" }, @@ -2249,6 +2315,15 @@ "version": "1.2.5", "dev": true }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "normalize-path": { "version": "3.0.0", "dev": true @@ -2287,6 +2362,15 @@ "callsites": "^3.0.0" } }, + "pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "path-is-absolute": { "version": "1.0.1", "dev": true @@ -2416,8 +2500,21 @@ } }, "svelte-routing": { - "version": "1.5.0", - "requires": {} + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/svelte-routing/-/svelte-routing-1.6.0.tgz", + "integrity": "sha512-+DbrSGttLA6lan7oWFz1MjyGabdn3tPRqn8Osyc471ut2UgCrzM5x1qViNMc2gahOP6fKbKK1aNtZMJEQP2vHQ==", + "requires": { + "svelte2tsx": "^0.1.157" + } + }, + "svelte2tsx": { + "version": "0.1.189", + "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.1.189.tgz", + "integrity": "sha512-Mo4Sei1tNYthzSZx6biGSK7pI6/vj7nGvvmSevmLIiws/o1hyj1UIHn+AwqogeA9L46fcvy6WU3t7HxDg+LbLg==", + "requires": { + "dedent-js": "^1.0.1", + "pascal-case": "^3.1.1" + } }, "to-regex-range": { "version": "5.0.1", @@ -2426,6 +2523,11 @@ "is-number": "^7.0.0" } }, + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + }, "typescript": { "version": "4.2.4" }, diff --git a/package.json b/package.json index 7c889af92..5418d9edf 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,6 @@ "@snowpack/plugin-typescript": "^1.2.1", "ethers": "^5.0.31", "svelte": "^3.32.3", - "svelte-routing": "^1.5.0" + "svelte-routing": "^1.6.0" } } diff --git a/src/Header.svelte b/src/Header.svelte index 11f0bf42a..92e42c6f1 100644 --- a/src/Header.svelte +++ b/src/Header.svelte @@ -12,11 +12,12 @@ import type { Session } from '@app/session'; import Logo from './Logo.svelte'; import Connect from './Connect.svelte'; + import type { Config } from '@app/config'; export let session: Session | null; - export let config; + export let config: Config; - let sessionButton = null; + let sessionButton: HTMLElement | null = null; let sessionButtonHover = false; $: address = session && session.address; diff --git a/src/Modal.svelte b/src/Modal.svelte index b7a370109..b8433924f 100644 --- a/src/Modal.svelte +++ b/src/Modal.svelte @@ -2,7 +2,7 @@ import { createEventDispatcher } from 'svelte'; export let floating = false; - export let error = null; + export let error: Error | null = null; let dispatch = createEventDispatcher(); diff --git a/src/base/orgs/CreateOrg.svelte b/src/base/orgs/CreateOrg.svelte index ab209fd00..b4521a666 100644 --- a/src/base/orgs/CreateOrg.svelte +++ b/src/base/orgs/CreateOrg.svelte @@ -15,8 +15,8 @@ } let state = State.Idle; - let error = null; - let org = null; + let error: Error | null = null; + let org: Org | null = null; const dispatch = createEventDispatcher(); const createOrg = async () => { diff --git a/src/base/orgs/Org.ts b/src/base/orgs/Org.ts index b4246864b..e0b2b6c65 100644 --- a/src/base/orgs/Org.ts +++ b/src/base/orgs/Org.ts @@ -20,12 +20,16 @@ export class Org { this.safe = safe; } - static fromReceipt(receipt: ContractReceipt): Org { - let event = receipt.events.find(e => e.event === 'OrgCreated'); - let address = event.args[0]; - let safe = event.args[1]; + static fromReceipt(receipt: ContractReceipt): Org | null { + let event = receipt.events?.find(e => e.event === 'OrgCreated'); - return new Org(address, safe); + if (event && event.args) { + let address = event.args[0]; + let safe = event.args[1]; + + return new Org(address, safe); + } + return null; } static async create( diff --git a/src/base/orgs/Orgs.svelte b/src/base/orgs/Orgs.svelte index 8e39b299e..c62c23826 100644 --- a/src/base/orgs/Orgs.svelte +++ b/src/base/orgs/Orgs.svelte @@ -6,12 +6,13 @@ import { error } from '@app/error'; import { session } from '@app/session'; import CreateOrg from '@app/base/orgs/CreateOrg.svelte'; + import type { Config } from '@app/config'; enum State { Idle, } - export let config; + export let config: Config; export const query = {}; let modal = null; diff --git a/src/base/orgs/Routes.svelte b/src/base/orgs/Routes.svelte index f1a910dc7..f60907345 100644 --- a/src/base/orgs/Routes.svelte +++ b/src/base/orgs/Routes.svelte @@ -1,8 +1,9 @@ diff --git a/src/base/register/registrar.ts b/src/base/register/registrar.ts index fa05e3030..58d90502a 100644 --- a/src/base/register/registrar.ts +++ b/src/base/register/registrar.ts @@ -1,9 +1,11 @@ // TODO: Show "look at your wallet" / "confirm tx" before state change. // TODO: Two registration actions with same label -import { ethers } from "ethers"; +import { ethers } from 'ethers'; +import type { BigNumber } from 'ethers'; import { State, state } from './state'; import * as session from '@app/session'; import { Failure } from '@app/error'; +import type { Config } from '@app/config'; const registrarAbi = [ {"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"commitment","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"CommitmentMade","type":"event"}, @@ -20,15 +22,15 @@ const registrarAbi = [ {"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"valid","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"} ]; -export function registrar(config) { +export function registrar(config: Config) { return new ethers.Contract(config.registrar.address, registrarAbi, config.provider); } -export async function registrationFee(config) { +export async function registrationFee(config: Config) { return await registrar(config).registrationFeeRad(); } -export async function registerName(name, owner, config) { +export async function registerName(name: string, owner: string, config: Config) { if (! name) return; let commitmentJson = window.localStorage.getItem('commitment'); @@ -47,14 +49,14 @@ export async function registerName(name, owner, config) { } } -async function approveRegistrar(owner, config) { +async function approveRegistrar(owner: string, config: Config) { state.set(State.Approving); const amount = await registrationFee(config); await session.approveSpender(config.registrar.address, amount, config); } -async function commitAndRegister(name, owner, config) { +async function commitAndRegister(name: string, owner: string, config: Config) { let salt = ethers.utils.randomBytes(32); let minAge = (await registrar(config).minCommitmentAge()).toNumber(); let fee = await registrationFee(config); @@ -71,14 +73,14 @@ async function commitAndRegister(name, owner, config) { await register(name, owner, salt, config); } -async function commit(commitment, fee, minAge, config) { +async function commit(commitment: string, fee: BigNumber, minAge: number, config: Config) { state.set(State.Committing); const signer = config.provider.getSigner(); const tx = await registrar(config) .connect(signer) .commit(commitment, { gasLimit: 150000 }) - .catch(e => console.error(e)); + .catch((e: Error) => console.error(e)); await tx.wait(1); session.state.updateBalance(fee.mul(-1)); @@ -88,7 +90,7 @@ async function commit(commitment, fee, minAge, config) { await tx.wait(minAge + 1); } -async function register(name, owner, salt, config) { +async function register(name: string, owner: string, salt: Uint8Array, config: Config) { state.set(State.Registering); const signer = config.provider.getSigner(); @@ -102,7 +104,7 @@ async function register(name, owner, salt, config) { state.set(State.Registered); } -function makeCommitment(name, owner, salt) { +function makeCommitment(name: string, owner: string, salt: Uint8Array): string { let bytes = ethers.utils.concat([ ethers.utils.toUtf8Bytes(name), ethers.utils.getAddress(owner), diff --git a/src/base/vesting/vesting.ts b/src/base/vesting/vesting.ts index 33ecd53ac..13738f887 100644 --- a/src/base/vesting/vesting.ts +++ b/src/base/vesting/vesting.ts @@ -2,6 +2,7 @@ import { ethers } from "ethers"; import { formatBalance } from "@app/utils"; import * as session from "@app/session"; import { State, state } from "./state"; +import type { Config } from "@app/config"; const abi = [ "function token() view returns (address)", @@ -20,7 +21,7 @@ const tokenAbi = [ "function symbol() view returns (string)", ]; -export async function withdrawVested(address, config) { +export async function withdrawVested(address: string, config: Config) { const contract = new ethers.Contract(address, abi, config.provider); const signer = config.provider.getSigner(); @@ -34,7 +35,7 @@ export async function withdrawVested(address, config) { state.set(State.Withdrawn); } -export async function getInfo(address, config) { +export async function getInfo(address: string, config: Config) { const contract = new ethers.Contract(address, abi, config.provider); const signer = config.provider.getSigner(); diff --git a/src/session.ts b/src/session.ts index aeccb94f7..c1b456dbb 100644 --- a/src/session.ts +++ b/src/session.ts @@ -1,6 +1,7 @@ // TODO: Handle wallet account change. import { get, writable, derived, Writable } from "svelte/store"; import { ethers } from "ethers"; +import type { BigNumber } from 'ethers'; import type { TransactionReceipt, TransactionResponse } from '@ethersproject/providers'; import type { Config } from "@app/config"; import { Unreachable, assert, assertEq } from "@app/error"; @@ -62,7 +63,7 @@ export const createState = (initial: State) => { } }, - updateBalance: n => { + updateBalance: (n: BigNumber) => { store.update((s) => { assert(s.connection === Connection.Connected); s.session.tokenBalance = s.session.tokenBalance.add(n); @@ -70,7 +71,7 @@ export const createState = (initial: State) => { }); }, - refreshBalance: async (config) => { + refreshBalance: async (config: Config) => { let state = get(store); assert(state.connection === Connection.Connected); const addr = state.session.address; @@ -102,6 +103,9 @@ export const createState = (initial: State) => { store.update(s => { switch (s.connection) { case Connection.Connected: + assert(s.session.tx !== null); + assert(s.session.tx.state === 'signing'); + s.session.tx = { state: 'pending', hash: tx.hash }; return s; default: @@ -114,6 +118,7 @@ export const createState = (initial: State) => { store.update(s => { switch (s.connection) { case Connection.Connected: + assert(s.session.tx !== null); assert(s.session.tx.state === 'pending'); if (tx.status === 1) { @@ -159,7 +164,7 @@ const tokenAbi = [ "function allowance(address, address) view returns (uint256)", ]; -export async function approveSpender(spender, amount, config) { +export async function approveSpender(spender: string, amount: BigNumber, config: Config) { const token = new ethers.Contract(config.radToken.address, tokenAbi, config.provider); const signer = config.provider.getSigner(); const addr = await signer.getAddress(); diff --git a/src/utils.ts b/src/utils.ts index e4ed12da5..e77162c24 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,10 +1,11 @@ import { ethers } from "ethers"; +import type { BigNumber } from "ethers"; -export function formatBalance(n) { +export function formatBalance(n: BigNumber) { return ethers.utils.commify(parseFloat(ethers.utils.formatUnits(n)).toFixed(2)); } -export function formatAddress(addr) { +export function formatAddress(addr: string) { return addr.substring(0, 6) + '...' + addr.substring(addr.length - 4, addr.length);