-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'staging' of https://github.com/zeitgeistpm/ui into tr-m…
…ore-asset-refactor
- Loading branch information
Showing
15 changed files
with
801 additions
and
153 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import SubScanIcon from "components/icons/SubScanIcon"; | ||
import Table, { TableColumn, TableData } from "components/ui/Table"; | ||
import Decimal from "decimal.js"; | ||
import { ZTG } from "lib/constants"; | ||
import { useChainConstants } from "lib/hooks/queries/useChainConstants"; | ||
import { useMintedInCourt } from "lib/hooks/queries/useMintedInCourt"; | ||
import { useZtgPrice } from "lib/hooks/queries/useZtgPrice"; | ||
import { formatNumberLocalized } from "lib/util"; | ||
import EmptyPortfolio from "./EmptyPortfolio"; | ||
import { | ||
isPayoutEligible, | ||
useCourtNextPayout, | ||
} from "lib/hooks/queries/useCourtNextPayout"; | ||
import { times } from "lodash-es"; | ||
import { isNotNull } from "@zeitgeistpm/utility/dist/null"; | ||
import InfoPopover from "components/ui/InfoPopover"; | ||
import { PiTimerBold } from "react-icons/pi"; | ||
|
||
const columns: TableColumn[] = [ | ||
{ | ||
header: "Time", | ||
accessor: "timestamp", | ||
type: "component", | ||
}, | ||
{ | ||
header: "Amount", | ||
accessor: "amount", | ||
type: "component", | ||
alignment: "right", | ||
}, | ||
{ | ||
header: "Subscan", | ||
accessor: "subscan", | ||
type: "component", | ||
width: "100px", | ||
}, | ||
]; | ||
|
||
const CourtRewardsTable = ({ address }: { address: string }) => { | ||
const { data: mintedInCourt, isLoading } = useMintedInCourt({ | ||
account: address, | ||
}); | ||
const { data: ztgPrice } = useZtgPrice(); | ||
const { data: constants } = useChainConstants(); | ||
|
||
const { data: courtPayout } = useCourtNextPayout(); | ||
|
||
let tableData: TableData[] | undefined = mintedInCourt?.map((mint) => { | ||
return { | ||
timestamp: ( | ||
<span> | ||
{new Intl.DateTimeFormat("default", { | ||
dateStyle: "medium", | ||
timeStyle: "medium", | ||
}).format(new Date(mint?.timestamp))} | ||
</span> | ||
), | ||
amount: ( | ||
<div> | ||
<div> | ||
{formatNumberLocalized( | ||
new Decimal(mint?.dBalance ?? 0).div(ZTG).toNumber(), | ||
)}{" "} | ||
<b>{constants?.tokenSymbol}</b> | ||
</div> | ||
<div className="text-gray-400"> | ||
${" "} | ||
{formatNumberLocalized( | ||
ztgPrice | ||
?.mul(mint?.dBalance ?? 0) | ||
.div(ZTG) | ||
.toNumber() ?? 0, | ||
)} | ||
</div> | ||
</div> | ||
), | ||
subscan: ( | ||
<a | ||
className="center text-sm" | ||
target="_blank" | ||
referrerPolicy="no-referrer" | ||
rel="noopener" | ||
href={`https://zeitgeist.subscan.io/block/${mint?.blockNumber}?tab=event`} | ||
> | ||
<div className=""> | ||
<SubScanIcon /> | ||
</div> | ||
</a> | ||
), | ||
}; | ||
}); | ||
|
||
tableData = [ | ||
isPayoutEligible(courtPayout) | ||
? { | ||
timestamp: ( | ||
<span className="flex items-center gap-2 text-gray-400"> | ||
{new Intl.DateTimeFormat("default", { | ||
dateStyle: "medium", | ||
timeStyle: "medium", | ||
}).format(courtPayout.nextRewardDate)} | ||
<span className="flex items-center gap-1 italic"> | ||
(ETA <PiTimerBold size={18} />) | ||
</span> | ||
</span> | ||
), | ||
amount: <div className="text-gray-300">--</div>, | ||
subscan: ( | ||
<div className="center text-center"> | ||
<InfoPopover | ||
icon={<PiTimerBold className="text-orange-300" size={24} />} | ||
position={"top-start"} | ||
> | ||
Next expected staking reward payout. | ||
</InfoPopover> | ||
</div> | ||
), | ||
} | ||
: null, | ||
...(tableData ?? []), | ||
].filter(isNotNull); | ||
|
||
return ( | ||
<div> | ||
{isLoading === false && | ||
(mintedInCourt == null || mintedInCourt?.length === 0) ? ( | ||
<EmptyPortfolio | ||
headerText="No Court Rewards" | ||
bodyText="" | ||
buttonText="Go To Court" | ||
buttonLink="/court" | ||
/> | ||
) : ( | ||
<Table columns={columns} data={tableData} /> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default CourtRewardsTable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Tab } from "@headlessui/react"; | ||
import SubTabsList from "components/ui/SubTabsList"; | ||
import { useQueryParamState } from "lib/hooks/useQueryParamState"; | ||
import CourtRewardsTable from "./CourtRewardsTable"; | ||
|
||
type CourtGroupItem = "Rewards"; | ||
const courtTabItems: CourtGroupItem[] = ["Rewards"]; | ||
|
||
const CourtTabGroup = ({ address }: { address: string }) => { | ||
const [historyTabSelection, setHistoryTabSelection] = | ||
useQueryParamState<CourtGroupItem>("courtTab"); | ||
|
||
const courtTabIndex = courtTabItems.indexOf(historyTabSelection); | ||
const selectedIndex = courtTabIndex !== -1 ? courtTabIndex : 0; | ||
|
||
return ( | ||
<Tab.Group | ||
defaultIndex={0} | ||
selectedIndex={selectedIndex} | ||
onChange={(index) => setHistoryTabSelection(courtTabItems[index])} | ||
> | ||
<SubTabsList titles={courtTabItems} /> | ||
<Tab.Panels> | ||
<Tab.Panel> | ||
<CourtRewardsTable address={address} /> | ||
</Tab.Panel> | ||
</Tab.Panels> | ||
</Tab.Group> | ||
); | ||
}; | ||
|
||
export default CourtTabGroup; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import { useQuery } from "@tanstack/react-query"; | ||
import { HistoricalAccountBalanceOrderByInput } from "@zeitgeistpm/indexer"; | ||
import { IndexerContext, isIndexedSdk, Sdk } from "@zeitgeistpm/sdk"; | ||
import { blockDate } from "@zeitgeistpm/utility/dist/time"; | ||
import Decimal from "decimal.js"; | ||
import { useChainTime } from "lib/state/chaintime"; | ||
import { useWallet } from "lib/state/wallet"; | ||
import { useSdkv2 } from "../useSdkv2"; | ||
import { useChainConstants } from "./useChainConstants"; | ||
|
||
export const courtNextPayoutRootKey = "court-next-payout"; | ||
|
||
export type CourtPayoutInfo = { | ||
inflationPeriod: number; | ||
nextPayoutBlock: number; | ||
lastPayoutBlock: number; | ||
nextPayoutDate: Date; | ||
lastPayoutDate: Date; | ||
}; | ||
|
||
export type WithPayoutEligibility = CourtPayoutInfo & { | ||
nextRewardBlock: number; | ||
nextRewardDate: Date; | ||
}; | ||
|
||
export const isPayoutEligible = ( | ||
info?: CourtPayoutInfo | WithPayoutEligibility | null, | ||
): info is WithPayoutEligibility => | ||
(info as WithPayoutEligibility)?.nextRewardBlock !== undefined; | ||
|
||
export const useCourtNextPayout = () => { | ||
const [sdk, id] = useSdkv2(); | ||
const now = useChainTime(); | ||
const { data: constants } = useChainConstants(); | ||
const wallet = useWallet(); | ||
|
||
const enabled = isIndexedSdk(sdk) && now && constants && wallet.realAddress; | ||
|
||
const query = useQuery<CourtPayoutInfo | WithPayoutEligibility | null>( | ||
[id, courtNextPayoutRootKey, wallet?.realAddress, now?.block], | ||
async () => { | ||
if (enabled) { | ||
/** | ||
* @note | ||
* last_payout_block(current_block) = floor(current_block / inf_per) * inf_per | ||
* next_payout_block(current_block) = last_payout_block(current_block) + inf_per | ||
*/ | ||
|
||
const currentBlock = new Decimal(now.block); | ||
|
||
const inflationPeriod = new Decimal( | ||
constants.court.inflationPeriodBlocks, | ||
); | ||
|
||
const lastPayoutBlock = new Decimal(currentBlock) | ||
.div(inflationPeriod) | ||
.floor() | ||
.mul(inflationPeriod); | ||
|
||
const nextPayoutBlock = lastPayoutBlock.add(inflationPeriod); | ||
|
||
const participantFirstJoinedAt = await getAccountJoined( | ||
sdk, | ||
wallet.realAddress!, | ||
); | ||
|
||
const courtPayoutInfo: CourtPayoutInfo = { | ||
inflationPeriod: inflationPeriod.toNumber(), | ||
nextPayoutBlock: nextPayoutBlock.toNumber(), | ||
lastPayoutBlock: lastPayoutBlock.toNumber(), | ||
nextPayoutDate: blockDate(now, nextPayoutBlock.toNumber()), | ||
lastPayoutDate: blockDate(now, lastPayoutBlock.toNumber()), | ||
}; | ||
|
||
if (participantFirstJoinedAt) { | ||
const withPayoutEligibility: WithPayoutEligibility = { | ||
...courtPayoutInfo, | ||
nextRewardBlock: nextPayoutBlock.toNumber(), | ||
nextRewardDate: blockDate(now, nextPayoutBlock.toNumber()), | ||
}; | ||
|
||
return withPayoutEligibility; | ||
} | ||
|
||
return courtPayoutInfo; | ||
} | ||
|
||
return null; | ||
}, | ||
{ | ||
enabled: Boolean(enabled), | ||
keepPreviousData: true, | ||
}, | ||
); | ||
|
||
return query; | ||
}; | ||
|
||
const getAccountJoined = async (sdk: Sdk<IndexerContext>, address: string) => { | ||
const { historicalAccountBalances } = | ||
await sdk.indexer.historicalAccountBalances({ | ||
where: { | ||
AND: [ | ||
{ accountId_eq: address }, | ||
{ | ||
OR: [ | ||
{ | ||
extrinsic: { | ||
name_eq: "Court.join_court", | ||
}, | ||
}, | ||
{ | ||
extrinsic: { | ||
name_eq: "Court.delegate", | ||
}, | ||
}, | ||
|
||
{ | ||
extrinsic: { | ||
name_eq: "Court.exit_court", | ||
}, | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
order: HistoricalAccountBalanceOrderByInput.BlockNumberDesc, | ||
}); | ||
|
||
let earliestEligibleJoin: Decimal | null = null; | ||
|
||
for (const event of historicalAccountBalances) { | ||
if ( | ||
event.extrinsic?.name === "Court.join_court" || | ||
event.extrinsic?.name === "Court.delegate" | ||
) { | ||
earliestEligibleJoin = new Decimal(event.blockNumber); | ||
} | ||
if (event.extrinsic?.name === "Court.exit_court") { | ||
console.log("EXITED"); | ||
break; | ||
} | ||
} | ||
|
||
return earliestEligibleJoin; | ||
}; |
Oops, something went wrong.