Skip to content

Commit

Permalink
Merge pull request #16 from PeerMeHQ/artcpa-nft-staking
Browse files Browse the repository at this point in the history
add artcpa nft staking
  • Loading branch information
michavie authored Dec 4, 2023
2 parents 806dfae + bf9862e commit 46c3e36
Show file tree
Hide file tree
Showing 11 changed files with 345 additions and 19 deletions.
3 changes: 2 additions & 1 deletion src/extensions/artcpaclub/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react'
import { NftTab } from './nft/NftTab'
import { Tab } from '@headlessui/react'
import { EsdtTab } from './esdt/EsdtTab'
import { TabButton } from '../../../shared/ui/elements'
Expand All @@ -16,7 +17,7 @@ export const App = () => {
<EsdtTab />
</Tab.Panel>
<Tab.Panel>
<p>Coming soon.</p>
<NftTab />
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
Expand Down
1 change: 1 addition & 0 deletions src/extensions/artcpaclub/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export const Config = {

Abis: {
EsdtStaking: 'https://marketplace.artcpaclub.com/peerme-extension/esdt-staking.abi.json',
NftStaking: 'https://marketplace.artcpaclub.com/peerme-extension/nft-staking.abi.json',
},
}
44 changes: 35 additions & 9 deletions src/extensions/artcpaclub/src/contracts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,57 @@ import { ClaimEsdtActionPreview } from './previews/ClaimEsdtActionPreview'
import { UnstakeEsdtActionPreview } from './previews/UnstakeEsdtActionPreview'
import { Network, ExtensionScInfo, ExtensionConfig } from '../../../shared/types'

const getContractAddress = (network: Network) => {
const getEsdtPoolContractAddress = (network: Network) => {
if (network === 'devnet') return 'erd1qqqqqqqqqqqqqpgqagtkct3gswr62z3p2qdqlf5l2ukseu2ql3tsjl02gh'
if (network === 'testnet') return '#'
return 'erd1qqqqqqqqqqqqqpgqj8exjpz38agu78sxh5rlxcp2kmxy35m6kqysscypf3'
}

const getNftPoolContractAddress = (network: Network) => {
if (network === 'devnet') return 'erd1qqqqqqqqqqqqqpgqzfk3v0rvwuucjh9xg4zjt6y3jdm4g569l3tsjfey97'
if (network === 'testnet') return '#'
return 'erd1qqqqqqqqqqqqqpgqfken0exk7jpr85dx6f8ym3jgcagesfcqkqys0xnquf'
}

export const Contracts = (config: ExtensionConfig): ExtensionScInfo => ({
UserStake: {
Address: getContractAddress(config.network),
EsdtUserStake: {
Address: getEsdtPoolContractAddress(config.network),
Endpoint: 'userStake',
ActionPreview: (action: ProposalAction) => <StakeEsdtActionPreview action={action} config={config} />,
},
UserUnstake: {
Address: getContractAddress(config.network),
EsdtUserUnstake: {
Address: getEsdtPoolContractAddress(config.network),
Endpoint: 'userUnstake',
ActionPreview: (action: ProposalAction) => <UnstakeEsdtActionPreview action={action} config={config} />,
},
UserClaim: {
Address: getContractAddress(config.network),
EsdtUserClaim: {
Address: getEsdtPoolContractAddress(config.network),
Endpoint: 'userClaim',
ActionPreview: (action: ProposalAction) => <ClaimEsdtActionPreview action={action} config={config} />,
},
ViewPool: {
Address: getContractAddress(config.network),
EsdtViewPool: {
Address: getEsdtPoolContractAddress(config.network),
Endpoint: 'viewPool',
AbiUrl: Config.Abis.EsdtStaking,
},
NftUserStake: {
Address: getNftPoolContractAddress(config.network),
Endpoint: 'userStake',
ActionPreview: (action: ProposalAction) => <StakeEsdtActionPreview action={action} config={config} />,
},
NftUserUnstake: {
Address: getNftPoolContractAddress(config.network),
Endpoint: 'userUnstake',
ActionPreview: (action: ProposalAction) => <UnstakeEsdtActionPreview action={action} config={config} />,
},
NftUserClaim: {
Address: getNftPoolContractAddress(config.network),
Endpoint: 'userClaim',
ActionPreview: (action: ProposalAction) => <ClaimEsdtActionPreview action={action} config={config} />,
},
NftViewPool: {
Address: getNftPoolContractAddress(config.network),
Endpoint: 'viewPool',
AbiUrl: Config.Abis.NftStaking,
},
})
8 changes: 4 additions & 4 deletions src/extensions/artcpaclub/src/esdt/EsdtTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function EsdtTab() {
const [poolId, setPoolId] = useState<number | null>(null)
const [selectedPool, setSelectedPool] = useState<EsdtPool | null>(null)
const [selectedPoolOnChain, setSelectedPoolOnChain] = useState<EsdtPoolOnChain | null>(null)
const poolScQuery = useScQuery(app.config.walletConfig, Contracts(app.config).ViewPool)
const poolScQuery = useScQuery(app.config.walletConfig, Contracts(app.config).EsdtViewPool)

useEffect(() => {
if (!poolUrl) return
Expand Down Expand Up @@ -45,7 +45,7 @@ export function EsdtTab() {
return selectedPool === null ? (
<AppSection title="Paste the link of a Pool">
<label htmlFor="starting_date" className="pl-1 text-xl mb-2 text-gray-800 dark:text-gray-200">
Link to Staking Pool
Link to ESDT Staking Pool
</label>
<Input
placeholder="https://marketplace.artcpaclub.com/staking/token/x"
Expand Down Expand Up @@ -93,8 +93,8 @@ function _PoolInfo(props: { app: AppContextValue; pool: EsdtPool }) {
function _PoolOnChainInfo(props: { app: AppContextValue; pool: EsdtPool; poolOnChain: EsdtPoolOnChain }) {
const handleRewardClaim = () =>
props.app.requestProposalAction(
Contracts(props.app.config).UserClaim.Address,
Contracts(props.app.config).UserClaim.Endpoint,
Contracts(props.app.config).EsdtUserClaim.Address,
Contracts(props.app.config).EsdtUserClaim.Endpoint,
0,
[props.pool.pool_id],
[]
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/artcpaclub/src/esdt/_Staker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export function _Staker(props: Props) {
const tokenTransfers = payment.isEgld() ? [] : [payment]

app.requestProposalAction(
Contracts(app.config).UserStake.Address,
Contracts(app.config).UserStake.Endpoint,
Contracts(app.config).EsdtUserStake.Address,
Contracts(app.config).EsdtUserStake.Endpoint,
value,
[props.pool.pool_id],
tokenTransfers
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/artcpaclub/src/esdt/_Unstaker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export function _Unstaker(props: Props) {
e.preventDefault()
const amountBig = new BigNumber(amount).shiftedBy(props.pool.stake_token_decimal)
app.requestProposalAction(
Contracts(app.config).UserUnstake.Address,
Contracts(app.config).UserUnstake.Endpoint,
Contracts(app.config).EsdtUserUnstake.Address,
Contracts(app.config).EsdtUserUnstake.Endpoint,
0,
[props.pool.pool_id, amountBig],
[]
Expand Down
8 changes: 7 additions & 1 deletion src/extensions/artcpaclub/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import BigNumber from 'bignumber.js'
import { EsdtPoolOnChain } from './types'
import { EsdtPoolOnChain, NftPoolOnChain } from './types'

export const toTypedEsdtPoolOnChain = (value: any): EsdtPoolOnChain => ({
user_stake_amount: new BigNumber(value.user_stake_amount),
user_reward_amount: new BigNumber(value.user_reward_amount),
...value,
})

export const toTypedNftPoolOnChain = (value: any): NftPoolOnChain => ({
user_stake_amount: new BigNumber(value.user_stake_amount),
user_reward_amount: new BigNumber(value.user_reward_amount),
...value,
})
128 changes: 128 additions & 0 deletions src/extensions/artcpaclub/src/nft/NftTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Config } from '../config'
import { _Staker } from './_Staker'
import { Input } from '@peerme/web-ui'
import { _Unstaker } from './_Unstaker'
import { Contracts } from '../contracts'
import React, { useEffect, useState } from 'react'
import { toTypedNftPoolOnChain } from '../helpers'
import { NftPoolOnChain, NftPool } from '../types'
import { useApp } from '../../../../shared/hooks/useApp'
import { AppContextValue } from '../../../../shared/types'
import { AppSection } from '../../../../shared/ui/elements'
import { toFormattedTokenAmount, useScQuery } from '@peerme/core-ts'

export function NftTab() {
const app = useApp()
const [poolUrl, setPoolUrl] = useState('')
const [poolId, setPoolId] = useState<number | null>(null)
const [selectedPool, setSelectedPool] = useState<any | null>(null)
const [selectedPoolOnChain, setSelectedPoolOnChain] = useState<NftPoolOnChain | null>(null)
const poolScQuery = useScQuery(app.config.walletConfig, Contracts(app.config).NftViewPool)

useEffect(() => {
if (!poolUrl) return
const match = poolUrl.match(/\/staking\/nft\/(\d+)/)
if (match && match[1] !== undefined) {
setPoolId(parseInt(match[1]))
} else {
app.showToast('Invalid pool URL', 'error')
setPoolUrl('')
}
}, [poolUrl])

useEffect(() => {
if (poolId === null) return
fetch(Config.ApiBaseUrl(app.config.network) + '/nftstaking/' + poolId).then(async (res) => {
const data = (await res.json()) as NftPool
setSelectedPool(data)
poolScQuery.query([data.pool_id, app.config.entity.address]).then((data) => {
const poolData = data.firstValue?.valueOf() || {}
setSelectedPoolOnChain(toTypedNftPoolOnChain(poolData))
})
})
}, [poolId])

return selectedPool === null ? (
<AppSection title="Paste the link of a Pool">
<label htmlFor="starting_date" className="pl-1 text-xl mb-2 text-gray-800 dark:text-gray-200">
Link to NFT Staking Pool
</label>
<Input
placeholder="https://marketplace.artcpaclub.com/nft/token/x"
value={poolUrl}
onChange={(val) => setPoolUrl(val)}
/>
</AppSection>
) : (
<>
{!!selectedPool && <_PoolInfo app={app} pool={selectedPool} />}
{!!selectedPool && !!selectedPoolOnChain && (
<_PoolOnChainInfo app={app} pool={selectedPool} poolOnChain={selectedPoolOnChain} />
)}
<_Staker pool={selectedPool} className="mb-4" />
{!!selectedPoolOnChain && selectedPoolOnChain.user_stake_amount.isGreaterThan(0) && (
<_Unstaker pool={selectedPool} poolOnChain={selectedPoolOnChain} className="mb-4" />
)}
</>
)
}

function _PoolInfo(props: { app: AppContextValue; pool: NftPool }) {
return (
<a
href={Config.MarketplaceUrl(props.app.config.network) + '/staking/token/' + props.pool.pool_id}
target="_blank"
rel="noopener"
className="flex px-6 py-3 bg-gray-200 dark:bg-gray-800 rounded-xl mb-4"
>
<div className="flex-grow text-left">
<h3 className="text-lg text-black dark:text-white mb-2">{props.pool.title}</h3>
<ul className="text-base text-gray-500 dark:text-gray-400 list-disc pl-4">
<li>
Stake Token: <strong>{props.pool.stake_token_id}</strong>
</li>
<li>
Reward Token: <strong>{props.pool.reward_token_id}</strong>
</li>
</ul>
</div>
</a>
)
}

function _PoolOnChainInfo(props: { app: AppContextValue; pool: NftPool; poolOnChain: NftPoolOnChain }) {
const handleRewardClaim = () =>
props.app.requestProposalAction(
Contracts(props.app.config).NftUserClaim.Address,
Contracts(props.app.config).NftUserClaim.Endpoint,
0,
[props.pool.pool_id],
[]
)

return (
<ul className="grid grid-cols-1 md:grid-cols-2 gap-4 list-none mb-4">
<li className="col-span-1">
<div className="bg-gray-200 dark:bg-gray-800 rounded-2xl px-6 py-4">
<h2 className="text-base mb-1">Our Stake</h2>
<strong className="font-head text-4xl text-primary-500 dark:text-primary-400">
{props.poolOnChain.user_stake_amount.toNumber()}
</strong>
</div>
</li>
<li className="col-span-1">
<div className="bg-gray-200 dark:bg-gray-800 rounded-2xl px-6 py-4">
<h2 className="text-base mb-1">Claimable Rewards</h2>
<strong className="block font-head text-4xl text-primary-500 dark:text-primary-400">
{toFormattedTokenAmount(props.poolOnChain.user_reward_amount, props.pool.reward_token_decimal)}
</strong>
{props.poolOnChain.user_reward_amount.isGreaterThan(0) && (
<button onClick={handleRewardClaim} className="text-blue-500">
Claim Rewards
</button>
)}
</div>
</li>
</ul>
)
}
51 changes: 51 additions & 0 deletions src/extensions/artcpaclub/src/nft/_Staker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { NftPool } from '../types'
import { Contracts } from '../contracts'
import React, { SyntheticEvent, useState } from 'react'
import { TokenTransfer } from '@multiversx/sdk-core/out'
import { useApp } from '../../../../shared/hooks/useApp'
import { AppSection } from '../../../../shared/ui/elements'
import { Button, EntityTransferSelector } from '@peerme/web-ui'

type Props = {
pool: NftPool
className?: string
}

export function _Staker(props: Props) {
const app = useApp()
const [payment, setPayment] = useState<TokenTransfer | null>(null)
const isSubmitDisabled = !payment || payment.amountAsBigInteger.isZero()

const handleSubmit = (e: SyntheticEvent) => {
e.preventDefault()
if (!payment) return
const value = payment.isEgld() ? payment.amountAsBigInteger : 0
const tokenTransfers = payment.isEgld() ? [] : [payment]

app.requestProposalAction(
Contracts(app.config).NftUserStake.Address,
Contracts(app.config).NftUserStake.Endpoint,
value,
[props.pool.pool_id],
tokenTransfers
)
}

return (
<AppSection title="Stake now" className={props.className}>
<form onSubmit={handleSubmit}>
<EntityTransferSelector
config={app.config.walletConfig}
entity={app.config.entity}
permissions={[{ name: '*', value: '0', destination: '', endpoint: '', arguments: [], payments: [] }]}
onSelected={(val) => setPayment(val)}
tokenIdWhitelist={['egld', props.pool.stake_token_id]}
className="mb-8"
/>
<Button color="blue" className="block w-full" disabled={isSubmitDisabled} submit>
Add Stake Action
</Button>
</form>
</AppSection>
)
}
Loading

1 comment on commit 46c3e36

@vercel
Copy link

@vercel vercel bot commented on 46c3e36 Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.