diff --git a/public/index.css b/public/index.css index c7c4f4720..e57e19fdb 100644 --- a/public/index.css +++ b/public/index.css @@ -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; } diff --git a/src/App.svelte b/src/App.svelte index 321175f62..85db48e77 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -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 @@ + diff --git a/src/base/faucet/Faucet.ts b/src/base/faucet/Faucet.ts new file mode 100644 index 000000000..ec39bf718 --- /dev/null +++ b/src/base/faucet/Faucet.ts @@ -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 { + 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 { + 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 { + 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 { + 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); +} diff --git a/src/base/faucet/Index.svelte b/src/base/faucet/Index.svelte new file mode 100644 index 000000000..9395d9af8 --- /dev/null +++ b/src/base/faucet/Index.svelte @@ -0,0 +1,121 @@ + + + + + + Radicle: Faucet + + +
+
+ {#if config.network.name == "homestead"} +
+ To get RAD tokens on {config.network.name}, please + check the known exchanges. +
+ {:else if !config.signer} +
+ To get RAD tokens on {config.network.name}, please + connect your wallet. +
+ {:else} +
+ Obtain RAD tokens on {config.network.name} +
+
+
+ error = ""} + /> + +
+ {#if error} +
+ {error} +
+ {/if} +
+ {/if} +
+
diff --git a/src/base/faucet/Routes.svelte b/src/base/faucet/Routes.svelte new file mode 100644 index 000000000..9a220b3c1 --- /dev/null +++ b/src/base/faucet/Routes.svelte @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/src/base/faucet/Submit.svelte b/src/base/faucet/Submit.svelte new file mode 100644 index 000000000..dfac92d0a --- /dev/null +++ b/src/base/faucet/Submit.svelte @@ -0,0 +1,86 @@ + + + + +{#if error} + navigate("/faucet")} + /> +{:else} + + + {#if state.status === Status.Success} +
🎉
+ {:else} +
🌐
+ {/if} + Withdrawal +
+ + + {#if state.status === Status.Signing} + Signing transaction. Please confirm in your wallet. + {:else if state.status === Status.Pending} + Awaiting transaction. + {/if} + + + + {#if state.status === Status.Success} + The amount of {window.history.state.amount.toString()} RAD tokens has been successfully transfered to + {requester} + {:else} + + {/if} + + + + {#if state.status === Status.Success} + + {/if} + +
+{/if} diff --git a/src/base/registrations/Update.svelte b/src/base/registrations/Update.svelte index 96e1227e1..be878e011 100644 --- a/src/base/registrations/Update.svelte +++ b/src/base/registrations/Update.svelte @@ -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; diff --git a/src/config.json b/src/config.json index 1e81396dc..2a67a25ab 100644 --- a/src/config.json +++ b/src/config.json @@ -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)"] } } diff --git a/src/config.ts b/src/config.ts index cde39e296..c7b257ab6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -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[] }; diff --git a/src/utils.ts b/src/utils.ts index d2dc15c7d..2f86c1b97 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -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 { const name = await config.provider.lookupAddress(address); return name === domain;