mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-15 19:30:38 +08:00
+9
-4
@@ -247,10 +247,10 @@ a.address {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
input[type="text"], button {
|
||||
input[type="text"], input[type="number"], button {
|
||||
line-height: 1.6;
|
||||
}
|
||||
input[type="text"] {
|
||||
input[type="text"], input[type="number"] {
|
||||
outline: none;
|
||||
border: none;
|
||||
font-size: 1rem;
|
||||
@@ -261,13 +261,18 @@ input[type="text"] {
|
||||
padding: 1rem 1.5rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
input[type="text"]::placeholder {
|
||||
input[type="text"]::placeholder, input[type="number"]::placeholder {
|
||||
color: var(--color-secondary);
|
||||
opacity: 1 !important;
|
||||
}
|
||||
input[type="text"].small {
|
||||
input[type="text"].small, input[type="number"].small {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
input.wide {
|
||||
width: 44ch;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import Registrations from '@app/base/registrations/Routes.svelte';
|
||||
import Orgs from '@app/base/orgs/Routes.svelte';
|
||||
import Users from '@app/base/users/Routes.svelte';
|
||||
import Faucet from '@app/base/faucet/Routes.svelte';
|
||||
import Projects from '@app/base/projects/Routes.svelte';
|
||||
import Resolver from '@app/base/resolver/Routes.svelte';
|
||||
import Header from '@app/Header.svelte';
|
||||
@@ -82,6 +83,7 @@
|
||||
</Route>
|
||||
<Registrations {config} session={$session} />
|
||||
<Orgs {config} />
|
||||
<Faucet {config} />
|
||||
<Users {config} />
|
||||
<Projects {config} />
|
||||
<Resolver {config} />
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as ethers from 'ethers';
|
||||
|
||||
import type { Config } from '@app/config';
|
||||
import { assert } from '@app/error';
|
||||
import type { TransactionResponse } from '@ethersproject/providers';
|
||||
import { parseUnits } from '@ethersproject/units';
|
||||
|
||||
export async function withdraw(amount: number, config: Config): Promise<TransactionResponse> {
|
||||
assert(config.signer);
|
||||
|
||||
const faucet = new ethers.Contract(
|
||||
config.radToken.faucet,
|
||||
config.abi.faucet,
|
||||
config.signer
|
||||
);
|
||||
|
||||
return faucet.withdraw(config.radToken.address, ethers.utils.parseUnits(amount.toString()));
|
||||
}
|
||||
|
||||
export async function getMaxWithdrawAmount(config: Config): Promise<ethers.BigNumber> {
|
||||
assert(config.signer);
|
||||
|
||||
const faucet = new ethers.Contract(
|
||||
config.radToken.faucet,
|
||||
config.abi.faucet,
|
||||
config.signer
|
||||
);
|
||||
|
||||
return faucet.maxWithdrawAmount();
|
||||
}
|
||||
|
||||
export async function calculateTimeLock(amount: ethers.BigNumber, config: Config): Promise<ethers.BigNumber> {
|
||||
assert(config.signer);
|
||||
|
||||
const faucet = new ethers.Contract(
|
||||
config.radToken.faucet,
|
||||
config.abi.faucet,
|
||||
config.signer
|
||||
);
|
||||
|
||||
return faucet.calculateTimeLock(parseUnits(amount.toString()));
|
||||
}
|
||||
|
||||
export async function lastWithdrawalByUser(config: Config): Promise<ethers.BigNumber> {
|
||||
assert(config.signer);
|
||||
|
||||
const address = config.signer.getAddress();
|
||||
|
||||
const faucet = new ethers.Contract(
|
||||
config.radToken.faucet,
|
||||
config.abi.faucet,
|
||||
config.signer
|
||||
);
|
||||
|
||||
return faucet.lastWithdrawalByUser(address);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<script lang="ts">
|
||||
import type { Config } from "@app/config";
|
||||
import { BigNumber } from "@ethersproject/bignumber";
|
||||
import { formatEther, parseUnits } from "@ethersproject/units";
|
||||
import type { ethers } from "ethers";
|
||||
import { onMount } from "svelte";
|
||||
import { navigate } from "svelte-routing";
|
||||
import { getMaxWithdrawAmount, lastWithdrawalByUser, calculateTimeLock } from "./Faucet";
|
||||
|
||||
export let config: Config;
|
||||
|
||||
let amount = 0;
|
||||
let maxWithdrawAmount: BigNumber;
|
||||
let lastWithdrawal: BigNumber;
|
||||
let error: string | undefined;
|
||||
|
||||
async function withdraw() {
|
||||
const [state, message] = await isAbleToWithdraw(amountBN);
|
||||
if (state === true) navigate("/faucet/submit", { state: { amount } });
|
||||
else error = message;
|
||||
}
|
||||
|
||||
async function isAbleToWithdraw(amount: ethers.BigNumber): Promise<[boolean, string?]> {
|
||||
if (amount.isZero()) { return [false, "Not able to withdraw zero tokens"]; }
|
||||
if (parseUnits(amountBN.toString()).gt(maxWithdrawAmount)) return [false, `Reduce amount, max withdrawal is ${formatEther(maxWithdrawAmount)}`];
|
||||
let currentTime = new Date().getTime();
|
||||
let timelock = await calculateTimeLock(amountBN, config);
|
||||
let nextAvailableWithdraw = lastWithdrawal.add(timelock).mul(Math.pow(10,3));
|
||||
if (nextAvailableWithdraw.gt(currentTime)) return [false, `Not ready to withdraw, return after ${new Date(nextAvailableWithdraw.toNumber()).toUTCString()}`];
|
||||
|
||||
return [true];
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
maxWithdrawAmount = await getMaxWithdrawAmount(config);
|
||||
lastWithdrawal = await lastWithdrawalByUser(config);
|
||||
});
|
||||
|
||||
$: amountBN = amount ? BigNumber.from(amount) : BigNumber.from(0);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
div.input-caption {
|
||||
font-size: 1.25rem;
|
||||
text-align: left;
|
||||
margin-left: 1.5rem;
|
||||
padding-left: 1.5rem;
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
div.input-main {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
margin: 1rem 1.5rem 0rem;
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
input[type="number"] {
|
||||
margin: 0;
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
.name {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 1rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.error {
|
||||
padding-left: 1rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
.description :global(p) {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.description.invalid {
|
||||
color: var(--color-negative) !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<svelte:head>
|
||||
<title>Radicle: Faucet</title>
|
||||
</svelte:head>
|
||||
|
||||
<main class="off-centered">
|
||||
<div>
|
||||
{#if config.network.name == "homestead"}
|
||||
<div class="input-caption">
|
||||
To get RAD tokens on <strong>{config.network.name}</strong>, please
|
||||
check the known exchanges.
|
||||
</div>
|
||||
{:else if !config.signer}
|
||||
<div class="input-caption">
|
||||
To get RAD tokens on <strong>{config.network.name}</strong>, please
|
||||
connect your wallet.
|
||||
</div>
|
||||
{:else}
|
||||
<div class="input-caption">
|
||||
Obtain RAD tokens on <strong>{config.network.name}</strong>
|
||||
</div>
|
||||
<div class="input-main">
|
||||
<div class="name">
|
||||
<input
|
||||
type="number"
|
||||
placeholder="Set amount to withdraw"
|
||||
bind:value={amount}
|
||||
on:input={() => error = ""}
|
||||
/>
|
||||
<button disabled={false} class="primary" on:click={withdraw}>
|
||||
Withdraw
|
||||
</button>
|
||||
</div>
|
||||
{#if error}
|
||||
<div class="error description invalid text-small faded">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Route } from "svelte-routing";
|
||||
import Index from "@app/base/faucet/Index.svelte";
|
||||
import type { Config } from "@app/config";
|
||||
import Submit from "./Submit.svelte";
|
||||
|
||||
export let config: Config;
|
||||
</script>
|
||||
|
||||
<Route path="faucet">
|
||||
<Index {config} />
|
||||
</Route>
|
||||
|
||||
<Route path="faucet/submit">
|
||||
<Submit {config} />
|
||||
</Route>
|
||||
@@ -0,0 +1,86 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { navigate } from "svelte-routing";
|
||||
import type { Config } from "@app/config";
|
||||
import Loading from "@app/Loading.svelte";
|
||||
import Modal from "@app/Modal.svelte";
|
||||
import Err from "@app/Error.svelte";
|
||||
import { Status, State } from "@app/utils";
|
||||
import { withdraw } from "./Faucet";
|
||||
import { session } from '@app/session';
|
||||
|
||||
export let config: Config;
|
||||
|
||||
let error: Error;
|
||||
let state: State = {
|
||||
status: Status.Failed,
|
||||
error: "Error withdrawing, something happened.",
|
||||
};
|
||||
$: requester = ($session && $session.address);
|
||||
|
||||
const back = () => navigate(`/faucet`);
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
state.status = Status.Signing;
|
||||
const tx = await withdraw(window.history.state.amount, config);
|
||||
state.status = Status.Pending;
|
||||
await tx.wait();
|
||||
state.status = Status.Success;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
error = e;
|
||||
state = { status: Status.Failed, error: e.message };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.loader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if error}
|
||||
<Err
|
||||
title="Transaction failed"
|
||||
message={error.message}
|
||||
on:close={() => navigate("/faucet")}
|
||||
/>
|
||||
{:else}
|
||||
<Modal>
|
||||
<span slot="title">
|
||||
{#if state.status === Status.Success}
|
||||
<div>🎉</div>
|
||||
{:else}
|
||||
<div>🌐</div>
|
||||
{/if}
|
||||
Withdrawal
|
||||
</span>
|
||||
|
||||
<span slot="subtitle">
|
||||
{#if state.status === Status.Signing}
|
||||
Signing transaction. Please confirm in your wallet.
|
||||
{:else if state.status === Status.Pending}
|
||||
Awaiting transaction.
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span slot="body" class="loader">
|
||||
{#if state.status === Status.Success}
|
||||
The amount of {window.history.state.amount.toString()} RAD tokens has been successfully transfered to
|
||||
<span class="highlight">{requester}</span>
|
||||
{:else}
|
||||
<Loading small center />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span slot="actions">
|
||||
{#if state.status === Status.Success}
|
||||
<button on:click={back} class="register"> Back </button>
|
||||
{/if}
|
||||
</span>
|
||||
</Modal>
|
||||
{/if}
|
||||
@@ -6,19 +6,7 @@
|
||||
import type { Config } from '@app/config';
|
||||
import Loading from '@app/Loading.svelte';
|
||||
import Modal from '@app/Modal.svelte';
|
||||
|
||||
enum Status {
|
||||
Signing,
|
||||
Pending,
|
||||
Success,
|
||||
Failed,
|
||||
}
|
||||
|
||||
type State =
|
||||
{ status: Status.Signing }
|
||||
| { status: Status.Pending }
|
||||
| { status: Status.Success }
|
||||
| { status: Status.Failed; error: string };
|
||||
import { Status, State } from "@app/utils";
|
||||
|
||||
export let subdomain: string;
|
||||
export let config: Config;
|
||||
|
||||
+8
-1
@@ -42,7 +42,8 @@
|
||||
"address": "0x80b68878442b6510D768Be1bd88712710B86eAcD"
|
||||
},
|
||||
"radToken": {
|
||||
"address": "0x7b6CbebC5646D996d258dcD4ca1d334B282e9948"
|
||||
"address": "0x7b6CbebC5646D996d258dcD4ca1d334B282e9948",
|
||||
"faucet": "0x8d9BeE9cA51fd3beb5692F6b404Fe9f76E5F3d1b"
|
||||
},
|
||||
"orgFactory": {
|
||||
"address": "0xF3D04e874D07d680e8b26332eEae5b9B1c263121"
|
||||
@@ -128,6 +129,12 @@
|
||||
"function withdrawableBalance() view returns (uint256)",
|
||||
"function withdrawVested()"
|
||||
],
|
||||
"faucet": [
|
||||
"function lastWithdrawalByUser(address) view returns (uint256)",
|
||||
"function maxWithdrawAmount() view returns (uint256)",
|
||||
"function calculateTimeLock(uint256) view returns (uint256)",
|
||||
"function withdraw(address, uint256)"
|
||||
],
|
||||
"ens": ["function owner(bytes32 node) view returns (address)"]
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ export type WalletConnectState =
|
||||
export class Config {
|
||||
network: { name: string; chainId: number };
|
||||
registrar: { address: string; domain: string };
|
||||
radToken: { address: string };
|
||||
radToken: { address: string; faucet: string };
|
||||
orgFactory: { address: string };
|
||||
reverseRegistrar: { address: string };
|
||||
orgs: { subgraph: string; contractHash: string; pinned: string[] };
|
||||
|
||||
@@ -39,6 +39,19 @@ export interface Token {
|
||||
balance: BigNumber;
|
||||
}
|
||||
|
||||
export enum Status {
|
||||
Signing,
|
||||
Pending,
|
||||
Success,
|
||||
Failed,
|
||||
}
|
||||
|
||||
export type State =
|
||||
{ status: Status.Signing }
|
||||
| { status: Status.Pending }
|
||||
| { status: Status.Success }
|
||||
| { status: Status.Failed; error: string };
|
||||
|
||||
export async function isReverseRecordSet(address: string, domain: string, config: Config): Promise<boolean> {
|
||||
const name = await config.provider.lookupAddress(address);
|
||||
return name === domain;
|
||||
|
||||
Reference in New Issue
Block a user