From d5b81398825ca180e922be5743f2f0c17658c333 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Tue, 24 Oct 2023 18:42:51 +0200 Subject: [PATCH 01/39] Adjust fetching XP to new approach Use locally saved data to get most of the informations about XP --- src/data/xp-data.json | 22 ++++ src/data/xp/4/1.json | 21 ++++ src/data/xp/4/leaderboard.json | 12 ++ src/redux-state/selectors/island.ts | 2 + src/redux-state/thunks/island.ts | 56 +++++---- src/shared/contracts/xp.ts | 81 +++---------- src/shared/types/island.ts | 6 +- src/shared/types/xp.ts | 4 +- src/shared/utils/index.ts | 1 + src/shared/utils/xp.ts | 108 +++++++----------- .../LeaderboardList/LeaderboardItem.tsx | 3 +- 11 files changed, 151 insertions(+), 165 deletions(-) create mode 100644 src/data/xp-data.json create mode 100644 src/data/xp/4/1.json create mode 100644 src/data/xp/4/leaderboard.json diff --git a/src/data/xp-data.json b/src/data/xp-data.json new file mode 100644 index 000000000..a31a0c61f --- /dev/null +++ b/src/data/xp-data.json @@ -0,0 +1,22 @@ +{ + "4": { + "xp": ["/data/xp/4/1.json"], + "leaderboard": "/data/xp/4/leaderboard.json" + }, + "7": { + "xp": [], + "leaderboard": null + }, + "9": { + "xp": [], + "leaderboard": null + }, + "19": { + "xp": [], + "leaderboard": null + }, + "22": { + "xp": [], + "leaderboard": null + } +} \ No newline at end of file diff --git a/src/data/xp/4/1.json b/src/data/xp/4/1.json new file mode 100644 index 000000000..ddc2b6e44 --- /dev/null +++ b/src/data/xp/4/1.json @@ -0,0 +1,21 @@ +{ + "totalAmount": "0x1e", + "merkleDistributor": "0x2F3b9De8b80294b6096dE7f036f621bC62ef7AE0", + "merkleRoot": "0x4208988e67d4ceac20ad6c3d1fc5002ff76c2d39c14a31718a07253eaa974ea9", + "claims": { + "0x5ca23512120e636f134b010e0b9a8c148d8ed2ea": { + "index": "0x0", + "amount": "0x0a", + "proof": [ + "0x94bcb09674cfc935fb2bc43eb7511610a691e685af49aaffde87152f4b173add" + ] + }, + "0x6e80164ea60673d64d5d6228beb684a1274bb017": { + "index": "0x1", + "amount": "0x14", + "proof": [ + "0x535faee8794ecc326a3d4a78dec4381ce261c52190f3b7b65a25c95b5207f737" + ] + } + } +} diff --git a/src/data/xp/4/leaderboard.json b/src/data/xp/4/leaderboard.json new file mode 100644 index 000000000..153e7c92d --- /dev/null +++ b/src/data/xp/4/leaderboard.json @@ -0,0 +1,12 @@ +[ + { + "rank": 1, + "beneficiary": "0x6e80164ea60673d64d5d6228beb684a1274bb017", + "amount": "0x14" + }, + { + "rank": 2, + "beneficiary": "0x5ca23512120e636f134b010e0b9a8c148d8ed2ea", + "amount": "0x0a" + } +] diff --git a/src/redux-state/selectors/island.ts b/src/redux-state/selectors/island.ts index d99e1a0ba..e2825a0d8 100644 --- a/src/redux-state/selectors/island.ts +++ b/src/redux-state/selectors/island.ts @@ -154,6 +154,8 @@ export const selectUnclaimedXpSumById = createSelector( [selectUnclaimedXpById], (unclaimedXp) => unclaimedXp?.reduce( + // TODO: fix - amount is a big int turned into a string, we should add this as bigints, + // adjust precision and display as readable number (acc, item) => acc + parseInt(item.claim.amount, 16), 0 ) ?? 0 diff --git a/src/redux-state/thunks/island.ts b/src/redux-state/thunks/island.ts index ad0e3cac8..cf4b55b78 100644 --- a/src/redux-state/thunks/island.ts +++ b/src/redux-state/thunks/island.ts @@ -30,9 +30,7 @@ import { import { updateTransactionStatus } from "redux-state/slices/wallet" import { bigIntToUserAmount, getAllowanceTransactionID } from "shared/utils" import { - convertXpData, getRealmLeaderboardData, - getRealmXpSorted, getUserLeaderboardRank, } from "shared/utils/xp" import { getXpAllocatable } from "shared/contracts/xp" @@ -291,17 +289,18 @@ export const fetchLeaderboardData = createDappAsyncThunk( await Promise.allSettled( Object.keys(realms).map(async (realmId) => { - const xpData = await getRealmLeaderboardData(realmId) + const leaderboardData = await getRealmLeaderboardData(realmId) - if (xpData) { - const converted = convertXpData(xpData) - const sorted = getRealmXpSorted(converted) - const leaderboard = sorted.slice(0, 10).map((item, index) => ({ - ...item, - rank: index + 1, - })) + if (leaderboardData) { + const leaderboard = leaderboardData + .slice(0, 10) + .map((item, index) => ({ + ...item, + amount: item.amount, + rank: index + 1, + })) - const currentUser = getUserLeaderboardRank(sorted, address) + const currentUser = getUserLeaderboardRank(leaderboardData, address) dispatch( setLeaderboardData({ @@ -328,27 +327,24 @@ export const fetchUnclaimedXp = createDappAsyncThunk( if (!account) return false await Promise.allSettled( - Object.entries(realms).map( - async ([realmId, { realmContractAddress, xpToken }]) => { - const unclaimedXp = await transactionService.read( - getUnclaimedXpDistributions, - { - realmAddress: realmContractAddress, - xpAddress: xpToken.contractAddress, - account, - } - ) - - if (unclaimedXp) { - dispatch( - setUnclaimedXpData({ - id: realmId, - data: unclaimedXp, - }) - ) + Object.keys(realms).map(async (realmId) => { + const unclaimedXp = await transactionService.read( + getUnclaimedXpDistributions, + { + realmId, + account, } + ) + + if (unclaimedXp) { + dispatch( + setUnclaimedXpData({ + id: realmId, + data: unclaimedXp, + }) + ) } - ) + }) ) return true diff --git a/src/shared/contracts/xp.ts b/src/shared/contracts/xp.ts index e34f19018..ca798eddd 100644 --- a/src/shared/contracts/xp.ts +++ b/src/shared/contracts/xp.ts @@ -1,21 +1,17 @@ -import { Contract, ethers, providers } from "ethers" +import { Contract, providers } from "ethers" import { ReadTransactionBuilder, UnclaimedXpData, WriteTransactionBuilder, - XpDistributor, XpMerkleTreeItem, } from "shared/types" -import { isSameAddress, normalizeAddress } from "shared/utils" -import { getUserXpByMerkleRoot } from "shared/utils/xp" -import { CONTRACT_DEPLOYMENT_BLOCK_NUMBER } from "shared/constants" +import { normalizeAddress } from "shared/utils" +import { getXpDataForRealmId } from "shared/utils/xp" import { xpMerkleDistributorAbi, xpAbi, xpMerkleDistributorFactoryAbi, } from "./abi" -import { getRealmContract } from "./realms" - import { getGameContract } from "./game" export const getXpMerkleDistributorContract: ReadTransactionBuilder< @@ -27,7 +23,7 @@ export const getXpMerkleDistributorContract: ReadTransactionBuilder< export const hasClaimedXp: ReadTransactionBuilder< { distributorContractAddress: string - index: number + index: string }, boolean > = async (provider, { distributorContractAddress, index }) => { @@ -40,79 +36,32 @@ export const hasClaimedXp: ReadTransactionBuilder< return hasClaimed } -const getDistributorsFromEvents = ( - events: ethers.Event[], - xpContractAddress: string -) => - events.flatMap((event) => { - const { args } = event - - if (!args) return [] - - const distributedXpAddress = args.xp - - if (!isSameAddress(distributedXpAddress, xpContractAddress)) { - return [] - } - - return { - distributorContractAddress: normalizeAddress(args.distributor), - merkleRoot: args.merkleRoot, - merkleDataUrl: args.merkleDataUrl, - } - }) - -export const getXPDistributorsAddresses: ReadTransactionBuilder< - { - realmContractAddress: string - xpContractAddress: string - }, - XpDistributor[] -> = async (provider, { realmContractAddress, xpContractAddress }) => { - const realmContract = await getRealmContract(provider, { - realmContractAddress, - }) - - const xpDistributedEventFilter = realmContract.filters.XpDistributed() - const xpDistributedEvents = await realmContract.queryFilter( - xpDistributedEventFilter, - CONTRACT_DEPLOYMENT_BLOCK_NUMBER - ) - - return getDistributorsFromEvents(xpDistributedEvents, xpContractAddress) -} - export const getUnclaimedXpDistributions: ReadTransactionBuilder< { - realmAddress: string - xpAddress: string + realmId: string account: string }, UnclaimedXpData[] -> = async (provider, { realmAddress, xpAddress, account }) => { - const distributorAddresses = await getXPDistributorsAddresses(provider, { - realmContractAddress: realmAddress, - xpContractAddress: xpAddress, - }) +> = async (provider, { realmId, account }) => { + const xpData = (await getXpDataForRealmId(realmId)) ?? [] const unclaimedOrNull = await Promise.all( - distributorAddresses.map>( - async ({ distributorContractAddress, merkleRoot, merkleDataUrl }) => { - const claims = await getUserXpByMerkleRoot(account, merkleDataUrl) + xpData.map>( + async ({ merkleDistributor, merkleRoot, claims }) => { + const claim = claims[normalizeAddress(account)] - if (claims[merkleRoot]) { + if (claim) { const hasClaimed = await hasClaimedXp(provider, { - distributorContractAddress, - index: claims[merkleRoot].index, + distributorContractAddress: merkleDistributor, + index: claim.index, }) return hasClaimed ? null : { - distributorContractAddress, + distributorContractAddress: merkleDistributor, merkleRoot, - merkleDataUrl, - claim: claims[merkleRoot], + claim, } } return null diff --git a/src/shared/types/island.ts b/src/shared/types/island.ts index 96b12174f..d044f00f6 100644 --- a/src/shared/types/island.ts +++ b/src/shared/types/island.ts @@ -38,10 +38,14 @@ export type XpMerkleTreeItemData = XpMerkleTreeItem & { beneficiary: string } -export type LeaderboardItemData = XpMerkleTreeItemData & { +export type LeaderboardItemData = { rank: number + beneficiary: string + amount: string } +export type XPLeaderboard = LeaderboardItemData[] + export type LeaderboardData = { currentUser: null | LeaderboardItemData leaderboard: LeaderboardItemData[] diff --git a/src/shared/types/xp.ts b/src/shared/types/xp.ts index f1cb7267b..88d058c2f 100644 --- a/src/shared/types/xp.ts +++ b/src/shared/types/xp.ts @@ -1,11 +1,12 @@ export type XpMerkleTreeItem = { - index: number + index: string amount: string proof: string[] } export type XpMerkleTree = { totalAmount: string merkleRoot: string + merkleDistributor: string claims: { [address: string]: XpMerkleTreeItem } @@ -13,7 +14,6 @@ export type XpMerkleTree = { export type XpDistributor = { distributorContractAddress: string merkleRoot: string - merkleDataUrl: string } export type XpByMerkleRoot = { [merkleRoot: string]: XpMerkleTreeItem diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts index cf63cb52a..d7ecf672a 100644 --- a/src/shared/utils/index.ts +++ b/src/shared/utils/index.ts @@ -8,3 +8,4 @@ export * from "./island" export * from "./timers" export * from "./numbers" export * from "./transactions" +export * from "./xp" diff --git a/src/shared/utils/xp.ts b/src/shared/utils/xp.ts index fe6a671c0..ca51bea89 100644 --- a/src/shared/utils/xp.ts +++ b/src/shared/utils/xp.ts @@ -1,103 +1,81 @@ -import { LeaderboardItemData, XpMerkleTreeItemData } from "shared/types" -import { XpMerkleTree, XpByMerkleRoot } from "shared/types/xp" -import { isSameAddress, normalizeAddress } from "shared/utils" +import { LeaderboardItemData, XPLeaderboard } from "shared/types" +import { XpMerkleTree } from "shared/types/xp" +import { isSameAddress, normalizeAddress } from "shared/utils/address" +import XP_DATA from "../../data/xp-data.json" -type DynamicXPMerkleTreeImport = { - default: XpMerkleTree +// TODO: remove mocks +import leaderboardMock from "../../data/xp/4/leaderboard.json" +import xpMock from "../../data/xp/4/1.json" + +type XpDataType = { + [realmId: string]: { leaderboard: string | null; xp: string[] } } export async function getRealmLeaderboardData( realmId: string -): Promise { +): Promise { if (!realmId) { throw new Error("Missing realm id") } - let xpData: null | DynamicXPMerkleTreeImport = null + let xpData: null | XPLeaderboard = null if (realmId) { try { - xpData = await import(`data/xp/${realmId}/leaderboard.json`) + const leaderboardUrl = (XP_DATA as XpDataType)[realmId]?.leaderboard + + if (!leaderboardUrl) { + throw new Error("Missing leaderboard url") + } + // xpData = await (await fetch(leaderboardUrl)).json() + xpData = leaderboardMock } catch (error) { // eslint-disable-next-line no-console - console.warn("No XP data found for the realm id:", realmId) + console.warn("No XP data found for the realm id:", realmId, error) } } - return xpData && (xpData.default as XpMerkleTree) + return xpData } -async function getXpData(url: string): Promise { - if (!url) { - throw new Error("Missing url") - } - - let xpData: null | XpMerkleTree = null - - if (url) { - // debugger - try { - xpData = await (await fetch(url)).json() - } catch (error) { - // eslint-disable-next-line no-console - console.warn("No XP data found for the url:", url) - } +export async function getXpDataForRealmId( + realmId: string +): Promise { + if (!realmId) { + throw new Error("Missing realm id") } - return xpData && (xpData as XpMerkleTree) -} -export async function getUserXpByMerkleRoot( - account: string, - url: string -): Promise { - const xpItemByMerkleRoot: XpByMerkleRoot = {} - const normalizedAddress = normalizeAddress(account) + let xpData: null | XpMerkleTree[] = null - const xpData = await getXpData(url) + try { + const xpLinks = (XP_DATA as XpDataType)[realmId]?.xp - if (xpData) { - try { - const { merkleRoot } = xpData - const userClaim = xpData.claims[normalizedAddress] + xpData = await Promise.all( + xpLinks.map(async (url) => { + // const data = await (await fetch(url)).json() + const data = xpMock - if (userClaim) { - xpItemByMerkleRoot[merkleRoot] = userClaim - } - } catch (error) { - // eslint-disable-next-line no-console - console.warn("Not a correct structure for XP data") - } + return data + }) + ) + } catch (error) { + // eslint-disable-next-line no-console + console.warn("No XP data found for the url:", realmId, error) } - return xpItemByMerkleRoot + return xpData } export function getUserLeaderboardRank( - sortedData: XpMerkleTreeItemData[], + leaderboard: XPLeaderboard, address: string ): LeaderboardItemData | null { if (!address) return null const normalizedAddress = normalizeAddress(address) - const index = sortedData.findIndex((item) => + const index = leaderboard.findIndex((item) => isSameAddress(item.beneficiary, normalizedAddress) ) - return index > -1 - ? { - rank: index + 1, - ...sortedData[index], - } - : null -} - -export function getRealmXpSorted(data: XpMerkleTreeItemData[]) { - return data.sort((a, b) => Number(b.amount) - Number(a.amount)) -} - -export function convertXpData(xpData: XpMerkleTree): XpMerkleTreeItemData[] { - return Object.entries(xpData.claims).map(([beneficiary, data]) => ({ - beneficiary, - ...data, - })) + return index > -1 ? leaderboard[index] : null } diff --git a/src/ui/Island/RealmDetails/LeaderboardList/LeaderboardItem.tsx b/src/ui/Island/RealmDetails/LeaderboardList/LeaderboardItem.tsx index faf31f9bc..84af5c072 100644 --- a/src/ui/Island/RealmDetails/LeaderboardList/LeaderboardItem.tsx +++ b/src/ui/Island/RealmDetails/LeaderboardList/LeaderboardItem.tsx @@ -3,6 +3,7 @@ import Icon from "shared/components/Icon" import crossIcon from "shared/assets/icons/plus.svg" import classNames from "classnames" import { + bigIntToUserAmount, isSameAddress, resolveAddressToWalletData, separateThousandsByComma, @@ -65,7 +66,7 @@ export default function LeaderboardItem({ {username || truncateAddress(address)} - {separateThousandsByComma(BigInt(amount))} XP + {separateThousandsByComma(bigIntToUserAmount(BigInt(amount)))} XP From 238500ddccc92dd7fe94ea589478e0ca864e5a3a Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 25 Oct 2023 10:28:52 +0200 Subject: [PATCH 02/39] Fix displayed XP reward amount --- src/redux-state/selectors/island.ts | 8 ++------ src/ui/Claim/modals/ClaimCongratulations.tsx | 2 +- .../RealmDetails/RealmBanners/BannerRewards.tsx | 14 +++++++------- src/ui/Island/RealmDetails/index.tsx | 4 ++-- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/redux-state/selectors/island.ts b/src/redux-state/selectors/island.ts index e2825a0d8..602a38b00 100644 --- a/src/redux-state/selectors/island.ts +++ b/src/redux-state/selectors/island.ts @@ -153,12 +153,8 @@ export const selectUnclaimedXpById = createSelector( export const selectUnclaimedXpSumById = createSelector( [selectUnclaimedXpById], (unclaimedXp) => - unclaimedXp?.reduce( - // TODO: fix - amount is a big int turned into a string, we should add this as bigints, - // adjust precision and display as readable number - (acc, item) => acc + parseInt(item.claim.amount, 16), - 0 - ) ?? 0 + unclaimedXp?.reduce((acc, item) => acc + BigInt(item.claim.amount), 0n) ?? + 0n ) /* Population - selectors */ export const selectSortedPopulation = createSelector(selectRealms, (realms) => { diff --git a/src/ui/Claim/modals/ClaimCongratulations.tsx b/src/ui/Claim/modals/ClaimCongratulations.tsx index dac660b39..b15b2dd94 100644 --- a/src/ui/Claim/modals/ClaimCongratulations.tsx +++ b/src/ui/Claim/modals/ClaimCongratulations.tsx @@ -5,7 +5,7 @@ import CongratulationsModal from "shared/components/Modals/CongratulationsModal" import RealmIcon from "shared/components/RealmIcon" type ClaimCongratulationsProps = { - amount: number + amount: string description: string realmId: string close: () => void diff --git a/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx b/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx index 599ba78fe..1e092ab06 100644 --- a/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx +++ b/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx @@ -15,12 +15,12 @@ import ClaimCongratulations from "ui/Claim/modals/ClaimCongratulations" import Tooltip from "shared/components/Tooltip" import { useTransactionSuccessCallback } from "shared/hooks" import TransactionsModal from "shared/components/Transactions/TransactionsModal" -import { separateThousandsByComma } from "shared/utils" +import { bigIntToUserAmount, separateThousandsByComma } from "shared/utils" import { LINKS } from "shared/constants" const CLAIM_XP_TX_ID = "claim-xp" -export default function BannerRewards({ amount }: { amount: number }) { +export default function BannerRewards({ amount }: { amount: bigint }) { const dispatch = useDappDispatch() const realmId = useDappSelector(selectDisplayedRealmId) const realm = useDappSelector((state) => selectRealmById(state, realmId)) @@ -68,6 +68,8 @@ export default function BannerRewards({ amount }: { amount: number }) { if (!realmId || !realm) return null + const parsedAmount = separateThousandsByComma(bigIntToUserAmount(amount)) + return ( <> setIsClaimTransactionModalOpen(true)} - isDisabled={amount === 0} + isDisabled={amount === 0n} > Claim XP @@ -113,9 +115,7 @@ export default function BannerRewards({ amount }: { amount: number }) { width="32px" color="var(--primary-p1-100)" /> -
- {separateThousandsByComma(amount)} -
+
{parsedAmount}
{realm.xpToken.symbol}
@@ -157,7 +157,7 @@ export default function BannerRewards({ amount }: { amount: number }) { {congratulationsModalOpen && ( setCongratulationsModalOpen(false)} /> diff --git a/src/ui/Island/RealmDetails/index.tsx b/src/ui/Island/RealmDetails/index.tsx index a0f810565..68b94a1ac 100644 --- a/src/ui/Island/RealmDetails/index.tsx +++ b/src/ui/Island/RealmDetails/index.tsx @@ -42,7 +42,7 @@ function RealmDetailsBanner({ const hasClaimed = useDappSelector(selectHasClaimed) const isStakingRealm = useDappSelector(selectIsStakingRealmDisplayed) const rewardAmount = useDappSelector((state) => - displayedRealmId ? selectUnclaimedXpSumById(state, displayedRealmId) : 0 + displayedRealmId ? selectUnclaimedXpSumById(state, displayedRealmId) : 0n ) if (!isConnected) { @@ -62,7 +62,7 @@ function RealmDetailsBanner({ ) } - if (isStakingRealm || (!isStakingRealm && rewardAmount > 0)) { + if (isStakingRealm || (!isStakingRealm && rewardAmount > 0n)) { return } From f44406490f59e241ae6dba3b104d0e44127fca44 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 25 Oct 2023 10:34:49 +0200 Subject: [PATCH 03/39] Remove redundant map on leaderboard data --- src/redux-state/thunks/island.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/redux-state/thunks/island.ts b/src/redux-state/thunks/island.ts index cf4b55b78..55051e37a 100644 --- a/src/redux-state/thunks/island.ts +++ b/src/redux-state/thunks/island.ts @@ -292,13 +292,7 @@ export const fetchLeaderboardData = createDappAsyncThunk( const leaderboardData = await getRealmLeaderboardData(realmId) if (leaderboardData) { - const leaderboard = leaderboardData - .slice(0, 10) - .map((item, index) => ({ - ...item, - amount: item.amount, - rank: index + 1, - })) + const leaderboard = leaderboardData.slice(0, 10) const currentUser = getUserLeaderboardRank(leaderboardData, address) From 13791b693ad189d28970257c5965b924534022d9 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 25 Oct 2023 10:51:02 +0200 Subject: [PATCH 04/39] Don't throw errors if there are no links to leadeboard or xp files --- src/shared/utils/xp.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/shared/utils/xp.ts b/src/shared/utils/xp.ts index ca51bea89..aa1136084 100644 --- a/src/shared/utils/xp.ts +++ b/src/shared/utils/xp.ts @@ -25,8 +25,9 @@ export async function getRealmLeaderboardData( const leaderboardUrl = (XP_DATA as XpDataType)[realmId]?.leaderboard if (!leaderboardUrl) { - throw new Error("Missing leaderboard url") + return null } + // xpData = await (await fetch(leaderboardUrl)).json() xpData = leaderboardMock } catch (error) { @@ -50,6 +51,10 @@ export async function getXpDataForRealmId( try { const xpLinks = (XP_DATA as XpDataType)[realmId]?.xp + if (!xpLinks || !xpLinks.length) { + return null + } + xpData = await Promise.all( xpLinks.map(async (url) => { // const data = await (await fetch(url)).json() From aaaa4bae78d29938a303d3ae8d784806c7aa2430 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 25 Oct 2023 12:55:06 +0200 Subject: [PATCH 05/39] Cleanup test xp drop files --- src/data/xp-data.json | 4 ++-- src/data/xp/4/1.json | 21 --------------------- src/data/xp/4/leaderboard.json | 12 ------------ src/shared/utils/xp.ts | 23 ++++++++++++++--------- 4 files changed, 16 insertions(+), 44 deletions(-) delete mode 100644 src/data/xp/4/1.json delete mode 100644 src/data/xp/4/leaderboard.json diff --git a/src/data/xp-data.json b/src/data/xp-data.json index a31a0c61f..bcac8addb 100644 --- a/src/data/xp-data.json +++ b/src/data/xp-data.json @@ -1,7 +1,7 @@ { "4": { - "xp": ["/data/xp/4/1.json"], - "leaderboard": "/data/xp/4/leaderboard.json" + "xp": [], + "leaderboard": null }, "7": { "xp": [], diff --git a/src/data/xp/4/1.json b/src/data/xp/4/1.json deleted file mode 100644 index ddc2b6e44..000000000 --- a/src/data/xp/4/1.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "totalAmount": "0x1e", - "merkleDistributor": "0x2F3b9De8b80294b6096dE7f036f621bC62ef7AE0", - "merkleRoot": "0x4208988e67d4ceac20ad6c3d1fc5002ff76c2d39c14a31718a07253eaa974ea9", - "claims": { - "0x5ca23512120e636f134b010e0b9a8c148d8ed2ea": { - "index": "0x0", - "amount": "0x0a", - "proof": [ - "0x94bcb09674cfc935fb2bc43eb7511610a691e685af49aaffde87152f4b173add" - ] - }, - "0x6e80164ea60673d64d5d6228beb684a1274bb017": { - "index": "0x1", - "amount": "0x14", - "proof": [ - "0x535faee8794ecc326a3d4a78dec4381ce261c52190f3b7b65a25c95b5207f737" - ] - } - } -} diff --git a/src/data/xp/4/leaderboard.json b/src/data/xp/4/leaderboard.json deleted file mode 100644 index 153e7c92d..000000000 --- a/src/data/xp/4/leaderboard.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "rank": 1, - "beneficiary": "0x6e80164ea60673d64d5d6228beb684a1274bb017", - "amount": "0x14" - }, - { - "rank": 2, - "beneficiary": "0x5ca23512120e636f134b010e0b9a8c148d8ed2ea", - "amount": "0x0a" - } -] diff --git a/src/shared/utils/xp.ts b/src/shared/utils/xp.ts index aa1136084..d377c531f 100644 --- a/src/shared/utils/xp.ts +++ b/src/shared/utils/xp.ts @@ -3,10 +3,6 @@ import { XpMerkleTree } from "shared/types/xp" import { isSameAddress, normalizeAddress } from "shared/utils/address" import XP_DATA from "../../data/xp-data.json" -// TODO: remove mocks -import leaderboardMock from "../../data/xp/4/leaderboard.json" -import xpMock from "../../data/xp/4/1.json" - type XpDataType = { [realmId: string]: { leaderboard: string | null; xp: string[] } } @@ -27,9 +23,12 @@ export async function getRealmLeaderboardData( if (!leaderboardUrl) { return null } - - // xpData = await (await fetch(leaderboardUrl)).json() - xpData = leaderboardMock + if (process.env.NODE_ENV === "development") { + // TODO: fix it - not working locally + xpData = (await import(`${leaderboardUrl}`)).default + } else { + xpData = await (await fetch(leaderboardUrl)).json() + } } catch (error) { // eslint-disable-next-line no-console console.warn("No XP data found for the realm id:", realmId, error) @@ -57,8 +56,14 @@ export async function getXpDataForRealmId( xpData = await Promise.all( xpLinks.map(async (url) => { - // const data = await (await fetch(url)).json() - const data = xpMock + let data + + if (process.env.NODE_ENV === "development") { + // TODO: fix it - not working locally + data = (await import(`${url}`)).default + } else { + data = await (await fetch(url)).json() + } return data }) From 78c002db4fde48dc00a1a68db775105a913f8250 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 25 Oct 2023 13:24:14 +0200 Subject: [PATCH 06/39] Renamed `data` folder to `assets` --- src/{data => assets}/questline-data.json | 0 src/{data => assets}/xp-data.json | 0 src/shared/constants/realms.ts | 2 +- src/shared/utils/xp.ts | 2 +- webpack.config.ts | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename src/{data => assets}/questline-data.json (100%) rename src/{data => assets}/xp-data.json (100%) diff --git a/src/data/questline-data.json b/src/assets/questline-data.json similarity index 100% rename from src/data/questline-data.json rename to src/assets/questline-data.json diff --git a/src/data/xp-data.json b/src/assets/xp-data.json similarity index 100% rename from src/data/xp-data.json rename to src/assets/xp-data.json diff --git a/src/shared/constants/realms.ts b/src/shared/constants/realms.ts index 4282eed3c..3036dc32f 100644 --- a/src/shared/constants/realms.ts +++ b/src/shared/constants/realms.ts @@ -1,6 +1,6 @@ import { RealmQuestlineData, RealmMapData } from "shared/types" import { realm19, realm22, realm4, realm7, realm9 } from "./realms-data" -import QUESTLINE_DATA from "../../data/questline-data.json" +import QUESTLINE_DATA from "../../assets/questline-data.json" // TODO: names and ids may change export const REALMS_WITH_CONTRACT_NAME: { diff --git a/src/shared/utils/xp.ts b/src/shared/utils/xp.ts index d377c531f..9ac70fcf0 100644 --- a/src/shared/utils/xp.ts +++ b/src/shared/utils/xp.ts @@ -1,7 +1,7 @@ import { LeaderboardItemData, XPLeaderboard } from "shared/types" import { XpMerkleTree } from "shared/types/xp" import { isSameAddress, normalizeAddress } from "shared/utils/address" -import XP_DATA from "../../data/xp-data.json" +import XP_DATA from "../../assets/xp-data.json" type XpDataType = { [realmId: string]: { leaderboard: string | null; xp: string[] } diff --git a/webpack.config.ts b/webpack.config.ts index 2d57ffa50..ba5bbc084 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -81,7 +81,7 @@ const config: Configuration = { Buffer: ["buffer", "Buffer"], }), new CopyPlugin({ - patterns: [{ from: "src/data/", to: "assets/" }], + patterns: [{ from: "src/assets/", to: "assets/" }], }), new DefinePlugin({ "process.env.VERSION": JSON.stringify(packageJson.version), From 8d8a259ca980358c1e12d1b3d92f986147b1762b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Wed, 25 Oct 2023 13:45:56 +0200 Subject: [PATCH 07/39] Update on the start of the season --- src/redux-state/selectors/island.ts | 3 +++ src/shared/constants/game.ts | 2 ++ src/shared/constants/index.ts | 1 + src/shared/contracts/game.ts | 8 ++------ src/ui/Island/RealmDetails/Quests/QuestsDetails.tsx | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 src/shared/constants/game.ts diff --git a/src/redux-state/selectors/island.ts b/src/redux-state/selectors/island.ts index d99e1a0ba..db7d42773 100644 --- a/src/redux-state/selectors/island.ts +++ b/src/redux-state/selectors/island.ts @@ -66,6 +66,9 @@ export const selectSeasonWeek = createSelector( if (isEndOfSeason) return durationInWeeks if (seasonStartTimestamp && durationInWeeks) { + const hasSeasonStarted = seasonStartTimestamp < Date.now() + if (!hasSeasonStarted) return 1 // if the start date is placed in the future, set season week to 1 + return Math.trunc((Date.now() - seasonStartTimestamp) / (7 * DAY) + 1) } diff --git a/src/shared/constants/game.ts b/src/shared/constants/game.ts new file mode 100644 index 000000000..b9c23b259 --- /dev/null +++ b/src/shared/constants/game.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export const SEASON_START_DATE = "2023-10-26" diff --git a/src/shared/constants/index.ts b/src/shared/constants/index.ts index aa3aa753f..07020921f 100644 --- a/src/shared/constants/index.ts +++ b/src/shared/constants/index.ts @@ -4,3 +4,4 @@ export { default as ROUTES } from "./routes" export * from "./time" export * from "./local-storage" export { default as LINKS } from "./external-links" +export * from "./game" diff --git a/src/shared/contracts/game.ts b/src/shared/contracts/game.ts index 3d94352ed..c52cc3816 100644 --- a/src/shared/contracts/game.ts +++ b/src/shared/contracts/game.ts @@ -1,6 +1,6 @@ import { providers, Contract } from "ethers" import { ReadTransactionBuilder, SeasonInfo } from "shared/types" -import { DAY } from "shared/constants" +import { DAY, SEASON_START_DATE } from "shared/constants" import { gameAbi, tahoDeployerAbi } from "./abi" export const getTahoDeployerContract: ReadTransactionBuilder< @@ -24,11 +24,7 @@ export const getSeasonInfo: ReadTransactionBuilder = async ( const seasonInfo = await gameContract.seasonInfo() const season = seasonInfo[0].toNumber() - // TODO: Delete when the season date has been set - const seasonStartTimestamp = seasonInfo[1].toNumber() - ? // Date requires ms, whereas block.timestamp is in s - seasonInfo[1].toNumber() * 1000 - : Date.now() + const seasonStartTimestamp = new Date(SEASON_START_DATE).getTime() const isInterSeason = seasonInfo[2] const durationInWeeks = Number(process.env.SEASON_LENGTH_IN_WEEKS ?? "8") diff --git a/src/ui/Island/RealmDetails/Quests/QuestsDetails.tsx b/src/ui/Island/RealmDetails/Quests/QuestsDetails.tsx index 4fd25dce2..72542ac75 100644 --- a/src/ui/Island/RealmDetails/Quests/QuestsDetails.tsx +++ b/src/ui/Island/RealmDetails/Quests/QuestsDetails.tsx @@ -43,7 +43,7 @@ export default function QuestsDetails({ {startDate && endDate && !isEndOfSeason && (
- {`${formatDate(startDate)} - ${formatDate(endDate)}`} + {`${formatDate(startDate)} - ${formatDate(endDate)}`}
)} From d8e8b46091bc3fde914e4b8c938c6759e055d154 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 25 Oct 2023 14:02:27 +0200 Subject: [PATCH 08/39] Fix disappearing XP claim congrats modal --- .../RealmDetails/RealmBanners/BannerRewards.tsx | 14 ++++++++++++-- src/ui/Island/RealmDetails/index.tsx | 10 ++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx b/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx index 1e092ab06..fe22bb54b 100644 --- a/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx +++ b/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx @@ -20,7 +20,13 @@ import { LINKS } from "shared/constants" const CLAIM_XP_TX_ID = "claim-xp" -export default function BannerRewards({ amount }: { amount: bigint }) { +export default function BannerRewards({ + amount, + setJustClaimed, +}: { + amount: bigint + setJustClaimed: (hasClaimed: boolean) => void +}) { const dispatch = useDappDispatch() const realmId = useDappSelector(selectDisplayedRealmId) const realm = useDappSelector((state) => selectRealmById(state, realmId)) @@ -37,6 +43,7 @@ export default function BannerRewards({ amount }: { amount: bigint }) { const claimTransaction = () => { if (realmId) { dispatch(claimXp({ id: CLAIM_XP_TX_ID, realmId })) + setJustClaimed(true) // to keep the banner + congratulation modal open } } @@ -159,7 +166,10 @@ export default function BannerRewards({ amount }: { amount: bigint }) { realmId={realmId} amount={parsedAmount} description={realm.xpToken.symbol} - close={() => setCongratulationsModalOpen(false)} + close={() => { + setCongratulationsModalOpen(false) + setJustClaimed(false) + }} /> )} diff --git a/src/ui/Island/RealmDetails/index.tsx b/src/ui/Island/RealmDetails/index.tsx index 68b94a1ac..178d0f141 100644 --- a/src/ui/Island/RealmDetails/index.tsx +++ b/src/ui/Island/RealmDetails/index.tsx @@ -44,6 +44,7 @@ function RealmDetailsBanner({ const rewardAmount = useDappSelector((state) => displayedRealmId ? selectUnclaimedXpSumById(state, displayedRealmId) : 0n ) + const [justClaimed, setJustClaimed] = useState(false) // used to keep claim rewards congrats modal open if (!isConnected) { return @@ -62,8 +63,13 @@ function RealmDetailsBanner({ ) } - if (isStakingRealm || (!isStakingRealm && rewardAmount > 0n)) { - return + if ( + isStakingRealm || + (!isStakingRealm && (rewardAmount > 0n || justClaimed)) + ) { + return ( + + ) } return null From 16cf8d336b59eb48e15c186d8e3d742ee32e6f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Wed, 25 Oct 2023 14:20:14 +0200 Subject: [PATCH 09/39] Get season start from env --- .env.defaults | 3 ++- src/shared/constants/game.ts | 2 -- src/shared/constants/index.ts | 1 - src/shared/contracts/game.ts | 12 ++++++++++-- 4 files changed, 12 insertions(+), 6 deletions(-) delete mode 100644 src/shared/constants/game.ts diff --git a/.env.defaults b/.env.defaults index 00c793e26..f36e3118e 100644 --- a/.env.defaults +++ b/.env.defaults @@ -19,5 +19,6 @@ ALLOW_TENDERLY_RESET="false" # Misc SEASON_LENGTH_IN_WEEKS=8 CONTRACT_DEPLOYMENT_BLOCK_NUMBER=553443 +SEASON_START_DATE="2023-10-26" SKIP_REACT_STRICT_MODE="false" -IS_COMING_SOON="true" +IS_COMING_SOON="true" \ No newline at end of file diff --git a/src/shared/constants/game.ts b/src/shared/constants/game.ts deleted file mode 100644 index b9c23b259..000000000 --- a/src/shared/constants/game.ts +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line import/prefer-default-export -export const SEASON_START_DATE = "2023-10-26" diff --git a/src/shared/constants/index.ts b/src/shared/constants/index.ts index 07020921f..aa3aa753f 100644 --- a/src/shared/constants/index.ts +++ b/src/shared/constants/index.ts @@ -4,4 +4,3 @@ export { default as ROUTES } from "./routes" export * from "./time" export * from "./local-storage" export { default as LINKS } from "./external-links" -export * from "./game" diff --git a/src/shared/contracts/game.ts b/src/shared/contracts/game.ts index c52cc3816..cf90df8eb 100644 --- a/src/shared/contracts/game.ts +++ b/src/shared/contracts/game.ts @@ -1,6 +1,6 @@ import { providers, Contract } from "ethers" import { ReadTransactionBuilder, SeasonInfo } from "shared/types" -import { DAY, SEASON_START_DATE } from "shared/constants" +import { DAY } from "shared/constants" import { gameAbi, tahoDeployerAbi } from "./abi" export const getTahoDeployerContract: ReadTransactionBuilder< @@ -24,7 +24,15 @@ export const getSeasonInfo: ReadTransactionBuilder = async ( const seasonInfo = await gameContract.seasonInfo() const season = seasonInfo[0].toNumber() - const seasonStartTimestamp = new Date(SEASON_START_DATE).getTime() + + // Season start data is accessible throught .env + // Date requires ms, whereas block.timestamp is in s + // const seasonStartTimestamp = seasonInfo[1].toNumber() * 1000 + + const seasonStartTimestamp = process.env.SEASON_START_DATE + ? new Date(process.env.SEASON_START_DATE).getTime() + : Date.now() + const isInterSeason = seasonInfo[2] const durationInWeeks = Number(process.env.SEASON_LENGTH_IN_WEEKS ?? "8") From d3c748b12eab11d7412f9a2afac7f41826e7e6bd Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 25 Oct 2023 14:20:19 +0200 Subject: [PATCH 10/39] Fix XP amount displayed on the congrats modal --- src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx b/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx index fe22bb54b..aeb8b1f51 100644 --- a/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx +++ b/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx @@ -30,6 +30,8 @@ export default function BannerRewards({ const dispatch = useDappDispatch() const realmId = useDappSelector(selectDisplayedRealmId) const realm = useDappSelector((state) => selectRealmById(state, realmId)) + const parsedAmount = separateThousandsByComma(bigIntToUserAmount(amount)) + const [savedAmount] = useState(() => parsedAmount) const [congratulationsModalOpen, setCongratulationsModalOpen] = useState(false) @@ -75,8 +77,6 @@ export default function BannerRewards({ if (!realmId || !realm) return null - const parsedAmount = separateThousandsByComma(bigIntToUserAmount(amount)) - return ( <> { setCongratulationsModalOpen(false) From 5f43fcc86a88aed85d44984ffdd69592ab608500 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 25 Oct 2023 15:50:27 +0200 Subject: [PATCH 11/39] Use a fallback provider for arbitrum sepolia --- src/shared/constants/chains.ts | 3 +++ src/shared/hooks/wallets.ts | 32 ++---------------------------- src/shared/services/transaction.ts | 16 +++++++++++++-- src/shared/utils/transactions.ts | 31 +++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/shared/constants/chains.ts b/src/shared/constants/chains.ts index 6289a77bf..0f35b6b2a 100644 --- a/src/shared/constants/chains.ts +++ b/src/shared/constants/chains.ts @@ -51,3 +51,6 @@ export const CONTRACT_DEPLOYMENT_BLOCK_NUMBER = process.env .CONTRACT_DEPLOYMENT_BLOCK_NUMBER ? parseInt(process.env.CONTRACT_DEPLOYMENT_BLOCK_NUMBER, 10) : undefined + +export const ARBITRUM_SEPOLIA_RPC_FALLBACK = + "https://arbitrum-sepolia.blockpi.network/v1/rpc/public" diff --git a/src/shared/hooks/wallets.ts b/src/shared/hooks/wallets.ts index bbbeb8755..6ac2d5b8a 100644 --- a/src/shared/hooks/wallets.ts +++ b/src/shared/hooks/wallets.ts @@ -1,6 +1,6 @@ import { useConnectWallet } from "@web3-onboard/react" import { useCallback, useEffect, useMemo, useState } from "react" -import { ethers, logger } from "ethers" +import { ethers } from "ethers" import { useDappDispatch, connectWalletGlobally, @@ -17,38 +17,10 @@ import { BALANCE_UPDATE_INTERVAL, LOCAL_STORAGE_WALLET, } from "shared/constants" -import { Logger, defineReadOnly } from "ethers/lib/utils" -import { Network } from "@ethersproject/networks" +import { StaticJsonBatchRpcProvider } from "shared/utils" import { useAssistant } from "./assistant" import { useInterval, useLocalStorageChange } from "./helpers" -class StaticJsonBatchRpcProvider extends ethers.providers.JsonRpcBatchProvider { - override async detectNetwork(): Promise { - let { network } = this - if (network == null) { - network = await super.detectNetwork() - - if (!network) { - logger.throwError( - "no network detected", - Logger.errors.UNKNOWN_ERROR, - {} - ) - } - - // If still not set, set it - // eslint-disable-next-line no-underscore-dangle - if (this._network == null) { - // A static network does not support "any" - defineReadOnly(this, "_network", network) - - this.emit("network", network, null) - } - } - return network - } -} - // To make it possible to start fetching blockchain data before the user // connects the wallet let's get the provider from the RPC URL export function useArbitrumProvider(): ethers.providers.JsonRpcBatchProvider { diff --git a/src/shared/services/transaction.ts b/src/shared/services/transaction.ts index 1b721fc47..5d7c3e928 100644 --- a/src/shared/services/transaction.ts +++ b/src/shared/services/transaction.ts @@ -1,12 +1,12 @@ import Emittery from "emittery" import { ethers } from "ethers" -import { ETHEREUM } from "shared/constants" +import { ARBITRUM_SEPOLIA_RPC_FALLBACK, ETHEREUM } from "shared/constants" import { WriteTransactionBuilder, ReadTransactionBuilder, TransactionProgressStatus, } from "shared/types" -import { normalizeAddress } from "shared/utils" +import { StaticJsonBatchRpcProvider, normalizeAddress } from "shared/utils" const ERROR_MESSAGE = { NO_ARBITRUM_PROVIDER: "Arbitrum provider is not ready, check RPC URL setup", @@ -136,6 +136,18 @@ class TransactionService { return response } catch (error) { + if ( + process.env.USE_ARBITRUM_SEPOLIA === "true" && + ARBITRUM_SEPOLIA_RPC_FALLBACK + ) { + const providerFallback = new StaticJsonBatchRpcProvider( + ARBITRUM_SEPOLIA_RPC_FALLBACK + ) + + const response = await transactionBuilder(providerFallback, data) + return response + } + // eslint-disable-next-line no-console console.error("Failed to read data from the blockchain", error) return null diff --git a/src/shared/utils/transactions.ts b/src/shared/utils/transactions.ts index 85e974bc9..ab7b7e108 100644 --- a/src/shared/utils/transactions.ts +++ b/src/shared/utils/transactions.ts @@ -1,3 +1,6 @@ +import { Network } from "@ethersproject/networks" +import { ethers, logger } from "ethers" +import { Logger, defineReadOnly } from "ethers/lib/utils" import { TransactionProgressStatus } from "shared/types" // eslint-disable-next-line import/prefer-default-export @@ -7,3 +10,31 @@ export const isTransactionPending = (status: TransactionProgressStatus) => status === TransactionProgressStatus.Sending export const getAllowanceTransactionID = (id: string) => `${id}_allowance` + +export class StaticJsonBatchRpcProvider extends ethers.providers + .JsonRpcBatchProvider { + override async detectNetwork(): Promise { + let { network } = this + if (network == null) { + network = await super.detectNetwork() + + if (!network) { + logger.throwError( + "no network detected", + Logger.errors.UNKNOWN_ERROR, + {} + ) + } + + // If still not set, set it + // eslint-disable-next-line no-underscore-dangle + if (this._network == null) { + // A static network does not support "any" + defineReadOnly(this, "_network", network) + + this.emit("network", network, null) + } + } + return network + } +} From ad1762ef9f1b53c628fad5db15e0a50c08599095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Wed, 25 Oct 2023 16:01:47 +0200 Subject: [PATCH 12/39] Add extra check for `AssistantFirstRealm` --- src/ui/Assistant/AssistantContent/AssistantFirstRealm.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ui/Assistant/AssistantContent/AssistantFirstRealm.tsx b/src/ui/Assistant/AssistantContent/AssistantFirstRealm.tsx index 0f4de13d4..559c594a1 100644 --- a/src/ui/Assistant/AssistantContent/AssistantFirstRealm.tsx +++ b/src/ui/Assistant/AssistantContent/AssistantFirstRealm.tsx @@ -1,9 +1,12 @@ import React from "react" import { useAssistant, useLocalStorageChange } from "shared/hooks" import { LOCAL_STORAGE_VISITED_REALM } from "shared/constants" +import { selectStakingRealmId, useDappSelector } from "redux-state" import AssistantContent from "." export default function AssistantFirstRealm() { + const isStakedInRealm = useDappSelector(selectStakingRealmId) + const { updateAssistant, assistantVisible } = useAssistant() const { updateStorage } = useLocalStorageChange( LOCAL_STORAGE_VISITED_REALM @@ -17,7 +20,7 @@ export default function AssistantFirstRealm() { return ( <>
How to choose a realm?
From bb853c0315337c1f26643236cdcff19efba74332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Wed, 25 Oct 2023 16:19:55 +0200 Subject: [PATCH 13/39] Adjust XP allocation --- src/ui/Island/RealmDetails/Quests/QuestsDetails.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ui/Island/RealmDetails/Quests/QuestsDetails.tsx b/src/ui/Island/RealmDetails/Quests/QuestsDetails.tsx index 72542ac75..461b27725 100644 --- a/src/ui/Island/RealmDetails/Quests/QuestsDetails.tsx +++ b/src/ui/Island/RealmDetails/Quests/QuestsDetails.tsx @@ -25,6 +25,10 @@ export default function QuestsDetails({ const isEndOfSeason = useDappSelector(selectIsEndOfSeason) const realm = useDappSelector((state) => selectRealmById(state, realmId)) + // 1_000_000 is hard-coded here since the season start does not match with + // the season start in contracts (one week difference) + const adjustedXpAllocatable = Number(realm?.xpAllocatable) - 1_000_000 + return (
@@ -57,7 +61,7 @@ export default function QuestsDetails({ color="var(--primary-p1-100)" /> {separateThousandsByComma( - Math.round(parseFloat(realm?.xpAllocatable ?? "0")) + Math.round(adjustedXpAllocatable > 0 ? adjustedXpAllocatable : 0) )} {tokenSymbol} From 99f653d022c730355e3939774171e16a7d9a072d Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 25 Oct 2023 16:46:30 +0200 Subject: [PATCH 14/39] Save provider fallback in `TransactionService` --- src/redux-state/thunks/wallet.ts | 16 +++++++++ src/shared/hooks/wallets.ts | 52 ++++++++++++++++++++++++++++-- src/shared/services/transaction.ts | 35 ++++++++++++-------- src/shared/utils/transactions.ts | 31 ------------------ 4 files changed, 86 insertions(+), 48 deletions(-) diff --git a/src/redux-state/thunks/wallet.ts b/src/redux-state/thunks/wallet.ts index b82d7f3a8..56b2ec950 100644 --- a/src/redux-state/thunks/wallet.ts +++ b/src/redux-state/thunks/wallet.ts @@ -58,6 +58,22 @@ export const connectArbitrumProvider = createDappAsyncThunk( } ) +export const connectArbitrumProviderFallback = createDappAsyncThunk( + "wallet/connectArbitrumProviderFallback", + async ( + { + arbitrumProviderFallback, + }: { + arbitrumProviderFallback: ethers.providers.JsonRpcBatchProvider + }, + { extra: { transactionService } } + ) => { + await transactionService.setArbitrumProviderFallback( + arbitrumProviderFallback + ) + } +) + export const prepareForWalletChange = createDappAsyncThunk( "wallet/prepareForWalletChange", async (_, { dispatch }) => { diff --git a/src/shared/hooks/wallets.ts b/src/shared/hooks/wallets.ts index 6ac2d5b8a..abe3be68f 100644 --- a/src/shared/hooks/wallets.ts +++ b/src/shared/hooks/wallets.ts @@ -1,6 +1,6 @@ import { useConnectWallet } from "@web3-onboard/react" import { useCallback, useEffect, useMemo, useState } from "react" -import { ethers } from "ethers" +import { ethers, logger } from "ethers" import { useDappDispatch, connectWalletGlobally, @@ -11,16 +11,46 @@ import { resetBalances, connectArbitrumProvider, selectDisplayedRealmId, + connectArbitrumProviderFallback, } from "redux-state" import { ARBITRUM_SEPOLIA, + ARBITRUM_SEPOLIA_RPC_FALLBACK, BALANCE_UPDATE_INTERVAL, LOCAL_STORAGE_WALLET, } from "shared/constants" -import { StaticJsonBatchRpcProvider } from "shared/utils" +import { Network } from "@ethersproject/networks" +import { Logger, defineReadOnly } from "ethers/lib/utils" import { useAssistant } from "./assistant" import { useInterval, useLocalStorageChange } from "./helpers" +class StaticJsonBatchRpcProvider extends ethers.providers.JsonRpcBatchProvider { + override async detectNetwork(): Promise { + let { network } = this + if (network == null) { + network = await super.detectNetwork() + + if (!network) { + logger.throwError( + "no network detected", + Logger.errors.UNKNOWN_ERROR, + {} + ) + } + + // If still not set, set it + // eslint-disable-next-line no-underscore-dangle + if (this._network == null) { + // A static network does not support "any" + defineReadOnly(this, "_network", network) + + this.emit("network", network, null) + } + } + return network + } +} + // To make it possible to start fetching blockchain data before the user // connects the wallet let's get the provider from the RPC URL export function useArbitrumProvider(): ethers.providers.JsonRpcBatchProvider { @@ -32,6 +62,18 @@ export function useArbitrumProvider(): ethers.providers.JsonRpcBatchProvider { return arbitrumProvider } +export function useArbitrumProviderFallback(): ethers.providers.JsonRpcBatchProvider | null { + const arbitrumProviderFallback = useMemo( + () => + process.env.USE_ARBITRUM_SEPOLIA === "true" + ? new StaticJsonBatchRpcProvider(ARBITRUM_SEPOLIA_RPC_FALLBACK) + : null, + [] + ) + + return arbitrumProviderFallback +} + // Signing transaction is always done with the signer from the wallet export function useArbitrumSigner(): ethers.providers.JsonRpcSigner | null { const [{ wallet }] = useConnectWallet() @@ -67,6 +109,7 @@ export function useBalanceFetch() { export function useWallet() { const [{ wallet }] = useConnectWallet() const arbitrumProvider = useArbitrumProvider() + const arbitrumProviderFallback = useArbitrumProviderFallback() const arbitrumSigner = useArbitrumSigner() const dispatch = useDappDispatch() @@ -78,7 +121,10 @@ export function useWallet() { if (arbitrumProvider) { dispatch(connectArbitrumProvider({ arbitrumProvider })) } - }, [arbitrumProvider, dispatch]) + if (arbitrumProviderFallback) { + dispatch(connectArbitrumProviderFallback({ arbitrumProviderFallback })) + } + }, [arbitrumProvider, arbitrumProviderFallback, dispatch]) useEffect(() => { if (address && arbitrumSigner) { diff --git a/src/shared/services/transaction.ts b/src/shared/services/transaction.ts index 5d7c3e928..e08f0df30 100644 --- a/src/shared/services/transaction.ts +++ b/src/shared/services/transaction.ts @@ -1,12 +1,12 @@ import Emittery from "emittery" import { ethers } from "ethers" -import { ARBITRUM_SEPOLIA_RPC_FALLBACK, ETHEREUM } from "shared/constants" +import { ETHEREUM } from "shared/constants" import { WriteTransactionBuilder, ReadTransactionBuilder, TransactionProgressStatus, } from "shared/types" -import { StaticJsonBatchRpcProvider, normalizeAddress } from "shared/utils" +import { normalizeAddress } from "shared/utils" const ERROR_MESSAGE = { NO_ARBITRUM_PROVIDER: "Arbitrum provider is not ready, check RPC URL setup", @@ -24,6 +24,8 @@ type Events = { class TransactionService { arbitrumProvider: ethers.providers.Provider | null = null + arbitrumProviderFallback: ethers.providers.Provider | null = null + arbitrumSigner: ethers.providers.JsonRpcSigner | null = null ethereumProvider: ethers.providers.Provider | null = null @@ -59,6 +61,12 @@ class TransactionService { this.arbitrumProvider = providerOrNull } + async setArbitrumProviderFallback( + providerOrNull: ethers.providers.Provider | null + ) { + this.arbitrumProviderFallback = providerOrNull + } + async setArbitrumSigner(signerOrNull: ethers.providers.JsonRpcSigner | null) { this.arbitrumSigner = signerOrNull } @@ -125,29 +133,28 @@ class TransactionService { async read( transactionBuilder: ReadTransactionBuilder, - data: T + data: T, + providerFallback?: ethers.providers.Provider ): Promise { try { - if (!this.arbitrumProvider) { + const provider = providerFallback ?? this.arbitrumProvider + + if (!provider) { throw new Error(ERROR_MESSAGE.NO_ARBITRUM_PROVIDER) } - const response = await transactionBuilder(this.arbitrumProvider, data) + const response = await transactionBuilder(provider, data) return response } catch (error) { - if ( - process.env.USE_ARBITRUM_SEPOLIA === "true" && - ARBITRUM_SEPOLIA_RPC_FALLBACK - ) { - const providerFallback = new StaticJsonBatchRpcProvider( - ARBITRUM_SEPOLIA_RPC_FALLBACK + if (this.arbitrumProviderFallback) { + const response = this.read( + transactionBuilder, + data, + this.arbitrumProviderFallback ) - - const response = await transactionBuilder(providerFallback, data) return response } - // eslint-disable-next-line no-console console.error("Failed to read data from the blockchain", error) return null diff --git a/src/shared/utils/transactions.ts b/src/shared/utils/transactions.ts index ab7b7e108..85e974bc9 100644 --- a/src/shared/utils/transactions.ts +++ b/src/shared/utils/transactions.ts @@ -1,6 +1,3 @@ -import { Network } from "@ethersproject/networks" -import { ethers, logger } from "ethers" -import { Logger, defineReadOnly } from "ethers/lib/utils" import { TransactionProgressStatus } from "shared/types" // eslint-disable-next-line import/prefer-default-export @@ -10,31 +7,3 @@ export const isTransactionPending = (status: TransactionProgressStatus) => status === TransactionProgressStatus.Sending export const getAllowanceTransactionID = (id: string) => `${id}_allowance` - -export class StaticJsonBatchRpcProvider extends ethers.providers - .JsonRpcBatchProvider { - override async detectNetwork(): Promise { - let { network } = this - if (network == null) { - network = await super.detectNetwork() - - if (!network) { - logger.throwError( - "no network detected", - Logger.errors.UNKNOWN_ERROR, - {} - ) - } - - // If still not set, set it - // eslint-disable-next-line no-underscore-dangle - if (this._network == null) { - // A static network does not support "any" - defineReadOnly(this, "_network", network) - - this.emit("network", network, null) - } - } - return network - } -} From 9049383164e95d631fa4af0d8641af4217fb1a8d Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 25 Oct 2023 17:07:17 -0400 Subject: [PATCH 15/39] Add PostHog pageview tracking The current tracking is just that the user hit the page. It's double- tracking on dev, need to check that this doesn't happen in prod. --- package.json | 5 +++-- src/index.tsx | 48 +++++++++++++++++++++++++++++++++++++++++++----- yarn.lock | 12 ++++++++++++ 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 081f56472..fc55a4afe 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "version": "1.0.0", "prettier": "@thesis/prettier-config", "scripts": { - "start": "yarn write-types && patch-package && webpack serve --mode=development", + "start": "yarn write-types && patch-package && webpack serve --mode=development --https", "build": "yarn write-types && patch-package && webpack build --mode=production", "write-types": "node scripts/write-taho-types.mjs", "lint:eslint": "eslint .", @@ -15,6 +15,7 @@ "postinstall": "patch-package" }, "dependencies": { + "@ethersproject/networks": "^5.7.1", "@react-spring/konva": "^9.7.3", "@react-spring/web": "^9.7.3", "@reduxjs/toolkit": "^1.9.5", @@ -29,7 +30,6 @@ "crypto-browserify": "^3.12.0", "emittery": "^1.0.1", "ethers": "^5", - "@ethersproject/networks": "^5.7.1", "gifler": "^0.1.0", "https-browserify": "^1.0.0", "konva": "^9.2.0", @@ -77,6 +77,7 @@ "eslint-plugin-import": "^2.28.1", "fork-ts-checker-webpack-plugin": "^8.0.0", "html-webpack-plugin": "^5.5.3", + "posthog-js": "^1.85.3", "prettier": "^2.8.1", "typescript": "^5.0.2", "webpack": "^5.88.1", diff --git a/src/index.tsx b/src/index.tsx index eb61470f1..2621223e4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,11 @@ -import React from "react" +import React, { ReactNode, useEffect } from "react" import ReactDOM from "react-dom/client" -import { BrowserRouter as Router, Switch, Route } from "react-router-dom" +import { + BrowserRouter as Router, + Switch, + Route, + useLocation, +} from "react-router-dom" import { Web3OnboardProvider } from "@web3-onboard/react" import { Provider } from "react-redux" import { @@ -32,8 +37,23 @@ import { ROUTES } from "shared/constants" import Onboarding from "ui/Onboarding" import FullPageLoader from "shared/components/FullPageLoader" import MobileScreen from "ui/MobileScreen" +// Unfortunately the PostHog React package structure does not play nice with +// no-extraneous-dependencies. +// eslint-disable-next-line import/no-extraneous-dependencies +import { PostHogProvider, usePostHog } from "posthog-js/react" import reduxStore from "./redux-state" +function TrackEvents({ children }: { children: ReactNode[] }) { + const location = useLocation() + const posthog = usePostHog() + + useEffect(() => { + posthog?.capture("$pageview", { url: location.pathname }) + }, []) + + return children +} + function DApp() { const islandMode = useDappSelector(selectIslandMode) const { isConnected } = useConnect() @@ -56,7 +76,7 @@ function DApp() { {(!walletOnboarded || !isConnected) && } {walletOnboarded && isConnected && ( - <> + @@ -76,7 +96,7 @@ function DApp() {