-
+
)
}
diff --git a/src/shared/components/RealmModal/index.tsx b/src/shared/components/RealmModal/index.tsx
index ffd9269a3..67370a882 100644
--- a/src/shared/components/RealmModal/index.tsx
+++ b/src/shared/components/RealmModal/index.tsx
@@ -45,15 +45,8 @@ export default function RealmModal({
const [props] = useSpring(
() => ({
- from: {
- transform: "translate3d(0,38.5%,0) scale(0.8)",
- opacity: 0,
- },
- to: {
- transform: "translate3d(0,0,0) scale(1)",
- opacity: 1,
- position: "relative",
- },
+ from: { opacity: 0 },
+ to: { opacity: 1, position: "relative" },
config: { duration: 300, easing: easings.easeInOutCubic },
}),
[]
diff --git a/src/shared/constants/chains.ts b/src/shared/constants/chains.ts
index 6289a77bf..7b712ba6f 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 =
+ process.env.ARBITRUM_RPC_FALLBACK_URL
diff --git a/src/shared/constants/external-links.ts b/src/shared/constants/external-links.ts
index b893453d6..1546d417e 100644
--- a/src/shared/constants/external-links.ts
+++ b/src/shared/constants/external-links.ts
@@ -7,4 +7,5 @@ export default {
DOCS: "https://docs.taho.xyz/app/",
BRAVE_SUPPORT:
"https://support.brave.com/hc/en-us/articles/360023646212-How-do-I-configure-global-and-site-specific-Shields-settings-",
+ FEEDBACK: "https://tahowallet.typeform.com/subscapebeta",
}
diff --git a/src/shared/constants/game.ts b/src/shared/constants/game.ts
new file mode 100644
index 000000000..6c872dd2d
--- /dev/null
+++ b/src/shared/constants/game.ts
@@ -0,0 +1,2 @@
+// eslint-disable-next-line import/prefer-default-export
+export const WEEKLY_XP_ALLOCATION = 1_000_000
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/constants/realms-data.ts b/src/shared/constants/realms-data.ts
index 8e89480d0..a6cd44754 100644
--- a/src/shared/constants/realms-data.ts
+++ b/src/shared/constants/realms-data.ts
@@ -68,7 +68,7 @@ export const realm4 = {
},
],
realmType: "realm",
- color: "#C6CB60",
+ color: "#D1F5F5",
labelX: 380,
labelY: 216.5,
partnerLogo: partners.gitcoin,
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/constants/routes.ts b/src/shared/constants/routes.ts
index 56215e1c2..d32bc80a8 100644
--- a/src/shared/constants/routes.ts
+++ b/src/shared/constants/routes.ts
@@ -12,5 +12,4 @@ export default {
},
REFERRALS: "/referrals",
LP: "/lp",
- FEEDBACK: "https://tahowallet.typeform.com/subscapebeta",
}
diff --git a/src/shared/contracts/game.ts b/src/shared/contracts/game.ts
index 3d94352ed..cf90df8eb 100644
--- a/src/shared/contracts/game.ts
+++ b/src/shared/contracts/game.ts
@@ -24,11 +24,15 @@ 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
+
+ // 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")
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/hooks/wallets.ts b/src/shared/hooks/wallets.ts
index bbbeb8755..abe3be68f 100644
--- a/src/shared/hooks/wallets.ts
+++ b/src/shared/hooks/wallets.ts
@@ -11,14 +11,16 @@ 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 { Logger, defineReadOnly } from "ethers/lib/utils"
import { Network } from "@ethersproject/networks"
+import { Logger, defineReadOnly } from "ethers/lib/utils"
import { useAssistant } from "./assistant"
import { useInterval, useLocalStorageChange } from "./helpers"
@@ -60,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()
@@ -95,6 +109,7 @@ export function useBalanceFetch() {
export function useWallet() {
const [{ wallet }] = useConnectWallet()
const arbitrumProvider = useArbitrumProvider()
+ const arbitrumProviderFallback = useArbitrumProviderFallback()
const arbitrumSigner = useArbitrumSigner()
const dispatch = useDappDispatch()
@@ -106,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 1b721fc47..4911b25ec 100644
--- a/src/shared/services/transaction.ts
+++ b/src/shared/services/transaction.ts
@@ -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,17 +133,30 @@ 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) {
+ // Use providerFallback only once.
+ // The next attempt should return null.
+ if (this.arbitrumProviderFallback && !providerFallback) {
+ const response = await this.read(
+ transactionBuilder,
+ data,
+ this.arbitrumProviderFallback
+ )
+ 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/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..9ac70fcf0 100644
--- a/src/shared/utils/xp.ts
+++ b/src/shared/utils/xp.ts
@@ -1,103 +1,91 @@
-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 "../../assets/xp-data.json"
-type DynamicXPMerkleTreeImport = {
- default: XpMerkleTree
+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) {
+ return null
+ }
+ 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)
+ 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")
+export async function getXpDataForRealmId(
+ realmId: string
+): Promise {
+ if (!realmId) {
+ throw new Error("Missing realm id")
}
- 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)
- }
- }
- return xpData && (xpData as XpMerkleTree)
-}
+ let xpData: null | XpMerkleTree[] = null
-export async function getUserXpByMerkleRoot(
- account: string,
- url: string
-): Promise {
- const xpItemByMerkleRoot: XpByMerkleRoot = {}
- const normalizedAddress = normalizeAddress(account)
+ try {
+ const xpLinks = (XP_DATA as XpDataType)[realmId]?.xp
- const xpData = await getXpData(url)
-
- if (xpData) {
- try {
- const { merkleRoot } = xpData
- const userClaim = xpData.claims[normalizedAddress]
-
- if (userClaim) {
- xpItemByMerkleRoot[merkleRoot] = userClaim
- }
- } catch (error) {
- // eslint-disable-next-line no-console
- console.warn("Not a correct structure for XP data")
+ if (!xpLinks || !xpLinks.length) {
+ return null
}
+
+ xpData = await Promise.all(
+ xpLinks.map(async (url) => {
+ 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
+ })
+ )
+ } 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/Assistant/AssistantContent/AssistantFirstRealm.tsx b/src/ui/Assistant/AssistantContent/AssistantFirstRealm.tsx
index 0f4de13d4..0ce6020c3 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,19 +20,20 @@ export default function AssistantFirstRealm() {
return (
<>
- How to choose a realm?
-
- Rewards are shared based on population and Quest completion.
-
+ Why join a Realm?
- So choose your realm based on quests you want and can complete.
+ Realm Citizens can complete Quests, earn{" "}
+ $XP, and rank on the Realm's{" "}
+ Leaderboard. Prizes await Citizens that top the
+ boards.
- But also be mind-full, that a popular realm might yield less
- individual rewards
+ Survey our 5 Beta Realms and choose the one you can dominate. The more
+ $TAHO you stake, the more $XP you earn. The more Citizens in each
+ Realm, the more your weekly $XP reward gets diluted.
>
)
diff --git a/src/ui/Assistant/AssistantContent/AssistantQuests.tsx b/src/ui/Assistant/AssistantContent/AssistantQuests.tsx
index 4827ae080..2d8877538 100644
--- a/src/ui/Assistant/AssistantContent/AssistantQuests.tsx
+++ b/src/ui/Assistant/AssistantContent/AssistantQuests.tsx
@@ -1,18 +1,10 @@
import React from "react"
import Icon from "shared/components/Icon"
import starIcon from "shared/assets/icons/star-2.svg"
-import {
- selectRealmById,
- selectStakingRealmId,
- useDappSelector,
-} from "redux-state"
import { useAssistant } from "shared/hooks"
import AssistantContent from "."
export default function AssistantQuests() {
- const stakedRealm = useDappSelector(selectStakingRealmId)
- const realm = useDappSelector((state) => selectRealmById(state, stakedRealm))
-
const { updateAssistant, assistantVisible } = useAssistant()
return (
@@ -21,18 +13,7 @@ export default function AssistantQuests() {
isVisible={assistantVisible("quests")}
close={() => updateAssistant({ visible: false, type: "default" })}
>
-
- You are now a Citizen of {realm?.name}, I think you'll like it
- here!
-
-
- {realm && (
-
- Population of {realm.population > 0 ? realm.population - 1 : 0} +
- 1 (You!)
-
- )}
-
+ Congrats Citizen!
-
- Let's start earning XP
-
by completing Quests
-
-
-
-
- You can see this week's Quests under the{" "}
- Quests bar. You'll be able to redeem XP in the
- future for rewards.
-
+
Now it's time to complete Quests and earn $XP.
+
+ Check out the Quests tab for details.
+
+
+ Your Guardians will airdrop you{" "}
+ $XP every Tuesday... so stay tuned and let us know on
+ Discord if you have any questions.
+