From 9eded4d24bf5f0035928afd007b8f44446819724 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Sat, 6 Mar 2021 17:22:01 +0100 Subject: [PATCH] Add "Register" module --- src/App.svelte | 26 +++-- src/base/register/Register.svelte | 129 ++++++++++++++++++++++++ src/base/register/RegisterButton.svelte | 90 +++++++++++++++++ src/base/register/registrar.js | 112 ++++++++++++++++++++ src/base/register/state.js | 16 +++ src/base/vesting/Vesting.svelte | 97 +++++++++--------- 6 files changed, 412 insertions(+), 58 deletions(-) create mode 100644 src/base/register/Register.svelte create mode 100644 src/base/register/RegisterButton.svelte create mode 100644 src/base/register/registrar.js create mode 100644 src/base/register/state.js diff --git a/src/App.svelte b/src/App.svelte index c7b9da527..dd3d04831 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -6,6 +6,7 @@ import { session } from './session.js'; import Vesting from './base/vesting/Vesting.svelte'; + import Register from './base/register/Register.svelte'; import Header from './Header.svelte'; export let url = ""; @@ -35,11 +36,22 @@ -
-
-
- - - +{#await getConfig()} + +{:then config} +
+
+
+ + + + + + + + +
-
+{:catch err} + +{/await} diff --git a/src/base/register/Register.svelte b/src/base/register/Register.svelte new file mode 100644 index 000000000..890ecc128 --- /dev/null +++ b/src/base/register/Register.svelte @@ -0,0 +1,129 @@ + + + + +
+ {#if $state === STATE.IDLE || $state === STATE.CHECKING_AVAILABILITY} +
+ Register a radicle.eth name +
+
+ +
+ + .radicle.eth +
+
+ +
+ {:else} + + {/if} +
diff --git a/src/base/register/RegisterButton.svelte b/src/base/register/RegisterButton.svelte new file mode 100644 index 000000000..efdee86c4 --- /dev/null +++ b/src/base/register/RegisterButton.svelte @@ -0,0 +1,90 @@ + + + + +{#if $state >= STATE.NAME_AVAILABLE && $state < STATE.REGISTERED} + {#if $session.address} + + {:else} + + {/if} + +{:else if $state === STATE.REGISTERED} + +{:else if $state === STATE.NAME_UNAVAILABLE} + +{:else if subdomain == ""} + +{:else if $state === STATE.CHECKING_AVAILABILITY} + +{:else} + +{/if} diff --git a/src/base/register/registrar.js b/src/base/register/registrar.js new file mode 100644 index 000000000..6b4b85d19 --- /dev/null +++ b/src/base/register/registrar.js @@ -0,0 +1,112 @@ +// TODO: Show "look at your wallet" / "confirm tx" before state change. +// TODO: Two registration actions with same label +import { ethers } from "ethers"; +import { STATE, state } from './state.js'; +import { approveSpender, updateBalance } from '../../session.js'; +import { ERROR } from '../../error.js'; + +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"}, + {"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}, + {"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"available","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}, + {"inputs":[{"internalType":"bytes32","name":"commitment","type":"bytes32"}],"name":"commit","outputs":[],"stateMutability":"nonpayable","type":"function"}, + {"inputs":[],"name":"commitments","outputs":[{"internalType":"contract Commitments","name":"","type":"address"}],"stateMutability":"view","type":"function"}, + {"inputs":[],"name":"ens","outputs":[{"internalType":"contract ENS","name":"","type":"address"}],"stateMutability":"view","type":"function"}, + {"inputs":[],"name":"minCommitmentAge","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}, + {"inputs":[{"internalType":"bytes32","name":"parent","type":"bytes32"},{"internalType":"bytes32","name":"label","type":"bytes32"}],"name":"namehash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"}, + {"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}, + {"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"salt","type":"uint256"}],"name":"register","outputs":[],"stateMutability":"nonpayable","type":"function"}, + {"inputs":[],"name":"registrationFeeRad","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}, + {"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"valid","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"} +]; + +export function registrar(config) { + return new ethers.Contract(config.registrar.address, registrarAbi, config.provider); +} + +export async function registrationFee(config) { + return await registrar(config).registrationFeeRad(); +} + +export async function registerName(name, owner, config) { + if (! name) return; + + let commitmentJson = window.localStorage.getItem('commitment'); + let commitment = commitmentJson && JSON.parse(commitmentJson); + + // Try to recover an existing commitment. + if (commitment && commitment.name === name && commitment.owner === owner) { + await register(name, owner, commitment.salt, config); + } else { + await approveRegistrar(owner, config); + await commitAndRegister(name, owner, config); + } +} + +async function approveRegistrar(owner, config) { + state.set(STATE.APPROVING); + + const amount = await registrationFee(config); + await approveSpender(config.registrar.address, amount, config); +} + +async function commitAndRegister(name, owner, config) { + let salt = ethers.utils.randomBytes(32); + let minAge = (await registrar(config).minCommitmentAge()).toNumber(); + let fee = await registrationFee(config); + + await commit(makeCommitment(name, owner, salt), fee, minAge, config); + // Save commitment in local storage in case the user refreshes or closes + // the page. + window.localStorage.setItem('commitment', JSON.stringify({ + name: name, + owner: owner, + salt: ethers.utils.hexlify(salt) + })); + + await register(name, owner, salt, config); +} + +async function commit(commitment, fee, minAge, 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)); + + await tx.wait(1); + updateBalance(fee.mul(-1)); + + // TODO: Getting "commitment too new" + state.set(STATE.WAITING_TO_REGISTER); + await tx.wait(minAge + 1); +} + +async function register(name, owner, salt, config) { + state.set(STATE.REGISTERING); + + const signer = config.provider.getSigner(); + const tx = await registrar(config).connect(signer).register( + name, owner, ethers.BigNumber.from(salt), { gasLimit: 150000 } + ); + console.log("Sent", tx); + + try { + await tx.wait(); + window.localStorage.clear(); + state.set(STATE.REGISTERED); + } catch (e) { + throw { type: ERROR.TRANSACTION_FAILED, hash: tx.hash }; + } +} + +function makeCommitment(name, owner, salt) { + let bytes = ethers.utils.concat([ + ethers.utils.toUtf8Bytes(name), + ethers.utils.getAddress(owner), + ethers.BigNumber.from(salt), + ]); + return ethers.utils.keccak256(bytes); +} diff --git a/src/base/register/state.js b/src/base/register/state.js new file mode 100644 index 000000000..5c3c85195 --- /dev/null +++ b/src/base/register/state.js @@ -0,0 +1,16 @@ +import { derived, writable } from "svelte/store"; + +export const STATE = { + ERROR: -1, + IDLE: 0, + CHECKING_AVAILABILITY: 1, + NAME_UNAVAILABLE: 2, + NAME_AVAILABLE: 3, + APPROVING: 4, + COMMITTING: 5, + WAITING_TO_REGISTER: 6, + REGISTERING: 7, + REGISTERED: 8, +}; + +export const state = writable(STATE.IDLE); diff --git a/src/base/vesting/Vesting.svelte b/src/base/vesting/Vesting.svelte index 98a145ff3..0e1dc3fb0 100644 --- a/src/base/vesting/Vesting.svelte +++ b/src/base/vesting/Vesting.svelte @@ -2,9 +2,10 @@ import { ethers } from 'ethers'; import { get } from 'svelte/store'; import { STATE, state } from './state.js'; - import { getConfig } from '../../config.js'; import { getInfo } from './vesting.js'; + export let config = null; + let contractAddress = ""; let info = null; @@ -33,57 +34,51 @@ } -{#await getConfig()} - -{:then config} -
- {#if info} - + {:else} +
+ Enter your Radicle vesting contract address +
+
+ +
+ +
+
+ +
+ {/if} +