From fabf08b75c15e6dee894de44812fb7f5ed4d7def Mon Sep 17 00:00:00 2001 From: Aaron Cox Date: Sun, 7 Jul 2024 14:53:35 -0700 Subject: [PATCH] Staking (#192) * Support REX stake/unstake * Fix bootstrap step * Show error details if possible * Change default percent to 0 * REX only supported on eos and jungle4 * Fix missing type * Add placeholder * Show matured balance * Overview page button status * Fix code format * Tooltip for matured balanced * Apply design change * Minor fix * Fix matured rex balance * Add claim * Fix savings balance * Fix eos rex convert * Added APY earnings * Fixing bug that will be resolved once we move to Wharf * Remove 72 hour message * Change icon --------- Co-authored-by: apporc --- src/abi-types.ts | 18 + src/app.svelte | 4 + .../elements/input/token/selector.svelte | 1 - .../elements/input/token/selector/row.svelte | 7 +- src/components/elements/slider.svelte | 310 +++++++++++++++ src/components/layout/navigation/index.svelte | 11 + src/config.ts | 4 + src/lib/rex.ts | 5 + src/pages/earn/components/label.svelte | 53 +++ src/pages/earn/components/progress.svelte | 27 ++ src/pages/earn/index.svelte | 353 ++++++++++++++++++ src/pages/earn/step/bootstrap.svelte | 150 ++++++++ src/pages/earn/step/claim.svelte | 190 ++++++++++ src/pages/earn/step/confirm.svelte | 128 +++++++ src/pages/earn/step/error.svelte | 59 +++ src/pages/earn/step/overview.svelte | 198 ++++++++++ src/pages/earn/step/stake.svelte | 178 +++++++++ src/pages/earn/step/unstake.svelte | 190 ++++++++++ src/pages/earn/types.ts | 19 + src/stores/account-provider.ts | 10 + 20 files changed, 1913 insertions(+), 2 deletions(-) create mode 100644 src/components/elements/slider.svelte create mode 100644 src/lib/rex.ts create mode 100644 src/pages/earn/components/label.svelte create mode 100644 src/pages/earn/components/progress.svelte create mode 100644 src/pages/earn/index.svelte create mode 100644 src/pages/earn/step/bootstrap.svelte create mode 100644 src/pages/earn/step/claim.svelte create mode 100644 src/pages/earn/step/confirm.svelte create mode 100644 src/pages/earn/step/error.svelte create mode 100644 src/pages/earn/step/overview.svelte create mode 100644 src/pages/earn/step/stake.svelte create mode 100644 src/pages/earn/step/unstake.svelte create mode 100644 src/pages/earn/types.ts diff --git a/src/abi-types.ts b/src/abi-types.ts index 7770a8fa..309d40c5 100644 --- a/src/abi-types.ts +++ b/src/abi-types.ts @@ -121,6 +121,24 @@ export class REXDeposit extends Struct { @Struct.field('asset') amount!: Asset } +@Struct.type('rexwithdraw') +export class REXWithdraw extends Struct { + @Struct.field('name') owner!: Name + @Struct.field('asset') amount!: Asset +} + +@Struct.type('rexbuyrex') +export class REXBUYREX extends Struct { + @Struct.field('name') from!: Name + @Struct.field('asset') amount!: Asset +} + +@Struct.type('rexsellrex') +export class REXSELLREX extends Struct { + @Struct.field('name') from!: Name + @Struct.field('asset') rex!: Asset +} + @Struct.type('rexrentcpu') export class REXRentCPU extends Struct { @Struct.field('name') from!: Name diff --git a/src/app.svelte b/src/app.svelte index b6c8a310..3533c14d 100644 --- a/src/app.svelte +++ b/src/app.svelte @@ -8,6 +8,7 @@ import Login from '~/pages/login.svelte' import Dashboard from '~/pages/dashboard/index.svelte' + import Earn from '~/pages/earn/index.svelte' import Request from '~/pages/request/index.svelte' import Send from '~/pages/send/index.svelte' import TokensPurchase from '~/pages/tokens/purchase/index.svelte' @@ -178,6 +179,9 @@ + + + diff --git a/src/components/elements/input/token/selector.svelte b/src/components/elements/input/token/selector.svelte index ddf1bedf..e66d60cc 100644 --- a/src/components/elements/input/token/selector.svelte +++ b/src/components/elements/input/token/selector.svelte @@ -1,6 +1,5 @@ + + + + +
+
+
+
+
(thumbHover = true)} + on:focus={() => (thumbHover = true)} + on:mouseout={() => (thumbHover = false)} + on:blur={() => (thumbHover = false)} + > + {#if holding || thumbHover} +
+ {value + ' %'} +
+ {/if} +
+
+
+
+ + + + diff --git a/src/components/layout/navigation/index.svelte b/src/components/layout/navigation/index.svelte index 4f77dd0c..b728a047 100644 --- a/src/components/layout/navigation/index.svelte +++ b/src/components/layout/navigation/index.svelte @@ -6,6 +6,7 @@ import {activeBlockchain, preferences} from '~/store' import type {NavigationItem} from '~/ui-types' import {banxaIsAvailable} from '~/lib/banxa' + import {rexIsAvailable} from '~/lib/rex' import MediaQuery from '~/components/utils/media-query.svelte' import NavigationContent from '~/components/layout/navigation/content.svelte' @@ -30,6 +31,16 @@ name: 'Transfer', path: '/transfer', }, + ...(rexIsAvailable($activeBlockchain) + ? [ + { + icon: 'dollar-sign', + name: 'Earn', + path: '/earn', + }, + ] + : []), + ...(banxaIsAvailable($activeBlockchain) ? [ { diff --git a/src/config.ts b/src/config.ts index 4df19377..98f8a0fb 100644 --- a/src/config.ts +++ b/src/config.ts @@ -78,6 +78,8 @@ export interface ChainConfig { banxaEnabled?: boolean /** Banxa coin_code */ banxa_coin_code?: string + /** Is REX available for this chain */ + rexEnabled?: boolean } /** Supported chains. */ @@ -105,6 +107,7 @@ export const chains: ChainConfig[] = [ bloksUrl: 'https://bloks.io', balanceProviders: new Set([BalanceProviders.LightAPI]), banxaEnabled: true, + rexEnabled: true, }, { id: 'fio', @@ -172,6 +175,7 @@ export const chains: ChainConfig[] = [ nodeUrl: 'https://jungle4.greymass.com', testnet: true, bloksUrl: 'https://eosauthority.com/?network=jungle', + rexEnabled: true, }, { id: 'proton', diff --git a/src/lib/rex.ts b/src/lib/rex.ts new file mode 100644 index 00000000..8bc62568 --- /dev/null +++ b/src/lib/rex.ts @@ -0,0 +1,5 @@ +import type {ChainConfig} from '~/config' + +export const rexIsAvailable = (chainData: ChainConfig | undefined): boolean => { + return !!chainData?.rexEnabled +} diff --git a/src/pages/earn/components/label.svelte b/src/pages/earn/components/label.svelte new file mode 100644 index 00000000..31e1e4de --- /dev/null +++ b/src/pages/earn/components/label.svelte @@ -0,0 +1,53 @@ + + + + +
+
+

{header}

+ {#if subheader} +

{subheader}

+ {/if} +
+ +
+ +
+ {#if changeStep} +
+ +
+ {/if} +
diff --git a/src/pages/earn/components/progress.svelte b/src/pages/earn/components/progress.svelte new file mode 100644 index 00000000..c277f404 --- /dev/null +++ b/src/pages/earn/components/progress.svelte @@ -0,0 +1,27 @@ + + + + +
+
+
+
diff --git a/src/pages/earn/index.svelte b/src/pages/earn/index.svelte new file mode 100644 index 00000000..05f286bc --- /dev/null +++ b/src/pages/earn/index.svelte @@ -0,0 +1,353 @@ + + + + + + + {#if $step === Step.Error} + switchStep($defaultStep)} /> + {:else if $step === Step.Bootstrap} + toStakeConfirm(Step.Bootstrap)} + /> + {:else if $step === Step.Overview} + switchStep(Step.Stake)} + toUnstake={() => switchStep(Step.Unstake)} + toClaim={() => switchStep(Step.Claim)} + /> + {:else if $step === Step.Stake} + toStakeConfirm(Step.Stake)} + handleBack={() => switchStep($defaultStep)} + /> + {:else if $step === Step.Unstake} + toStakeConfirm(Step.Unstake)} + handleBack={() => switchStep($defaultStep)} + /> + {:else if $step === Step.Claim} + toStakeConfirm(Step.Claim)} + handleBack={() => switchStep($defaultStep)} + /> + {:else if $step === Step.Confirm} + + {/if} + + diff --git a/src/pages/earn/step/bootstrap.svelte b/src/pages/earn/step/bootstrap.svelte new file mode 100644 index 00000000..68d3d4fb --- /dev/null +++ b/src/pages/earn/step/bootstrap.svelte @@ -0,0 +1,150 @@ + + + + +
+
+
EOS Staking
+
Earn ~{rexInfo.apy}% APY*
+
+
+
+
+
{availableTokens}
+
Available Tokens
+
+
+ selected amount to stake + + +
+ +
+ +
+
+
+

The unstaking process takes 21 days before the tokens become available for claim.

+
+

* The APY changes based on the total number of EOS tokens staked at any given moment.

+
+
diff --git a/src/pages/earn/step/claim.svelte b/src/pages/earn/step/claim.svelte new file mode 100644 index 00000000..dbd090ec --- /dev/null +++ b/src/pages/earn/step/claim.svelte @@ -0,0 +1,190 @@ + + + + +
+
+
Claim
+
Remove from your staked balance
+ +
+
+
+ amount to claim +
+ {}} + /> +
+
+ total staked {rexInfo.total} +
+ + +
+ Entire Balance +
+ +
+
+ + +
+ +
+
+
diff --git a/src/pages/earn/step/confirm.svelte b/src/pages/earn/step/confirm.svelte new file mode 100644 index 00000000..d5e71241 --- /dev/null +++ b/src/pages/earn/step/confirm.svelte @@ -0,0 +1,128 @@ + + + + +
+
+
{action}
+
Review and sign
+ +
+
+ + {action} + + +
+ + + + {amount} +
+ {#if usd} +
≈ $ {usd} USD
+ {/if} +
+
+
+ +
+
diff --git a/src/pages/earn/step/error.svelte b/src/pages/earn/step/error.svelte new file mode 100644 index 00000000..7db1791c --- /dev/null +++ b/src/pages/earn/step/error.svelte @@ -0,0 +1,59 @@ + + + + +
+
+

Transfer Failed

+
+ +
+

+ {error} +

+
+ +
+ +
+
diff --git a/src/pages/earn/step/overview.svelte b/src/pages/earn/step/overview.svelte new file mode 100644 index 00000000..15c8c07d --- /dev/null +++ b/src/pages/earn/step/overview.svelte @@ -0,0 +1,198 @@ + + + + +
+
+
EOS Staking
+
Earn ~{rexInfo.apy}% APY*
+
+
+
+
currently staked balance
+
+ {rexInfo.total} +
+ {#if Number(rexInfo.savings.value) > 0} +
+ {rexInfo.savings} unstakable +
+ {/if} + {#if Number(rexInfo.matured.value) > 0} +
+ {rexInfo.matured} claimable +
+ {/if} + +
+ + + +
+
+
+
+

The unstaking process takes 21 days before the tokens become available for claim.

+
+

* The APY changes based on the total number of EOS tokens staked at any given moment.

+
+
diff --git a/src/pages/earn/step/stake.svelte b/src/pages/earn/step/stake.svelte new file mode 100644 index 00000000..112bf3fa --- /dev/null +++ b/src/pages/earn/step/stake.svelte @@ -0,0 +1,178 @@ + + + + +
+
+
Stake
+
Add to your staked balance
+ +
+
+
+ amount to stake +
+ {}} /> +
+
+ currently staked {rexInfo.total} +
+ + +
+ Entire Balance +
+ +
+
+ + +
+ +
+
+
diff --git a/src/pages/earn/step/unstake.svelte b/src/pages/earn/step/unstake.svelte new file mode 100644 index 00000000..63f8e1ae --- /dev/null +++ b/src/pages/earn/step/unstake.svelte @@ -0,0 +1,190 @@ + + + + +
+
+
Unstake
+
Unstake to be claimable
+ +
+
+
+ amount to unstake +
+ {}} + /> +
+
+ total staked {rexInfo.total} +
+ + +
+ Entire Balance +
+ +
+
+ + +
+ +
+
+
diff --git a/src/pages/earn/types.ts b/src/pages/earn/types.ts new file mode 100644 index 00000000..55a33213 --- /dev/null +++ b/src/pages/earn/types.ts @@ -0,0 +1,19 @@ +import type {Asset} from 'anchor-link' + +export const enum Step { + Bootstrap, + Overview, + Stake, + Unstake, + Claim, + Confirm, + Success, + Error, +} + +export interface REXInfo { + total: Asset + savings: Asset + matured: Asset + apy: string +} diff --git a/src/stores/account-provider.ts b/src/stores/account-provider.ts index 2562596e..e5505569 100644 --- a/src/stores/account-provider.ts +++ b/src/stores/account-provider.ts @@ -109,6 +109,16 @@ export async function loadAccount( if (row) { const age = Date.now() - row.updated.getTime() stale = age > maxAge + // TODO: Remove this once Wharf is implemented + // This was needed to fix a bug where the last_vote_weight and proxied_vote_weight fields were missing + if (row.account && row.account.voter_info) { + if (!row.account.voter_info.last_vote_weight) { + row.account.voter_info.last_vote_weight = '0' + } + if (!row.account.voter_info.proxied_vote_weight) { + row.account.voter_info.proxied_vote_weight = '0' + } + } set({account: API.v1.AccountObject.from(row.account), stale}) } if (stale || refresh) {