diff --git a/src/assets/pic/collective-logo.jpg b/src/assets/pic/collective-logo.jpg new file mode 100644 index 0000000..7a511f2 Binary files /dev/null and b/src/assets/pic/collective-logo.jpg differ diff --git a/src/assets/pic/rif-logo.png b/src/assets/pic/rif-logo.png new file mode 100644 index 0000000..3362d21 Binary files /dev/null and b/src/assets/pic/rif-logo.png differ diff --git a/src/assets/pic/rootstock.png b/src/assets/pic/rootstock.png new file mode 100644 index 0000000..093b18d Binary files /dev/null and b/src/assets/pic/rootstock.png differ diff --git a/src/components/account-select/index.vue b/src/components/account-select/index.vue index a5f3cfe..9f99b15 100644 --- a/src/components/account-select/index.vue +++ b/src/components/account-select/index.vue @@ -1,13 +1,13 @@ diff --git a/src/views/portfolio/components/portfolio-item-validator.vue b/src/views/portfolio/components/portfolio-item-validator.vue index c9139d1..f571299 100644 --- a/src/views/portfolio/components/portfolio-item-validator.vue +++ b/src/views/portfolio/components/portfolio-item-validator.vue @@ -25,35 +25,57 @@ Balance

- {{ $filters.cryptoCurrencyFormat(item.balance / LAMPORTS_IN_SOL) }} {{ token?.symbol }} + + +  {{ token?.symbol }}

+

Reward

- {{ $filters.cryptoCurrencyFormat(item.reward) }} {{ token?.symbol }} + +

