Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Use Registrar::commitWithPermit
Alexis Sellier committed 5 years ago
commit 182acc31d12b90627970763ca2048a2bba71e3f2
parent 890a02815360273225b3a5d7e719770e7dd2411d
6 files changed +77 -25
modified src/base/register/registrar.ts
@@ -3,17 +3,19 @@
import { ethers } from 'ethers';
import type { BigNumber } from 'ethers';
import type { EnsResolver } from '@ethersproject/providers';
+
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
import { State, state } from './state';
import * as session from '@app/session';
import { Failure } from '@app/error';
import type { Config } from '@app/config';
+
import { unixTime } from '@app/utils';
+
import { assert } from '@app/error';

const registrarAbi = [
-
  'function rad() returns (address)',
-
  'function radNode() returns (bytes32)',
-
  'function minCommitmentAge() returns (uint256)',
-
  'function registrationFeeRad() returns (uint256)',
-
  'function commit(bytes32)',
+
  'function rad() view returns (address)',
+
  'function radNode() view returns (bytes32)',
+
  'function minCommitmentAge() view returns (uint256)',
+
  'function registrationFeeRad() view returns (uint256)',
  'function commitWithPermit(bytes32, address, uint256, uint256, uint8, bytes32, bytes32)',
  'function register(string, address, uint256)',
  'function valid(string) pure returns (bool)',
@@ -75,7 +77,6 @@ export async function registerName(name: string, owner: string, config: Config)
    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);
    }
  } catch (e) {
@@ -83,13 +84,6 @@ export async function registerName(name: string, owner: string, config: 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: string, owner: string, config: Config) {
  let salt = ethers.utils.randomBytes(32);
  let minAge = (await registrar(config).minCommitmentAge()).toNumber();
@@ -110,10 +104,23 @@ async function commitAndRegister(name: string, owner: string, config: Config) {
async function commit(commitment: string, fee: BigNumber, minAge: number, config: Config) {
  state.set(State.Committing);

-
  const signer = config.provider.getSigner();
+
  const owner = config.signer;
+
  const ownerAddr = await owner.getAddress();
+
  const spender = config.registrar.address;
+
  const deadline = ethers.BigNumber.from(unixTime()).add(3600); // Expire one hour from now.
+
  const token = session.token(config);
+
  const signature = await permitSignature(owner, token, spender, fee, deadline);
  const tx = await registrar(config)
-
    .connect(signer)
-
    .commit(commitment, { gasLimit: 150000 })
+
    .connect(config.signer)
+
    .commitWithPermit(
+
      commitment,
+
      ownerAddr,
+
      fee,
+
      deadline,
+
      signature.v,
+
      signature.r,
+
      signature.s,
+
      { gasLimit: 150000 })
    .catch((e: Error) => console.error(e));

  await tx.wait(1);
@@ -124,6 +131,45 @@ async function commit(commitment: string, fee: BigNumber, minAge: number, config
  await tx.wait(minAge + 1);
}

+
async function permitSignature(
+
  owner: ethers.Signer & TypedDataSigner,
+
  token: ethers.Contract,
+
  spenderAddr: string,
+
  value: ethers.BigNumberish,
+
  deadline: ethers.BigNumberish,
+
): Promise<ethers.Signature> {
+
  assert(owner.provider);
+

+
  const ownerAddr = await owner.getAddress();
+
  const nonce = await token.nonces(ownerAddr);
+
  const chainId = (await owner.provider.getNetwork()).chainId;
+

+
  const domain = {
+
    name: await token.name(),
+
    chainId,
+
    verifyingContract: token.address,
+
  };
+
  const types = {
+
    Permit: [
+
      { "name": "owner", "type": "address" },
+
      { "name": "spender", "type": "address" },
+
      { "name": "value", "type": "uint256" },
+
      { "name": "nonce", "type": "uint256" },
+
      { "name": "deadline", "type": "uint256" }
+
    ]
+
  };
+
  const values = {
+
    "owner": ownerAddr,
+
    "spender": spenderAddr,
+
    "value": value,
+
    "nonce": nonce,
+
    "deadline": deadline
+
  };
+
  const sig = await owner._signTypedData(domain, types, values);
+

+
  return ethers.utils.splitSignature(sig);
+
}
+

async function register(name: string, owner: string, salt: Uint8Array, config: Config) {
  state.set(State.Registering);

modified src/base/register/state.ts
@@ -3,7 +3,6 @@ import { derived, writable } from "svelte/store";
export enum State {
  Failed = -1,
  Idle,
-
  Approving,
  Committing,
  WaitingToRegister,
  Registering,
modified src/base/register/steps/Submit.svelte
@@ -54,13 +54,7 @@
    </span>

    <span slot="body">
-
      {#if $state === State.Approving}
-
        Approving Registry for {#await getFee(config)}
-
          ?
-
        {:then fee}
-
          {fee}
-
        {/await} <strong>RAD</strong>...
-
      {:else if $state === State.Committing}
+
      {#if $state === State.Committing}
        Committing...
      {:else if $state === State.WaitingToRegister}
        Waiting for commitment time...
modified src/config.ts
@@ -1,4 +1,5 @@
import { ethers } from "ethers";
+
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
import config from "@app/config.json";

declare global {
@@ -14,7 +15,7 @@ export type Config = {
  orgFactory: { address: string },
  gasLimits: { createOrg: number },
  provider: ethers.providers.JsonRpcProvider,
-
  signer: ethers.Signer,
+
  signer: ethers.Signer & TypedDataSigner,
};

/// Gas limits for various transactions.
modified src/session.ts
@@ -162,6 +162,9 @@ const tokenAbi = [
  "function balanceOf(address) view returns (uint256)",
  "function approve(address, uint256) returns (bool)",
  "function allowance(address, address) view returns (uint256)",
+
  "function DOMAIN_SEPARATOR() view returns (bytes32)",
+
  "function name() pure returns (string)",
+
  "function nonces(address) view returns (uint256)",
];

export async function approveSpender(spender: string, amount: BigNumber, config: Config) {
@@ -177,6 +180,10 @@ export async function approveSpender(spender: string, amount: BigNumber, config:
  }
}

+
export function token(config: Config): ethers.Contract {
+
  return new ethers.Contract(config.radToken.address, tokenAbi, config.provider);
+
}
+

export function disconnectWallet() {
  location.reload();
}
modified src/utils.ts
@@ -23,3 +23,8 @@ export function parseEnsLabel(name: string, config: Config) {
  let domain = config.registrar.domain.replace(".", "\\.");
  return name.replace(new RegExp(`\\.${domain}$`), "");
}
+

+
// Return the current unix time.
+
export function unixTime(): number {
+
  return Math.floor(Date.now() / 1000);
+
}