Skip to content

Commit

Permalink
Staking (#192)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
aaroncox and apporc authored Jul 7, 2024
1 parent b607d09 commit fabf08b
Show file tree
Hide file tree
Showing 20 changed files with 1,913 additions and 2 deletions.
18 changes: 18 additions & 0 deletions src/abi-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/app.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -178,6 +179,9 @@
<Route path="/">
<Dashboard />
</Route>
<Route path="/earn">
<Earn />
</Route>
<Route path="/send">
<Send />
</Route>
Expand Down
1 change: 0 additions & 1 deletion src/components/elements/input/token/selector.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script>
import {writable} from 'svelte/store'
import {Asset} from 'anchor-link'
import {activeBlockchain} from '~/store'
import type {Token} from '~/stores/tokens'
Expand Down
7 changes: 6 additions & 1 deletion src/components/elements/input/token/selector/row.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
let balance
$: {
balance = $balances && $balances.find((balance) => balance.tokenKey === token.key)?.quantity
if (token.balance) {
balance = token.balance
} else {
balance =
$balances && $balances.find((balance) => balance.tokenKey === token.key)?.quantity
}
if (typeof balance === 'string') {
formattedTokenBalance = balance
Expand Down
310 changes: 310 additions & 0 deletions src/components/elements/slider.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
<script>
import {createEventDispatcher} from 'svelte'
import {fly, fade} from 'svelte/transition'
// Props
export let min = 0
export let max = 100
export let initialValue = 0
export let id: any = null
export let value = typeof initialValue === 'string' ? parseInt(initialValue) : initialValue
// Node Bindings
let container = null
let thumb = null
let progressBar = null
let element = null
// Internal State
let elementX = null
let currentThumb = null
let holding = false
let thumbHover = false
let keydownAcceleration = 0
let accelerationTimer = null
// Dispatch 'change' events
const dispatch = createEventDispatcher()
// Mouse shield used onMouseDown to prevent any mouse events penetrating other elements,
// ie. hover events on other elements while dragging. Especially for Safari
const mouseEventShield = document.createElement('div')
mouseEventShield.setAttribute('class', 'mouse-over-shield')
mouseEventShield.addEventListener('mouseover', (e) => {
e.preventDefault()
e.stopPropagation()
})
function resizeWindow() {
elementX = element.getBoundingClientRect().left
}
// Allows both bind:value and on:change for parent value retrieval
function setValue(val) {
value = val
dispatch('change', {value})
}
function onTrackEvent(e) {
// Update value immediately before beginning drag
updateValueOnEvent(e)
onDragStart(e)
}
function onHover(e) {
thumbHover = thumbHover ? false : true
}
function onDragStart(e) {
// If mouse event add a pointer events shield
if (e.type === 'mousedown') document.body.append(mouseEventShield)
currentThumb = thumb
}
function onDragEnd(e) {
// If using mouse - remove pointer event shield
if (e.type === 'mouseup') {
if (document.body.contains(mouseEventShield))
document.body.removeChild(mouseEventShield)
// Needed to check whether thumb and mouse overlap after shield removed
if (isMouseInElement(e, thumb)) thumbHover = true
}
currentThumb = null
}
// Check if mouse event cords overlay with an element's area
function isMouseInElement(event, element) {
if (element === null) {
return false
}
let rect = element.getBoundingClientRect()
let {clientX: x, clientY: y} = event
if (x < rect.left || x >= rect.right) return false
if (y < rect.top || y >= rect.bottom) return false
return true
}
// Accessible keypress handling
function onKeyPress(e) {
// Max out at +/- 10 to value per event (50 events / 5)
// 100 below is to increase the amount of events required to reach max velocity
if (keydownAcceleration < 50) keydownAcceleration++
let throttled = Math.ceil(keydownAcceleration / 5)
if (e.key === 'ArrowUp' || e.key === 'ArrowRight') {
if (value + throttled > max || value >= max) {
setValue(max)
} else {
setValue(value + throttled)
}
}
if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') {
if (value - throttled < min || value <= min) {
setValue(min)
} else {
setValue(value - throttled)
}
}
// Reset acceleration after 100ms of no events
clearTimeout(accelerationTimer)
accelerationTimer = setTimeout(() => (keydownAcceleration = 1), 100)
}
function calculateNewValue(clientX) {
// Find distance between cursor and element's left cord (20px / 2 = 10px) - Center of thumb
let delta = clientX - (elementX + 10)
// Use width of the container minus (5px * 2 sides) offset for percent calc
let percent = (delta * 100) / (container.clientWidth - 10)
// Limit percent 0 -> 100
percent = percent < 0 ? 0 : percent > 100 ? 100 : percent
// Limit value min -> max
setValue(parseInt((percent * (max - min)) / 100) + min)
}
// Handles both dragging of touch/mouse as well as simple one-off click/touches
function updateValueOnEvent(e) {
// touchstart && mousedown are one-off updates, otherwise expect a currentPointer node
if (!currentThumb && e.type !== 'touchstart' && e.type !== 'mousedown') return false
if (e.stopPropagation) e.stopPropagation()
if (e.preventDefault) e.preventDefault()
// Get client's x cord either touch or mouse
const clientX =
e.type === 'touchmove' || e.type === 'touchstart' ? e.touches[0].clientX : e.clientX
calculateNewValue(clientX)
}
// React to left position of element relative to window
$: if (element) elementX = element.getBoundingClientRect().left
// Set a class based on if dragging
$: holding = Boolean(currentThumb)
// Update progressbar and thumb styles to represent value
$: if (progressBar && thumb) {
// Limit value min -> max
value = value > min ? value : min
value = value < max ? value : max
let percent = ((value - min) * 100) / (max - min)
let offsetLeft = (container.clientWidth - 10) * (percent / 100) + 5
// Update thumb position + active slider track width
thumb.style.left = `${offsetLeft}px`
progressBar.style.width = `${offsetLeft}px`
}
</script>

<style>
.slider {
position: relative;
flex: 1;
}
.slider__wrapper {
min-width: 100%;
position: relative;
padding: 0.5rem;
box-sizing: border-box;
outline: none;
}
.slider__wrapper:focus-visible > .slider__track {
box-shadow: 0 0 0 2px white, 0 0 0 3px var(--main-blue);
}
.slider__track {
height: 6px;
background-color: var(--cultured);
border-radius: 999px;
}
.slider__track--highlighted {
background-color: var(--main-blue);
width: 0;
height: 6px;
position: absolute;
border-radius: 999px;
}
.slider__thumb {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
width: 20px;
height: 20px;
background-color: var(--thumb-bgcolor, white);
cursor: pointer;
border-radius: 999px;
margin-top: -8px;
transition: box-shadow 100ms;
user-select: none;
box-shadow: var(
--thumb-boxshadow,
0 1px 1px 0 rgba(0, 0, 0, 0.14),
0 0px 2px 1px rgba(0, 0, 0, 0.2)
);
}
.slider__thumb--holding {
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 2px 1px rgba(0, 0, 0, 0.2),
0 0 0 6px var(--thumb-holding-outline, rgba(113, 119, 250, 0.3));
}
.slider__tooltip {
pointer-events: none;
position: absolute;
top: -33px;
color: var(--tooltip-text, white);
width: 38px;
padding: 4px 0;
border-radius: 4px;
text-align: center;
background-color: var(--main-blue);
}
.slider__tooltip::after {
content: '';
display: block;
position: absolute;
height: 7px;
width: 7px;
background-color: var(--main-blue);
bottom: -3px;
left: calc(50% - 3px);
clip-path: polygon(0% 0%, 100% 100%, 0% 100%);
transform: rotate(-45deg);
border-radius: 0 0 0 3px;
}
</style>

<svelte:window
on:touchmove|nonpassive={updateValueOnEvent}
on:touchcancel={onDragEnd}
on:touchend={onDragEnd}
on:mousemove={updateValueOnEvent}
on:mouseup={onDragEnd}
on:resize={resizeWindow}
/>
<div class="slider">
<div
class="slider__wrapper"
tabindex="0"
on:keydown={onKeyPress}
bind:this={element}
role="slider"
aria-valuemin={min}
aria-valuemax={max}
aria-valuenow={value}
{id}
on:mousedown={onTrackEvent}
on:touchstart={onTrackEvent}
>
<div class="slider__track" bind:this={container}>
<div class="slider__track--highlighted" bind:this={progressBar} />
<div
class="slider__thumb"
class:slider__thumb--holding={holding}
bind:this={thumb}
on:touchstart={onDragStart}
on:mousedown={onDragStart}
on:mouseover={() => (thumbHover = true)}
on:focus={() => (thumbHover = true)}
on:mouseout={() => (thumbHover = false)}
on:blur={() => (thumbHover = false)}
>
{#if holding || thumbHover}
<div
class="slider__tooltip"
in:fly={{y: 7, duration: 200}}
out:fade={{duration: 100}}
>
{value + ' %'}
</div>
{/if}
</div>
</div>
</div>
</div>

<svelte:head>
<style>
.mouse-over-shield {
position: fixed;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
background-color: rgba(255, 0, 0, 0);
z-index: 10000;
cursor: grabbing;
}
</style>
</svelte:head>
11 changes: 11 additions & 0 deletions src/components/layout/navigation/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -30,6 +31,16 @@
name: 'Transfer',
path: '/transfer',
},
...(rexIsAvailable($activeBlockchain)
? [
{
icon: 'dollar-sign',
name: 'Earn',
path: '/earn',
},
]
: []),
...(banxaIsAvailable($activeBlockchain)
? [
{
Expand Down
Loading

0 comments on commit fabf08b

Please sign in to comment.