- - + @@ -78,11 +100,11 @@ import MoreIcon from "@/icons/common/more-icon.vue"; import InfoTooltip from "@/components/info-tooltip/index.vue"; import { onClickOutside } from "@vueuse/core"; import { useRouter } from "vue-router"; -import { Statuses, Token } from "@/core/interfaces"; +import { Chains, Statuses, Token } from "@/core/interfaces"; import { StakingTypes } from "@/store/modules/staking/consts"; import { useStore } from "vuex"; import { SharedTypes } from "@/store/shared/consts"; -import { openSolscanExplorerAddress } from "@/utils/browser"; +import { openExplorerAddress } from "@/utils/browser"; import { LAMPORTS_IN_SOL } from "@/core/constants"; import { trackButtonsEvents } from '@/libs/metrics'; import { ButtonsActionEventType } from '@/libs/metrics/types'; @@ -114,6 +136,15 @@ const props = defineProps({ const activeChain = computed(() => store.getters[SharedTypes.CHAIN_GETTER]); const network = computed(() => store.getters[SharedTypes.NETWORK_GETTER]); +const isRootstock = computed(() => activeChain.value === Chains.ROOTSTOCK); +const displayBalance = computed(() => { + if (isRootstock.value) { + return props.item.balance; + } + + return props.item.balance / LAMPORTS_IN_SOL; +}); +const showRootstockUnstake = computed(() => isRootstock.value && props.item.balance > 0); const amountClassObject = computed(() => ({ 'portfolio-item-validator__info-amount--green': props.item.reward > 0, @@ -142,7 +173,7 @@ const infoTooltipText = computed(() => { return "Your SOL is currently staked with a validator. You'll need to unstake to access these funds" }); -const rewardInfoTooltipText = "Solana adds staking rewards at the end of each epoch. An epoch on Solana typically lasts around two days. If you have a stake active throughout an epoch, you'll see your rewards credited approximately every two days.
These rewards are automatically compounded if you choose to keep them staked, so over time your stake grows as long as you maintain your delegation."; +const rewardInfoTooltipText = isRootstock ? "Rootstock collective adds rewards based on your stRIF allocations to innovative Builders. Your allocations shape their rewards, and you retain full ownership and access to your stRIF while earning a portion of their rewards. For more information check the Whitepaper." :"Solana adds staking rewards at the end of each epoch. An epoch on Solana typically lasts around two days. If you have a stake active throughout an epoch, you'll see your rewards credited approximately every two days.
These rewards are automatically compounded if you choose to keep them staked, so over time your stake grows as long as you maintain your delegation."; const validator = computed(() => { return validators[props.item.provider][activeChain.value]; @@ -186,9 +217,18 @@ const withdrawAction = async (index: number) => { router.push({ name: 'withdraw' }); }; +const rootstockUnstake = async () => { + if (!showRootstockUnstake.value) { + return; + } + + trackButtonsEvents(ButtonsActionEventType.PortfolioScreenUnstakeButtonButtoClicked); + await store.dispatch(StakingTypes.ROOTSTOCK_UNSTAKE_ACTION, props.item.balance); +}; + const detailsAction = () => { trackButtonsEvents(ButtonsActionEventType.PortfolioScreenViewInExploreButtonClicked); - openSolscanExplorerAddress(props.item.stakeAccount, network.value); + openExplorerAddress(props.item.stakeAccount, activeChain.value, network.value); }; @@ -473,5 +513,9 @@ const detailsAction = () => { display: block; } } + + &__rootstock-unstake { + margin-top: 8px; + } } diff --git a/src/views/portfolio/components/portfolio-item.vue b/src/views/portfolio/components/portfolio-item.vue index 9bfcb90..09f2876 100644 --- a/src/views/portfolio/components/portfolio-item.vue +++ b/src/views/portfolio/components/portfolio-item.vue @@ -12,7 +12,7 @@
Total staked

- {{ $filters.cryptoCurrencyFormat(item.totalStaked / LAMPORTS_IN_SOL) }} {{ item.baseToken?.symbol }} + {{ totalStakedDisplay }} {{ totalStakedSymbol }}

~{{ $filters.currencyFormat(totalStakedUsd, "USD") }} @@ -24,14 +24,25 @@

- {{ $filters.cryptoCurrencyFormat(item.totalRewards) }} {{ item.baseToken?.symbol }} + {{ totalRewardsDisplay }} {{ item.baseToken?.symbol }}

~{{ $filters.currencyFormat(totalRewardsUsd, "USD") }}

+
+ +
-
Avg. Yield
+
Avg.Yield (ABI%)
+
Avg. Yield

~{{ $filters.percentFormat(stakingItems[activeChain].apr) }}

@@ -52,22 +63,23 @@
-
-
-
-
-

Validator

-
-
-

Balance

-
-
-

Reward

+ { + if (activeChain.value === Chains.ROOTSTOCK) { + return "Rootstock collective adds rewards based on your stRIF allocations to innovative Builders. Your allocations shape their rewards, and you retain full ownership and access to your stRIF while earning a portion of their rewards. For more information check the Whitepaper."; + } + + return "Solana adds staking rewards at the end of each epoch. An epoch on Solana typically lasts around two days. If you have a stake active throughout an epoch, you'll see your rewards credited approximately every two days.
These rewards are automatically compounded if you choose to keep them staked, so over time your stake grows as long as you maintain your delegation."; +}); const router = useRouter(); const store = useStore(); @@ -121,15 +140,32 @@ isOpen.value = props.isFirst; const prices = computed(() => store.getters[SharedTypes.PRICE_GETTER]); const activeChain = computed(() => store.getters[SharedTypes.CHAIN_GETTER]); const stakingItems = computed(() => store.getters[StakingTypes.STAKING_ITEMS_GETTER]); +const isLoading = computed(() => store.getters[StakingTypes.IS_LOADING_GETTER]); +const isRootstock = computed(() => activeChain.value === Chains.ROOTSTOCK); +const totalStakedAmount = computed(() => { + const divider = activeChain.value === Chains.ROOTSTOCK ? 1 : LAMPORTS_IN_SOL; + return props.item.totalStaked / divider; +}); +const totalStakedDisplay = computed(() => { + if (activeChain.value === Chains.ROOTSTOCK) { + return Number(totalStakedAmount.value || 0).toFixed(2); + } + return cryptoCurrencyFormat(totalStakedAmount.value); +}); +const totalStakedSymbol = computed(() => { + return activeChain.value === Chains.ROOTSTOCK ? "stRIF" : (props.item.baseToken?.symbol ?? ""); +}); const totalStakedUsd = computed(() => { const price = prices.value?.[BASE_TOKENS[activeChain.value].symbol] || 0; - return (props.item.totalStaked / LAMPORTS_IN_SOL) * price; + return totalStakedAmount.value * price; }); const totalRewardsUsd = computed(() => { const price = prices.value?.[BASE_TOKENS[activeChain.value].symbol] || 0; return props.item.totalRewards * price; }); +const totalRewardsDisplay = computed(() => Number(props.item.totalRewards || 0).toFixed(2)); +const canClaimRewards = computed(() => isRootstock.value && props.item.totalRewards > 0); const toggle = () => { isOpen.value = !isOpen.value; @@ -140,6 +176,13 @@ const onStakeMoreClicked = (e: Event) => { e.stopPropagation(); router.push({ name: "stake" }); } + +const onClaimRewardsClicked = async () => { + if (!canClaimRewards.value || isLoading.value) { + return; + } + await store.dispatch(StakingTypes.CLAIM_ROOTSTOCK_REWARDS_ACTION); +} diff --git a/src/views/stake-confirm/index.vue b/src/views/stake-confirm/index.vue index 25c506a..b9ea419 100644 --- a/src/views/stake-confirm/index.vue +++ b/src/views/stake-confirm/index.vue @@ -9,6 +9,7 @@ :account="walletAccount" title="From" :amount="walletBalance" + :token="nativeToken" />
@@ -21,7 +22,7 @@
-
+
@@ -77,7 +78,7 @@ const isLoading = ref(false); const isSend = ref(false); const isSendDone = ref(false); const isError = ref(false); -const nativeToken = ref(BASE_TOKENS[Chains.SOLANA]); +const nativeToken = computed(() => BASE_TOKENS[activeChain.value]); const walletBalance = computed(() => store.getters[SharedTypes.WALLET_BALANCE_GETTER]); const amountValue = computed(() => store.getters[StakingTypes.STAKING_AMOUNT_GETTER]); @@ -85,6 +86,7 @@ const walletAccount = computed(() => store.getters[SharedTypes.WALLET_ACCOUNT_GE const stakingData = computed(() => store.getters[StakingTypes.STAKING_DATA_GETTER]); const validators = computed(() => store.getters[StakingTypes.VALIDATORS_GETTER]); const activeChain = computed(() => store.getters[SharedTypes.CHAIN_GETTER]); +const showValidatorInfo = computed(() => activeChain.value === Chains.SOLANA); const error = computed(() => store.getters[StakingTypes.ERROR_GETTER]); const fee = computed(() => store.getters[StakingTypes.STAKING_FEE_GETTER]); @@ -102,6 +104,19 @@ const nextAction = async () => { trackScreenEvents(ScreenEventType.StackingProcessScreenShown); try { + if (activeChain.value === Chains.ROOTSTOCK) { + const result = await store.dispatch(StakingTypes.START_STAKE_ACTION, null); + if (result) { + isSendDone.value = true; + trackScreenEvents(ScreenEventType.StackingDoneScreenShown); + } + return; + } + + if (!stakingData.value) { + throw new Error("Staking data is missing"); + } + const decodedTransaction = Buffer.from(stakingData.value.unsignedTransaction, 'base64'); const transaction = Transaction.from(decodedTransaction); const signedTransaction = await wallet.signTransaction.value?.(transaction); diff --git a/src/views/stake-enter-amount/index.vue b/src/views/stake-enter-amount/index.vue index 4b7c4ab..528a7d6 100644 --- a/src/views/stake-enter-amount/index.vue +++ b/src/views/stake-enter-amount/index.vue @@ -14,17 +14,51 @@ @update:amount="inputAmount" /> - + +

+ The staking amount will be automatically distributed among top builders in Rootstock ecosystem. You as + a backer will automatically earn the yield shared by builders in Rootstock ecosystem. The staking process will + ask you to approve total of 3 transaction (approve RIF spending, staking, backing builders) to complete the staking process. +

+
+
+
+ {{ step.status === "done" ? "v" : index + 1 }} +
+
+

{{ step.title }}

+ +
+
+
-

Estimated Balance in 1 year

+

+ {{ estimatedLabel }} +

- {{ $filters.cryptoCurrencyFormat(returns) }} {{ BASE_TOKENS[activeChain].symbol }} + +

-

+

~{{ $filters.currencyFormat(returnsUSD, "USD") }}

@@ -64,10 +98,20 @@ const walletBalance = computed(() => store.getters[SharedTypes.WALLET_BALANCE_GE const activeChain = computed(() => store.getters[SharedTypes.CHAIN_GETTER]); const prices = computed(() => store.getters[SharedTypes.PRICE_GETTER]); const validators = computed(() => store.getters[StakingTypes.VALIDATORS_GETTER]); +const stakingItems = computed(() => store.getters[StakingTypes.STAKING_ITEMS_GETTER]); const walletAccount = computed(() => store.getters[SharedTypes.WALLET_ACCOUNT_GETTER]); const isLoading = computed(() => store.getters[StakingTypes.IS_STAKING_LOADING_GETTER]); +const isRootstock = computed(() => activeChain.value === Chains.ROOTSTOCK); +const rootstockStakeFlowSteps = computed(() => store.getters[StakingTypes.ROOTSTOCK_STAKE_FLOW_STEPS_GETTER]); +const abiValue = computed(() => { + const raw = stakingItems.value?.[activeChain.value]?.apr ?? "0"; + const parsed = parseFloat(String(raw)); + return Number.isFinite(parsed) ? parsed : 0; +}); +const estimatedLabel = computed(() => (isRootstock.value ? "ABI" : "Estimated Balance in 1 year")); const returns = computed(() => { - return amount.value * (1 + validators.value[Providers.p2p][activeChain.value].apy); + const apy = Number(validators.value[Providers.p2p][activeChain.value].apy || 0); + return amount.value * (1 + apy); }); const returnsUSD = computed(() => { const value = returns.value || 0; @@ -76,12 +120,33 @@ const returnsUSD = computed(() => { }); const amount = ref(0); -const nativeToken = ref(BASE_TOKENS[Chains.SOLANA]); +const nativeToken = computed(() => BASE_TOKENS[activeChain.value]); +const showValidatorInfo = computed(() => activeChain.value === Chains.SOLANA); const minAmount = ref(1.003); +const rootstockFlowSteps = computed(() => [ + { + id: "approve", + title: "Allow RIF spending", + status: rootstockStakeFlowSteps.value?.approve ?? "pending", + }, + { + id: "stake", + title: "Stake RIF", + status: rootstockStakeFlowSteps.value?.stake ?? "pending", + }, + { + id: "backBuilders", + title: "Back Top Builders", + status: rootstockStakeFlowSteps.value?.backBuilders ?? "pending", + }, +]); trackScreenEvents(ScreenEventType.StackingScreenShown); onMounted(async () => { + if (isRootstock.value) { + store.commit(StakingTypes.RESET_ROOTSTOCK_STAKE_FLOW_STEPS); + } await store.dispatch( StakingTypes.SET_ERROR_STATE, null, @@ -130,6 +195,16 @@ const inputAmount = (newVal: string) => { store.commit(StakingTypes.SET_STAKING_AMOUNT, newVal); }; +const formatStepStatus = (status: string) => { + if (status === "done") { + return "Done"; + } + if (status === "in_progress") { + return "In progress"; + } + return "Pending"; +}; +