diff --git a/cfg/cspell-dictionary.txt b/cfg/cspell-dictionary.txt index 744394248..e25d3c194 100644 --- a/cfg/cspell-dictionary.txt +++ b/cfg/cspell-dictionary.txt @@ -25,6 +25,7 @@ eswatini faso fleur futuna +galxe gcloud hamsa headlessui diff --git a/src/typescript/.env.example b/src/typescript/.env.example index fadac70d0..996ef3868 100644 --- a/src/typescript/.env.example +++ b/src/typescript/.env.example @@ -1,4 +1,3 @@ - # To run your own local node for e2e testing, set this to "false" START_LOCAL_NODE_FOR_TEST="true" @@ -19,3 +18,9 @@ NEXT_PUBLIC_INTEGRATOR_ADDRESS="0xbabe8f471b7f4744502b1397530bafe80e3731b358c0df # The BPS fee rate for each swap, liquidity provision, or liquidity removal. NEXT_PUBLIC_INTEGRATOR_FEE_RATE_BPS="125" + +# If false, no allowlist is set up +# If true, NEXT_PUBLIC_GALXE_CAMPAIGN_ID (and|or) ALLOWLISTER3K_URL needs to be set +NEXT_PUBLIC_IS_ALLOWLIST_ENABLED="false" +GALXE_CAMPAIGN_ID="" +ALLOWLISTER3K_URL="" diff --git a/src/typescript/frontend/src/lib/build-env.ts b/src/typescript/frontend/src/lib/build-env.ts deleted file mode 100644 index 8abc145d3..000000000 --- a/src/typescript/frontend/src/lib/build-env.ts +++ /dev/null @@ -1,11 +0,0 @@ -import "server-only"; - -let REVALIDATION_TIME: number; - -if (process.env.REVALIDATION_TIME) { - REVALIDATION_TIME = Number(process.env.REVALIDATION_TIME); -} else { - if (process.env.NODE) throw new Error("Environment variable REVALIDATION_TIME is undefined."); -} - -export { REVALIDATION_TIME }; diff --git a/src/typescript/frontend/src/lib/env.ts b/src/typescript/frontend/src/lib/env.ts index fc85e34c8..fadefdfd3 100644 --- a/src/typescript/frontend/src/lib/env.ts +++ b/src/typescript/frontend/src/lib/env.ts @@ -4,6 +4,8 @@ let APTOS_NETWORK: Network; let INTEGRATOR_ADDRESS: string; let INTEGRATOR_FEE_RATE_BPS: number; +const IS_ALLOWLIST_ENABLED: boolean = process.env.NEXT_PUBLIC_IS_ALLOWLIST_ENABLED === "true"; + if (process.env.NEXT_PUBLIC_APTOS_NETWORK) { const network = process.env.NEXT_PUBLIC_APTOS_NETWORK; if (["mainnet", "testnet", "devnet", "local", "custom"].includes(network)) { @@ -35,4 +37,9 @@ if (vercel && local) { ); } -export { APTOS_NETWORK, INTEGRATOR_ADDRESS, INTEGRATOR_FEE_RATE_BPS }; +export { + APTOS_NETWORK, + INTEGRATOR_ADDRESS, + INTEGRATOR_FEE_RATE_BPS, + IS_ALLOWLIST_ENABLED, +}; diff --git a/src/typescript/frontend/src/lib/server-env.ts b/src/typescript/frontend/src/lib/server-env.ts new file mode 100644 index 000000000..e5d4eb655 --- /dev/null +++ b/src/typescript/frontend/src/lib/server-env.ts @@ -0,0 +1,19 @@ +import "server-only"; +import { IS_ALLOWLIST_ENABLED } from "./env"; + +let REVALIDATION_TIME: number; + +if (process.env.REVALIDATION_TIME) { + REVALIDATION_TIME = Number(process.env.REVALIDATION_TIME); +} else { + if (process.env.NODE) throw new Error("Environment variable REVALIDATION_TIME is undefined."); +} + +const ALLOWLISTER3K_URL: string | undefined = process.env.ALLOWLISTER3K_URL; +const GALXE_CAMPAIGN_ID: string | undefined = process.env.GALXE_CAMPAIGN_ID; + +if (IS_ALLOWLIST_ENABLED && ALLOWLISTER3K_URL === undefined && GALXE_CAMPAIGN_ID === undefined) { + throw new Error("Allowlist is enabled but no allowlist provider is set."); +} + +export { ALLOWLISTER3K_URL, GALXE_CAMPAIGN_ID, REVALIDATION_TIME }; diff --git a/src/typescript/frontend/src/lib/utils/allowlist.ts b/src/typescript/frontend/src/lib/utils/allowlist.ts new file mode 100644 index 000000000..6b5bcfe4d --- /dev/null +++ b/src/typescript/frontend/src/lib/utils/allowlist.ts @@ -0,0 +1,60 @@ +import "server-only"; + +import { IS_ALLOWLIST_ENABLED } from "lib/env"; +import { ALLOWLISTER3K_URL, GALXE_CAMPAIGN_ID } from "lib/build-env"; + +export const GALXE_URL = "https://graphigo.prd.galaxy.eco/query"; + +// Checks if the given address is allow listed either in Galxe or in Allowlister3000. +// +// If IS_ALLOWLIST_ENABLED is not truthy, the function returns true. +// +// The address can be provided either as "0xabc" or directly "abc". +export async function isAllowListed(address: string): Promise { + if (!IS_ALLOWLIST_ENABLED) { + return true; + } + + if (!address.startsWith("0x")) { + address = `0x${address}`; + } + + if (GALXE_CAMPAIGN_ID !== undefined) { + const condition = await fetch(GALXE_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ + query: `{ + campaign(id: "${GALXE_CAMPAIGN_ID}") { + whitelistInfo(address: "${address}") { + maxCount + usedCount + claimedLoyaltyPoints + currentPeriodMaxLoyaltyPoints + currentPeriodClaimedLoyaltyPoints + } + } + }`, + }), + }) + .then((r) => r.json()) + .then((data) => data.data.campaign.whitelistInfo.usedCount === 1); + if (condition) { + return true; + } + } + + if (ALLOWLISTER3K_URL !== undefined) { + const condition = await fetch(`${ALLOWLISTER3K_URL}/${address}`) + .then((r) => r.text()) + .then((data) => data === "true"); + if (condition) { + return true; + } + } + + return false; +}