diff --git a/.gitignore b/.gitignore index 7763f32..3695d0e 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,4 @@ front/.vercel # typescript front/*.tsbuildinfo +.npmrc diff --git a/README.md b/README.md index c98f8d7..0dc3095 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Implementation of a general voting system using the $META token. The last version of the deployed contract, in Near `mainnet`, is in the `stable` branch. -Current stable version: [**v0.1.0**](https://github.com/Narwallets/meta-vote/releases/tag/v0.1.0) +Current stable version: [**v0.1.1**](https://github.com/Narwallets/meta-vote/releases/tag/v0.1.1) Check all releases in [Meta Vote Releases](https://github.com/Narwallets/meta-vote/releases). To get the stable version, run the commands: @@ -18,7 +18,7 @@ git clone https://github.com/Narwallets/meta-vote.git cd meta-vote git fetch origin --tags -git checkout tags/v0.1.0 -b stable +git checkout tags/v0.1.1 -b stable ``` ## Type Notation @@ -67,9 +67,24 @@ pub fn new( ) -> Self; ``` +The best way to deploy Meta Vote is using the scripts for `mainnet` and `testnet`: + +- [main_deploy.sh](contract/scripts/main_deploy.sh) +- [test_deploy.sh](contract/scripts/test_deploy.sh) + +## Getting a list of paginated Voters + +Getting all the voters with pagination using `testnet`: + +```rs +NEAR_ENV=testnet near view metavote.testnet get_voters '{"from_index": 0, "limit": 100}' +``` + ## View Contract Functions ```rs +pub fn get_voters(&self, from_index: u32, limit: u32) -> Vec; + pub fn get_balance(&self, voter_id: VoterId) -> U128; pub fn get_locked_balance(&self, voter_id: VoterId) -> U128; diff --git a/contract/meta-vote-contract/Cargo.toml b/contract/meta-vote-contract/Cargo.toml index f8291c1..1dcff20 100644 --- a/contract/meta-vote-contract/Cargo.toml +++ b/contract/meta-vote-contract/Cargo.toml @@ -10,4 +10,4 @@ crate-type = ["cdylib", "rlib"] [dependencies] near-sdk = "4.0.0" near-contract-standards = "4.0.0" -uint = "0.9.3" \ No newline at end of file +uint = "0.9.3" diff --git a/contract/meta-vote-contract/src/lib.rs b/contract/meta-vote-contract/src/lib.rs index 924559b..c094572 100644 --- a/contract/meta-vote-contract/src/lib.rs +++ b/contract/meta-vote-contract/src/lib.rs @@ -7,7 +7,7 @@ use near_sdk::json_types::U128; use near_sdk::{env, log, near_bindgen, AccountId, Balance, PanicOnDefault, require}; use types::*; use utils::{generate_hash_id, get_current_epoch_millis}; -use voter::Voter; +use voter::{Voter, VoterJSON}; mod constants; mod deposit; @@ -555,6 +555,21 @@ impl MetaVoteContract { /* View functions */ /**********************/ + pub fn get_voters(&self, from_index: u32, limit: u32) -> Vec { + let keys = self.voters.keys_as_vector(); + let voters_len = keys.len() as u64; + let start = from_index as u64; + let limit = limit as u64; + + let mut results = Vec::::new(); + for index in start..std::cmp::min(start + limit, voters_len) { + let voter_id = keys.get(index).unwrap(); + let voter = self.voters.get(&voter_id).unwrap(); + results.push(voter.to_json(voter_id)); + } + results + } + pub fn get_balance(&self, voter_id: VoterId) -> U128 { let voter = self.internal_get_voter(&voter_id); let balance = voter.balance + voter.sum_unlocked(); @@ -631,8 +646,9 @@ impl MetaVoteContract { &self, contract_address: ContractAddress ) -> Vec { - let objects = self.votes.get(&contract_address) - .expect("Contract Address not in Meta Vote."); + let objects = self.votes.get(&contract_address) + .unwrap_or(UnorderedMap::new(StorageKey::Votes)); + let mut results: Vec = Vec::new(); for (id, voting_power) in objects.iter() { results.push( diff --git a/contract/meta-vote-contract/src/locking_position.rs b/contract/meta-vote-contract/src/locking_position.rs index ea130ba..1d7bf90 100644 --- a/contract/meta-vote-contract/src/locking_position.rs +++ b/contract/meta-vote-contract/src/locking_position.rs @@ -56,7 +56,7 @@ impl LockingPosition { unlocking_started_at: self.unlocking_started_at, is_unlocked: self.is_unlocked(), is_unlocking: self.is_unlocking(), - is_locked: self.is_locked(), + is_locked: self.is_locked() } } } diff --git a/contract/meta-vote-contract/src/tests/mod.rs b/contract/meta-vote-contract/src/tests/mod.rs index 8271803..e7df525 100644 --- a/contract/meta-vote-contract/src/tests/mod.rs +++ b/contract/meta-vote-contract/src/tests/mod.rs @@ -48,6 +48,13 @@ fn test_single_deposit() { msg.parse::().unwrap() ); assert_eq!(vote_power, voter.voting_power, "Incorrect voting power calculation!"); + + let voters = contract.get_voters(0, 10); + assert_eq!(voters.len(), 1); + let locking_position = &voters.first().unwrap().locking_positions; + assert_eq!(locking_position.len(), 1); + let vote_position = &voters.first().unwrap().vote_positions; + assert_eq!(vote_position.len(), 0); } #[test] @@ -105,6 +112,13 @@ fn test_multiple_deposit_same_locking_period() { contract.get_balance(sender_id.clone()), "Incorrect balance!" ); + + let voters = contract.get_voters(0, 10); + assert_eq!(voters.len(), 1); + let locking_position = &voters.first().unwrap().locking_positions; + assert_eq!(locking_position.len(), 1); + let vote_position = &voters.first().unwrap().vote_positions; + assert_eq!(vote_position.len(), 0); } #[test] @@ -147,7 +161,6 @@ fn test_multiple_deposit_diff_locking_period() { ); testing_env!(context.clone()); assert_eq!( - U128::from(total_vote_power), contract.get_available_voting_power(sender_id.clone()), "Incorrect voting power calculation!" @@ -164,6 +177,13 @@ fn test_multiple_deposit_diff_locking_period() { contract.get_balance(sender_id.clone()), "Incorrect balance!" ); + + let voters = contract.get_voters(0, 10); + assert_eq!(voters.len(), 1); + let locking_position = &voters.first().unwrap().locking_positions; + assert_eq!(locking_position.len(), 2); + let vote_position = &voters.first().unwrap().vote_positions; + assert_eq!(vote_position.len(), 0); } #[test] @@ -748,10 +768,16 @@ fn test_clear_locking_position() { Meta::from(contract.get_balance(sender_id.clone())), "Incorrect balance!" ); + + let voters = contract.get_voters(0, 10); + assert_eq!(voters.len(), 1); + let locking_position = &voters.first().unwrap().locking_positions; + assert_eq!(locking_position.len(), 0); + let vote_position = &voters.first().unwrap().vote_positions; + assert_eq!(vote_position.len(), 0); } #[test] - #[should_panic(expected="Not enough free voting power to unlock! You have 0, required 20370370370370370370370370.")] fn test_unlock_position_without_voting_power() { const LOCKING_PERIOD: u64 = 100; @@ -873,6 +899,13 @@ fn test_rebalance_increase_and_decrease() { let votes_for_address = voter.get_votes_for_address(&sender_id, &contract_address); let votes = votes_for_address.get(&votable_object_id).unwrap(); assert_eq!(votes, u128::from(additional_votes), "Incorrect Voting Power calculation."); + + let voters = contract.get_voters(0, 10); + assert_eq!(voters.len(), 1); + let locking_position = &voters.first().unwrap().locking_positions; + assert_eq!(locking_position.len(), 1); + let vote_position = &voters.first().unwrap().vote_positions; + assert_eq!(vote_position.len(), 1); } #[test] @@ -1009,4 +1042,10 @@ fn test_multi_voter_contract() { "Incorrect vote count for project 2, object 1." ); + let voters = contract.get_voters(0, 10); + assert_eq!(voters.len(), 4); + let locking_position = &voters.first().unwrap().locking_positions; + assert_eq!(locking_position.len(), 1); + let vote_position = &voters.first().unwrap().vote_positions; + assert_eq!(vote_position.len(), 1); } diff --git a/contract/meta-vote-contract/src/types.rs b/contract/meta-vote-contract/src/types.rs index fbd47e7..ae0944c 100644 --- a/contract/meta-vote-contract/src/types.rs +++ b/contract/meta-vote-contract/src/types.rs @@ -17,7 +17,7 @@ construct_uint! { pub struct U256(4); } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] #[serde(crate = "near_sdk::serde")] pub struct LockingPositionJSON { pub index: Option, @@ -37,3 +37,11 @@ pub struct VotableObjectJSON { pub id: VotableObjId, pub current_votes: U128 } + +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "near_sdk::serde")] +pub struct VotePositionJSON { + pub votable_address: AccountId, + pub votable_object_id: String, + pub voting_power: U128 +} diff --git a/contract/meta-vote-contract/src/voter.rs b/contract/meta-vote-contract/src/voter.rs index 233a133..299b265 100644 --- a/contract/meta-vote-contract/src/voter.rs +++ b/contract/meta-vote-contract/src/voter.rs @@ -1,5 +1,15 @@ use crate::*; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; +use near_sdk::serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "near_sdk::serde")] +pub struct VoterJSON { + pub voter_id: AccountId, + pub locking_positions: Vec, + pub voting_power: U128, + pub vote_positions: Vec +} #[derive(BorshDeserialize, BorshSerialize)] pub struct Voter { @@ -117,4 +127,34 @@ impl Voter { } result } + + pub(crate) fn to_json(&self, voter_id: VoterId) -> VoterJSON { + let mut locking_positions = Vec::::new(); + for index in 0..self.locking_positions.len() { + let pos = self.locking_positions.get(index).unwrap(); + locking_positions.push(pos.to_json(Some(index))); + } + + let mut vote_positions = Vec::::new(); + for address in self.vote_positions.keys_as_vector().iter() { + let pos = self.vote_positions.get(&address).unwrap(); + for obj in pos.keys_as_vector().iter() { + let value = pos.get(&obj).unwrap(); + vote_positions.push( + VotePositionJSON { + votable_address: address.clone(), + votable_object_id: obj, + voting_power: U128::from(value) + } + ); + } + } + + VoterJSON { + voter_id, + locking_positions, + voting_power: U128::from(self.voting_power), + vote_positions + } + } } diff --git a/front/.env.example b/front/.env.example index b984eb4..ab7ee9e 100644 --- a/front/.env.example +++ b/front/.env.example @@ -3,4 +3,7 @@ NEXT_PUBLIC_METAPOOL_CONTRACT_ID= NEXT_PUBLIC_META_CONTRACT_ID= NEXT_PUBLIC_CONTRACT_ADDRESS_METAVOTE= NEXT_PUBLIC_GA_TRACKING_ID= -NEXT_PUBLIC_VERCEL_ENV= \ No newline at end of file +NEXT_PUBLIC_VOTES_REFETCH_INTERVAL= +NEXT_PUBLIC_VOTER_DATA_REFETCH_INTERVAL= +NEXT_PUBLIC_VERCEL_ENV= +NEXT_PUBLIC_GET_META_CONTRACT= \ No newline at end of file diff --git a/front/config.ts b/front/config.ts index 91c0e4b..2a2071f 100644 --- a/front/config.ts +++ b/front/config.ts @@ -12,7 +12,7 @@ export const getConfig = (env: string) => { helperUrl: "https://helper.mainnet.near.org", explorerUrl: "https://explorer.mainnet.near.org", metapoolUrl: "https://metapool.app/dapp/mainnet/meta", - refFinance: "https://www.ref.finance/", + refFinance: "https://app.ref.finance/#meta-pool.near%7Cmeta-token.near", metayieldUrl: "https://metayield.app" }; @@ -27,7 +27,7 @@ export const getConfig = (env: string) => { helperUrl: "https://helper.testnet.near.org", explorerUrl: "https://explorer.testnet.near.org", metapoolUrl: "https://metapool.app/dapp/testnet/meta", - refFinance: "https://www.ref.finance/", + refFinance: "https://app.ref.finance/#meta-pool.near%7Cmeta-token.near", metayieldUrl: "https://metayield.app" }; case "betanet": @@ -39,7 +39,7 @@ export const getConfig = (env: string) => { helperUrl: "https://helper.betanet.near.org", explorerUrl: "https://explorer.betanet.near.org", metapoolUrl: "https://metapool.app/dapp/testnet/meta", - refFinance: "https://www.ref.finance/", + refFinance: "https://app.ref.finance/#meta-pool.near%7Cmeta-token.near", metayieldUrl: "https://metayield.app" }; case "local": @@ -49,7 +49,7 @@ export const getConfig = (env: string) => { keyPath: `${process.env.HOME}/.near/validator_key.json`, walletUrl: "http://localhost:4000/wallet", metapoolUrl: "https://metapool.app/dapp/testnet/meta", - refFinance: "https://www.ref.finance/", + refFinance: "https://app.ref.finance/#meta-pool.near%7Cmeta-token.near", contractName: CONTRACT_NAME, metayieldUrl: "https://metayield.app" }; diff --git a/front/constants/index.tsx b/front/constants/index.tsx index fa52829..9cc306c 100644 --- a/front/constants/index.tsx +++ b/front/constants/index.tsx @@ -1,5 +1,26 @@ +export const MIN_LOCK_DAYS = 30; +export const DEFAULT_LOCK_DAYS = 90; - export const CONTRACT_ADDRESS = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS_METAVOTE; +export const MAX_LOCK_DAYS = 300; + +export const MODAL_DURATION = { + LONG: 9000, + SUCCESS: 3000, + ERROR: 5000 +} + +export const FETCH_VOTES_INTERVAL = process.env.NEXT_PUBLIC_VOTER_DATA_REFETCH_INTERVAL ? parseInt(process.env.NEXT_PUBLIC_VOTER_DATA_REFETCH_INTERVAL) : 5000; +export const FETCH_VOTER_DATA_INTERVAL = process.env.NEXT_PUBLIC_VOTES_REFETCH_INTERVAL ? parseInt(process.env.NEXT_PUBLIC_VOTES_REFETCH_INTERVAL) : 5000; +export const FETCH_WHITELISTED_TOKENS_INTERVAL = 10000; +export const FETCH_METAPOOL_STATE_INTERVAL = 2000; +export const FETCH_NEAR_PRICE_INTERVAL = 2000; +export const FETCH_TOKEN_BALANCE_INTERVAL = 5000; + +export const GET_META_DEFAULT_SLIPPAGE = 0.3; +export const GET_META_MIN_SLIPPAGE = 0.01; +export const GET_META_ENABLED = (process.env.NEXT_PUBLIC_ENABLE_GET_META && process.env.NEXT_PUBLIC_ENABLE_GET_META == "true") || false; + +export const CONTRACT_ADDRESS = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS_METAVOTE||"metayield.app"; export const enum ACTION_TYPE { RELOCK, @@ -13,7 +34,7 @@ export const MODAL_TEXT = { UNLOCK: { CONFIRM: { title: `Start unlocking`, - text: `Are you sure you want to start unlocking this position? Your tokens will be releases when the locking period ends.` + text: `Are you sure you want to start unlocking this position? Your tokens will be released when the locking period ends.` }, ERROR_NOT_ENOUGH: { title: `Not Enough Available Voting Power`, diff --git a/front/constants/whitelist.ts b/front/constants/whitelist.ts index 8268a09..e4292fb 100644 --- a/front/constants/whitelist.ts +++ b/front/constants/whitelist.ts @@ -1,10 +1,17 @@ export const WHITELIST_SITES = [ { platform: 'metayield.app', - isologo: 'metayield_iso.png' + isologo: 'metayield_iso.png', + url: 'https://metayield.app/vote/' }, { platform: 'metapool.app', - isologo: 'metapool_iso.png' + isologo: 'metapool_iso.png', + url: 'https://vote.metapool.app/' + }, + { + platform: 'metastaking.app', + isologo: 'metapool_iso.png', + url: 'https://vote.metapool.app/' }, ] \ No newline at end of file diff --git a/front/contexts/WalletSelectorContext.tsx b/front/contexts/WalletSelectorContext.tsx index 1d469f3..bb29716 100644 --- a/front/contexts/WalletSelectorContext.tsx +++ b/front/contexts/WalletSelectorContext.tsx @@ -4,6 +4,7 @@ import { NetworkId, setupWalletSelector, Wallet, + WalletModuleFactory, } from "@near-wallet-selector/core"; import type { WalletSelector, AccountState } from "@near-wallet-selector/core"; import { setupModal } from "@near-wallet-selector/modal-ui"; @@ -11,8 +12,13 @@ import type { WalletSelectorModal } from "@near-wallet-selector/modal-ui"; import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; import { setupMathWallet } from "@near-wallet-selector/math-wallet"; import { setupLedger } from "@near-wallet-selector/ledger"; -import { CONTRACT_ID, METAPOOL_CONTRACT_ID, NETWORK_ID } from "../lib/near" +import { setupNightly } from "@near-wallet-selector/nightly"; +import { CONTRACT_ID, METAPOOL_CONTRACT_ID, NETWORK_ID } from "../lib/near"; import { getConfig } from "../config"; +import { setupWalletConnect } from "@near-wallet-selector/wallet-connect"; +import { setupNearWallet } from "@near-wallet-selector/near-wallet"; +import { setupHereWallet } from "@near-wallet-selector/here-wallet"; +import { setupMeteorWallet } from "@near-wallet-selector/meteor-wallet"; declare global { interface Window { selector: WalletSelector; @@ -33,58 +39,39 @@ const WalletSelectorContext = React.createContext(null); export const WalletSelectorContextProvider: React.FC = ({ children }) => { - const env = process.env.NEXT_PUBLIC_VERCEL_ENV || "production"; + const env = process.env.NEXT_PUBLIC_VERCEL_ENV || "development"; const nearConfig = getConfig(env); const [selector, setSelector] = useState(null); const [modal, setModal] = useState(null); const [accounts, setAccounts] = useState>([]); - const setupNearWalletCustom = () => { - return async (options: any) => { - const wallet = await setupMyNearWallet({ - walletUrl: nearConfig.walletUrl, - iconUrl: "./assets/near-wallet-iconx.png", - })(options); - - if (!wallet) { - return null; - } - - return { - ...wallet, - id: "near-wallet", - metadata: { - ...wallet.metadata, - name: "NEAR Wallet", - description: null, - iconUrl: "./assets/near-wallet-icon.png", - deprecated: false, - available: true, - }, - }; - }; - }; - const init = useCallback(async () => { const _selector = await setupWalletSelector({ network: NETWORK_ID as NetworkId, debug: true, modules: [ - setupNearWalletCustom(), - // setupMyNearWallet(), - // setupSender(), + setupMeteorWallet() as WalletModuleFactory, + setupNearWallet({ + walletUrl: nearConfig.walletUrl, + iconUrl: "/assets/near-wallet-icon.png", + }), + setupMyNearWallet(), setupMathWallet(), - // setupNightly(), + setupNightly(), // setupLedger(), - /* setupWalletConnect({ - projectId: "c4f79cc...", + setupWalletConnect({ + projectId: + process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID || + "3ec2226fd3f38b6fb82e789fcfc232bf", metadata: { - name: "NEAR Wallet Selector", - description: "Example dApp used by NEAR Wallet Selector", - url: "https://github.com/near/wallet-selector", + name: "NEAR Wallet Selector for Meta Vote", + description: + "Wallet Connect integration on Wallet Selector for Meta Vote", + url: "https://metavote.app/", icons: ["https://avatars.githubusercontent.com/u/37784886"], }, }), + setupHereWallet() /* setupNightlyConnect({ url: "wss://ncproxy.nightly.app/app", appMetadata: { @@ -97,7 +84,6 @@ export const WalletSelectorContextProvider: React.FC = ({ children }) => { ], }); - const _modal = setupModal(_selector, { contractId: CONTRACT_ID || "" }); const state = _selector.store.getState(); setAccounts(state.accounts); @@ -131,7 +117,13 @@ export const WalletSelectorContextProvider: React.FC = ({ children }) => { distinctUntilChanged() ) .subscribe((nextAccounts) => { + window.account_id = nextAccounts.find( + (account) => account.active + )?.accountId!; setAccounts(nextAccounts); + window.account_id = nextAccounts.find( + (account) => account.active + )?.accountId!; }); return () => subscription.unsubscribe(); diff --git a/front/hooks/example.ts b/front/hooks/example.ts deleted file mode 100644 index 02a525a..0000000 --- a/front/hooks/example.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useQuery } from "react-query"; -import { - queryExample -} from "../queries/example"; - - -export const useExample = () => { - return useQuery("example", () => queryExample(), { - onError: (err) => { - console.error(err); - }, - }); -}; - - diff --git a/front/hooks/getMeta.ts b/front/hooks/getMeta.ts new file mode 100644 index 0000000..5a33b38 --- /dev/null +++ b/front/hooks/getMeta.ts @@ -0,0 +1,21 @@ +import { useQuery } from "react-query"; +import { FETCH_WHITELISTED_TOKENS_INTERVAL } from "../constants"; +import { getWhitelistedTokens, getMetaContractFee } from "../lib/near"; + +export const useGetWhitelistedTokens = () => { + + return useQuery(["whitelisted-tokens"], () => getWhitelistedTokens(), { + onError: (err) => { + console.error(err); + }, + refetchInterval: FETCH_WHITELISTED_TOKENS_INTERVAL, + }); + }; + +export const useGetMetaContractFee = () => { + return useQuery('get-meta-fee', () => getMetaContractFee(), { + onError: (err) => { + console.error(err); + }, + }) +} \ No newline at end of file diff --git a/front/hooks/metapool.ts b/front/hooks/metapool.ts new file mode 100644 index 0000000..4f23efc --- /dev/null +++ b/front/hooks/metapool.ts @@ -0,0 +1,13 @@ +import { useQuery } from "react-query"; +import { FETCH_METAPOOL_STATE_INTERVAL, FETCH_TOKEN_BALANCE_INTERVAL } from "../constants"; +import { getBalanceStNear, getMetapoolContractState } from "../lib/near"; + +export const useGetMetapoolContractState = () => { + return useQuery("metapoolContractState", () => getMetapoolContractState(), { + onError: (err) => { + console.error(err); + }, + refetchInterval: FETCH_METAPOOL_STATE_INTERVAL, + staleTime: FETCH_METAPOOL_STATE_INTERVAL, + }); +}; \ No newline at end of file diff --git a/front/hooks/near.ts b/front/hooks/near.ts new file mode 100644 index 0000000..371d584 --- /dev/null +++ b/front/hooks/near.ts @@ -0,0 +1,63 @@ +import { useQuery } from "react-query"; +import { + FETCH_NEAR_PRICE_INTERVAL, + FETCH_TOKEN_BALANCE_INTERVAL, +} from "../constants"; +import { + getBalanceStNear, + getContractMetadata, + getNearBalance, + getTokenBalanceOf, +} from "../lib/near"; +import { + isDenominationACurrency, + isNearDenomination, + isStNearDenomination, +} from "../pages/get-meta/TokenIcon/util"; +import { getNearDollarPrice } from "../queries/near"; + +export const useGetTokenMetadata = (tokenContract: string) => { + return useQuery( + ["metadata", tokenContract], + () => getContractMetadata(tokenContract), + { + onError: (err) => { + console.error(err); + }, + cacheTime: Infinity, + enabled: !isDenominationACurrency(tokenContract), + } + ); +}; + +export const useGetNearDollarPrice = () => { + return useQuery("nearDollarPrice", () => getNearDollarPrice(), { + onError: (err) => { + console.error(err); + }, + refetchInterval: FETCH_NEAR_PRICE_INTERVAL, + staleTime: FETCH_NEAR_PRICE_INTERVAL, + }); +}; + +export const useGetBalance = (accountId: string, currency?: string) => { + return useQuery( + ["balance", currency, accountId], + () => { + if (isNearDenomination(currency)) { + return getNearBalance(accountId!); + } else if (isStNearDenomination(currency)) { + return getBalanceStNear(); + } + return getTokenBalanceOf(currency!, accountId!); + }, + { + onError: (err) => { + console.error(err); + }, + refetchInterval: FETCH_TOKEN_BALANCE_INTERVAL, + staleTime: FETCH_TOKEN_BALANCE_INTERVAL, + enabled: !!currency && !!accountId && currency !== null + } + ); +}; \ No newline at end of file diff --git a/front/lib/methods.ts b/front/lib/methods.ts index 7167848..460c04a 100644 --- a/front/lib/methods.ts +++ b/front/lib/methods.ts @@ -31,16 +31,19 @@ export const metavoteChangeMethods = { export const metaPoolMethods = { getStNearPrice: "get_st_near_price", - getAccountInfo: "get_account_info" + getAccountInfo: "get_account_info", + getContractState: "get_contract_state" }; export const metaTokenMethods = { getMetas: "ft_balance_of", + getVestingInfo: "get_vesting_info", getAccountInfo: "ft_metadata" }; export const projectTokenViewMethods = { storageBalanceOf: "storage_balance_of", + balanceOf: "ft_balance_of", metadata: "ft_metadata", storageBalanceBounds: "storage_balance_bounds" } @@ -48,3 +51,17 @@ export const projectTokenViewMethods = { export const projectTokenChangeMethods = { storageDeposit: "storage_deposit" } + +export const getMetaViewMethods = { + isTokenWhitelisted: "is_token_whitelisted", + getMetaBalance: "get_meta_balance", + getTokenBalance: "get_token_balance", + getWhitelistedTokens: "get_whitelisted_tokens", + computeMetaAmountOnReturn: "compute_meta_amount_on_return", + getMetaFee: "get_meta_fee" +} + +export const getMetaChangeMethods = { + depositToken: "ft_transfer_call", + depositNear: "deposit_near" +} \ No newline at end of file diff --git a/front/lib/near.ts b/front/lib/near.ts index 648c568..5137b15 100644 --- a/front/lib/near.ts +++ b/front/lib/near.ts @@ -6,9 +6,7 @@ import { providers, ConnectConfig, } from "near-api-js"; -import { - getTransactionLastResult, -} from "near-api-js/lib/providers"; +import { getTransactionLastResult } from "near-api-js/lib/providers"; import { FinalExecutionOutcome } from "near-api-js/lib/providers"; import { AccountView } from "near-api-js/lib/providers/provider"; const BN = require("bn.js"); @@ -20,24 +18,39 @@ import { metaPoolMethods, metaTokenMethods, projectTokenViewMethods, + getMetaViewMethods, + getMetaChangeMethods, } from "./methods"; import { + checkPanicError, decodeJsonRpcData, encodeJsonRpcData, + getLogsAndErrorsFromReceipts, getPanicError, getPanicErrorFromText, getTxFunctionCallMethod, yton, } from "./util"; - -export const CONTRACT_ID = process.env.NEXT_PUBLIC_CONTRACT_ID; -export const NETWORK_ID = process.env.NEXT_PUBLIC_VERCEL_ENV == 'production' ? 'mainnet' : 'testnet'; -export const METAPOOL_CONTRACT_ID = process.env.NEXT_PUBLIC_METAPOOL_CONTRACT_ID; -export const META_CONTRACT_ID = process.env.NEXT_PUBLIC_META_CONTRACT_ID; +import { blockerStore } from "../stores/pageBlocker"; +import { Wallet } from "@near-wallet-selector/core"; +import { MetapoolContractState } from "../types/metapool.types"; + +export const CONTRACT_ID = + process.env.NEXT_PUBLIC_CONTRACT_ID || "metavote.testnet"; +const env = process.env.NEXT_PUBLIC_VERCEL_ENV || "development"; +export const NETWORK_ID = + process.env.NEXT_PUBLIC_VERCEL_ENV == "production" ? "mainnet" : "testnet"; +export const IS_PRODUCTION = NETWORK_ID == "mainnet"; +export const METAPOOL_CONTRACT_ID = + process.env.NEXT_PUBLIC_METAPOOL_CONTRACT_ID || "meta-v2.pool.testnet"; +export const METAPOOL_DEV_CONTRACT_ID = + process.env.NEXT_PUBLIC_METAPOOL_DEV_CONTRACT_ID; +export const META_CONTRACT_ID = + process.env.NEXT_PUBLIC_META_CONTRACT_ID || "token.meta.pool.testnet"; +export const GET_META_CONTRACT_ID = process.env.NEXT_PUBLIC_GET_META_CONTRACT; export const gas = new BN("70000000000000"); export const GAS = "200000000000000"; - -const env = process.env.NEXT_PUBLIC_VERCEL_ENV || 'production'; +export const TRANSFER_CALL_DEPOSIT = "1"; const nearConfig = getConfig(env); const provider = new providers.JsonRpcProvider({ url: nearConfig.nodeUrl }); @@ -49,26 +62,17 @@ export const getNearConfig = () => { return nearConfig; }; -export const getWallet = async () => { - const connectConfig: ConnectConfig = { - ...nearConfig, - headers: {}, - keyStore: new keyStores.BrowserLocalStorageKeyStore(), - }; - const near = await connect(connectConfig); - const wallet = new WalletConnection(near, "metavote"); - return wallet; -}; - -export const signInWallet = async () => { - const wallet = await getWallet(); - wallet.requestSignIn(METAPOOL_CONTRACT_ID, "Metapool contract"); - return wallet; -}; - -export const signOutWallet = async () => { - const wallet = await getWallet(); - wallet!.signOut(); +export const signOutWallet = async (wallet: Wallet) => { + blockerStore.setState({ isActive: true }); + wallet + .signOut() + .catch((err) => { + console.log("Failed to sign out"); + console.error(err); + }) + .finally(() => { + blockerStore.setState({ isActive: false }); + }); }; export const getConnection = async () => { @@ -78,23 +82,8 @@ export const getConnection = async () => { keyStore: new keyStores.BrowserLocalStorageKeyStore(), }; const nearConnection = await connect(connectConfig); - return nearConnection; -} - -/* export const getAccount = async () => { - const accountId = window.account_id; - const account = provider - .query({ - request_type: "view_account", - finality: "final", - account_id: accountId, - }) - .then((data) => ({ - ...data, - account_id: accountId, - })); - return account; -}; */ + return nearConnection; +}; export const getAccount = async () => { const accountId = window.account_id; @@ -111,57 +100,49 @@ export const getAccount = async () => { return account; }; -/* export const getContract = async () => { - const account = await getAccount(); - return new Contract(account, CONTRACT_ID!, { - viewMethods: Object.values(metavoteViewMethods), - changeMethods: Object.values(metavoteChangeMethods), - }); -}; */ - -/* export const getMetapoolContract = async () => { - const account = await getAccount(); - return new Contract(account, METAPOOL_CONTRACT_ID!, { - viewMethods: Object.values(metaPoolMethods), - changeMethods: ["ft_transfer_call"], - }); -}; - -export const getMetaTokenContract = async () => { - const account = await getAccount(); - return new Contract(account, META_CONTRACT_ID!, { - viewMethods: Object.values(metaTokenMethods), - changeMethods: ["ft_transfer_call"], - }); -}; */ - - export const getStNearPrice = async () => { return callPublicMetapoolMethod(metaPoolMethods.getStNearPrice, {}); }; export const getMetapoolAccountInfo = async () => { const account_id = window.account_id; - return callViewMetapoolMethod( metaPoolMethods.getAccountInfo, { + return callViewMetapoolMethod(metaPoolMethods.getAccountInfo, { account_id: account_id, }); }; +export const getMetapoolContractState = + async (): Promise => { + return callViewMetapoolMethod(metaPoolMethods.getContractState, {}); + }; + export const getMetaTokenAccountInfo = async () => { const account_id = window.account_id; - return callViewMetaTokenMethod( metaTokenMethods.getMetas, { + if (!account_id) return 0; + return callViewMetaTokenMethod(metaTokenMethods.getMetas, { account_id: account_id, }); }; +export const getVestingInfo = async () => { + const account_id = window.account_id; + return callViewMetaTokenMethod(metaTokenMethods.getVestingInfo, { + account_id: account_id, + }); +} + export const getMetaBalance = async (): Promise => { const accountInfo = await getMetaTokenAccountInfo(); return yton(accountInfo); }; -export const getBalance = async (): Promise => { - const accountInfo = await getMetapoolAccountInfo(); - return yton(accountInfo.st_near); +export const getBalanceStNear = async (): Promise => { + if (IS_PRODUCTION) { + const accountInfo = await getMetapoolAccountInfo(); + return accountInfo.st_near; + } + const account_id = window.account_id; + return getTokenBalanceOf(METAPOOL_DEV_CONTRACT_ID!, account_id!); }; export const getTxStatus = async ( @@ -206,7 +187,6 @@ export const getContractMetadata = async (contract: string) => { return decodeJsonRpcData(response.result); }; - const callPublicMetavoteMethod = async (method: string, args: any) => { const response: any = await provider.query({ request_type: "call_function", @@ -219,20 +199,14 @@ const callPublicMetavoteMethod = async (method: string, args: any) => { return decodeJsonRpcData(response.result); }; -/*const callChangeMetavoteMethod = async ( args: any, method: string, deposit?: string) => { - const contract = await getContract(); - let response; - if (deposit) { - response = (contract as any)[method](args, "200000000000000", deposit); - } else { - response = (contract as any)[method](args, "200000000000000"); - } - return response; -}; */ - -const callChangeMetavoteMethod = async (method: string, args: any, deposit?: string): Promise => { +const callChangeMetavoteMethod = async ( + method: string, + args: any, + deposit?: string +): Promise => { const wallet = window.wallet; const account_id = window.account_id; + blockerStore.setState({ isActive: true }); const result = await wallet! .signAndSendTransaction({ signerId: account_id!, @@ -243,7 +217,7 @@ const callChangeMetavoteMethod = async (method: string, args: any, deposit?: str methodName: method, args: args, gas: GAS, - deposit: deposit ? deposit : '', + deposit: deposit ? deposit : "", }, }, ], @@ -251,10 +225,13 @@ const callChangeMetavoteMethod = async (method: string, args: any, deposit?: str .catch((err) => { console.error(`Failed to call metavote contract -- method: ${method}`); throw getPanicErrorFromText(err.message); + }) + .finally(() => { + blockerStore.setState({ isActive: false }); }); - if (result instanceof Object) { - return result; - } + if (result instanceof Object) { + return result; + } return null; }; @@ -270,16 +247,6 @@ const callPublicMetapoolMethod = async (method: string, args: any) => { return decodeJsonRpcData(response.result); }; - - -/* const callViewMetapoolMethod = async ( - method: string, - args: any -) => { - const contract = await getMetapoolContract(); - return (contract as any)[method](args); -}; */ - const callViewMetapoolMethod = async (method: string, args: any) => { const response: any = await provider.query({ request_type: "call_function", @@ -292,14 +259,6 @@ const callViewMetapoolMethod = async (method: string, args: any) => { return decodeJsonRpcData(response.result); }; -/* const callViewMetaTokenMethod = async ( - method: string, - args: any -) => { - const contract = await getMetaTokenContract(); - return (contract as any)[method](args); -}; */ - const callViewMetaTokenMethod = async (method: string, args: any) => { const response: any = await provider.query({ request_type: "call_function", @@ -312,6 +271,18 @@ const callViewMetaTokenMethod = async (method: string, args: any) => { return decodeJsonRpcData(response.result); }; +const callViewGetMetaMethod = async (method: string, args: any) => { + const response: any = await provider.query({ + request_type: "call_function", + finality: "optimistic", + account_id: GET_META_CONTRACT_ID, + method_name: method, + args_base64: encodeJsonRpcData(args), + }); + + return decodeJsonRpcData(response.result); +}; + export const getBalanceOfTokenForSupporter = async ( tokenContractAddress: string ) => { @@ -326,78 +297,75 @@ export const getBalanceOfTokenForSupporter = async ( return decodeJsonRpcData(response.result); }; - const callChangeMetaTokenMethod = async (method: string, args: any) => { const wallet = window.wallet; const account_id = window.account_id; - const result = wallet! - .signAndSendTransaction({ - signerId: account_id!, - receiverId: META_CONTRACT_ID, - actions: [ - { - type: "FunctionCall", - params: { - methodName: method, - args: args, - gas: GAS, - deposit: '1', - }, + blockerStore.setState({ isActive: true }); + const result = await wallet!.signAndSendTransaction({ + signerId: account_id!, + receiverId: META_CONTRACT_ID, + actions: [ + { + type: "FunctionCall", + params: { + methodName: method, + args: args, + gas: GAS, + deposit: "1", }, - ], - }) - .catch((err) => { - console.error(`Failed to call metavote contract -- method: ${method}`); - throw err; - }); - return result; + }, + ], + }); + checkPanicError(result); + blockerStore.setState({ isActive: false }); + if (result instanceof Object) { + return result; + } + return null; }; -/* const callChangeMetaTokenMethod = async ( - method: string, - args: any -) => { - const contract = await getMetaTokenContract(); - return (contract as any)[method](args, "300000000000000", "1"); -}; */ - - /*********** METAVOTE VIEW METHODS *************/ export const getAvailableVotingPower = async () => { - return callPublicMetavoteMethod(metavoteViewMethods.getAvailableVotingPower, {voter_id: window.account_id -}); + return callPublicMetavoteMethod(metavoteViewMethods.getAvailableVotingPower, { + voter_id: window.account_id, + }); }; export const getInUseVotingPower = async () => { - return callPublicMetavoteMethod(metavoteViewMethods.getUsedVotingPower, {voter_id: window.account_id -}); + return callPublicMetavoteMethod(metavoteViewMethods.getUsedVotingPower, { + voter_id: window.account_id, + }); }; export const getAllLockingPositions = async () => { - return callPublicMetavoteMethod(metavoteViewMethods.getAllLockingPositions, {voter_id: window.account_id -}); + return callPublicMetavoteMethod(metavoteViewMethods.getAllLockingPositions, { + voter_id: window.account_id, + }); }; export const getBalanceMetaVote = async () => { - return callPublicMetavoteMethod(metavoteViewMethods.getBalance, {voter_id: window.account_id -}); + return callPublicMetavoteMethod(metavoteViewMethods.getBalance, { + voter_id: window.account_id, + }); }; export const getLockedBalance = async () => { - return callPublicMetavoteMethod(metavoteViewMethods.getLockedBalance, {voter_id: window.account_id -}); + return callPublicMetavoteMethod(metavoteViewMethods.getLockedBalance, { + voter_id: window.account_id, + }); }; export const getUnlockingBalance = async () => { - return callPublicMetavoteMethod(metavoteViewMethods.getUnlockingBalance, {voter_id: window.account_id -}); + return callPublicMetavoteMethod(metavoteViewMethods.getUnlockingBalance, { + voter_id: window.account_id, + }); }; export const getVotes = async (id: string, contract: string) => { return callPublicMetavoteMethod(metavoteViewMethods.getTotalVotes, { contract_address: contract, - votable_object_id: id + votable_object_id: id, }); }; @@ -409,62 +377,195 @@ export const getVotesByContract = async (contract: string) => { export const getVotesByVoter = async () => { return callPublicMetavoteMethod(metavoteViewMethods.getVotesByVoter, { - voter_id: window.account_id + voter_id: window.account_id, }); }; /*********** METAVOTE CHANGE METHODS *************/ -export const voteProject = async (id: string, contractName: string, votingPower: string) => { +export const voteProject = async ( + id: string, + contractName: string, + votingPower: string +) => { const args = { voting_power: votingPower, contract_address: contractName, - votable_object_id: id - } - return callChangeMetavoteMethod( metavoteChangeMethods.vote, args, ); + votable_object_id: id, + }; + return callChangeMetavoteMethod(metavoteChangeMethods.vote, args); }; -export const unvoteProject = async (id: string, contractNameId: string ) => { +export const unvoteProject = async (id: string, votableObjAddress: string) => { const args = { - contract_address: contractNameId, - votable_object_id: id - } - return callChangeMetavoteMethod( metavoteChangeMethods.unvote, args); + contract_address: votableObjAddress, + votable_object_id: id, + }; + return callChangeMetavoteMethod(metavoteChangeMethods.unvote, args); }; -export const lock = async (days: string, amount: string ) => { +export const lock = async (days: string, amount: string) => { const args = { receiver_id: CONTRACT_ID, amount: amount, - msg: days - } - return callChangeMetaTokenMethod( "ft_transfer_call", args); + msg: days, + }; + return callChangeMetaTokenMethod("ft_transfer_call", args); }; -export const unlock = async (positionId: string ) => { +export const unlock = async (positionId: string) => { const args = { - index: positionId - } - return callChangeMetavoteMethod( metavoteChangeMethods.unlockPosition, args ); + index: positionId, + }; + return callChangeMetavoteMethod(metavoteChangeMethods.unlockPosition, args); }; export const withdrawAPosition = async (positionId: string) => { const args = { - position_index_list: positionId ? [positionId] : [], - amount_from_balance: '0' - } - return callChangeMetavoteMethod( metavoteChangeMethods.withdraw, args ); + position_index_list: positionId ? [positionId] : [], + amount_from_balance: "0", + }; + return callChangeMetavoteMethod(metavoteChangeMethods.withdraw, args); }; export const withdrawAll = async () => { - const args = {} - return callChangeMetavoteMethod( metavoteChangeMethods.withdrawAll, args); + const args = {}; + return callChangeMetavoteMethod(metavoteChangeMethods.withdrawAll, args); }; -export const relock = async (positionIndex: string, period: string, amount: string ) => { +export const relock = async ( + positionIndex: string, + period: string, + amount: string +) => { const args = { index: positionIndex, locking_period: period, - amount_from_balance: '0' + amount_from_balance: "0", + }; + return callChangeMetavoteMethod(metavoteChangeMethods.relock, args); +}; + +/*********** GETMETA METHODS *************/ +export const getWhitelistedTokens = async () => { + return callViewGetMetaMethod(getMetaViewMethods.getWhitelistedTokens, {}); +}; + +export const computeMetaAmountOnReturn = async ( + token_contract_address: string, + token_amount: string +) => { + return callViewGetMetaMethod(getMetaViewMethods.computeMetaAmountOnReturn, { + token_contract_address, + token_amount, + }); +}; + +export const getTokenBalanceOf = async ( + tokenContractAddress: string, + accountId: string +) => { + if (!accountId) return 0; + + const response: any = await provider.query({ + request_type: "call_function", + finality: "final", + account_id: tokenContractAddress, + method_name: projectTokenViewMethods.balanceOf, + args_base64: encodeJsonRpcData({ account_id: accountId }), + }); + return decodeJsonRpcData(response.result); +}; + +export const getNearBalance = async (accountId: string) => { + const response: any = await provider.query({ + request_type: "view_account", + finality: "final", + account_id: accountId, + }); + return response.amount; +}; + +export const getMetaContractFee = async () => { + return callViewGetMetaMethod(getMetaViewMethods.getMetaFee, {}); +}; + +export const depositToken = async ( + tokenContracId: string, + amount: string, + minMetaAmountExpected: string +) => { + const wallet = window.wallet; + const account_id = window.account_id; + const args = { + amount: amount, + receiver_id: GET_META_CONTRACT_ID, + msg: minMetaAmountExpected, + }; + const result = await wallet! + .signAndSendTransaction({ + signerId: account_id!, + receiverId: tokenContracId, + actions: [ + { + type: "FunctionCall", + params: { + methodName: getMetaChangeMethods.depositToken, + args: args, + gas: GAS, + deposit: TRANSFER_CALL_DEPOSIT, + }, + }, + ], + }) + .catch((err) => { + console.log("Failed to fund to kickstarter"); + + throw getPanicErrorFromText(err.message); + }) + .finally(() => { + blockerStore.setState({ isActive: false }); + }); + if (result instanceof Object) { + return result; } - return callChangeMetavoteMethod( metavoteChangeMethods.relock, args ); + return null; }; + +export const depositNear = async ( + amount: string, + minMetaAmountExpected: string +) => { + const wallet = window.wallet; + const account_id = window.account_id; + const args = { + min_amount_expected: minMetaAmountExpected, + }; + const result = await wallet! + .signAndSendTransaction({ + signerId: account_id!, + receiverId: GET_META_CONTRACT_ID, + actions: [ + { + type: "FunctionCall", + params: { + methodName: getMetaChangeMethods.depositNear, + args: args, + gas: GAS, + deposit: amount, + }, + }, + ], + }) + .catch((err) => { + console.log("Failed to fund to kickstarter"); + + throw getPanicErrorFromText(err.message); + }) + .finally(() => { + blockerStore.setState({ isActive: false }); + }); + if (result instanceof Object) { + return result; + } + return null; +}; \ No newline at end of file diff --git a/front/lib/util.ts b/front/lib/util.ts index 380ad15..cdbbf41 100644 --- a/front/lib/util.ts +++ b/front/lib/util.ts @@ -148,6 +148,14 @@ export const getPanicError = (txResult: any) => { } }; + +export const checkPanicError = ( txResult: any) => { + const error = getPanicError(txResult); + if(error) { + throw new Error(error); + } +} + export const getPanicErrorFromText = (text: string) => { let result = text; const KEY = "panicked at "; diff --git a/front/next.config.js b/front/next.config.js index e682237..09d4642 100644 --- a/front/next.config.js +++ b/front/next.config.js @@ -13,7 +13,7 @@ const nextConfig = { env: { MINIMUM_AMOUNT_DEPOSIT: 1 }, - pageExtensions: ["page.tsx", "ts", "tsx"], + pageExtensions: ["page.tsx", "tsx"], }; module.exports = nextConfig; diff --git a/front/package.json b/front/package.json index aa47884..9673d67 100644 --- a/front/package.json +++ b/front/package.json @@ -15,15 +15,21 @@ "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", "@fontsource/inter": "^4.5.5", - "@near-wallet-selector/core": "^5.0.1", - "@near-wallet-selector/ledger": "^5.0.3", - "@near-wallet-selector/math-wallet": "^5.0.3", - "@near-wallet-selector/modal-ui": "^5.0.3", - "@near-wallet-selector/near-wallet": "^5.0.3", - "@near-wallet-selector/my-near-wallet": "^5.0.3", + "@meta-pool-apps/meta-shared-components": "^0.1.0", + "@near-wallet-selector/core": "^7.0.3", + "@near-wallet-selector/here-wallet": "^7.4.0", + "@near-wallet-selector/ledger": "^7.0.3", + "@near-wallet-selector/math-wallet": "^7.0.3", + "@near-wallet-selector/meteor-wallet": "^7.0.3", + "@near-wallet-selector/modal-ui": "^7.0.3", + "@near-wallet-selector/my-near-wallet": "^7.0.3", + "@near-wallet-selector/near-wallet": "^7.0.3", + "@near-wallet-selector/nightly": "^7.0.3", + "@near-wallet-selector/wallet-connect": "^7.0.3", "@types/gtag.js": "^0.0.10", "better-sqlite3": "^7.6.2", "bn": "^1.0.5", + "caniuse-lite": "^1.0.30001396", "dotenv": "^16.0.0", "formik": "^2.2.9", "framer-motion": "^6.2.8", @@ -37,14 +43,18 @@ "react": "17.0.2", "react-dom": "17.0.2", "react-query": "^3.34.16", + "rxjs": "^7.5.6", "web-vitals": "^2.1.4", "yup": "^0.32.11", "zustand": "^3.7.1" }, "devDependencies": { + "@carbon/icons-react": "^11.9.0", + "@carbon/type": "^11.10.0", "@testing-library/jest-dom": "^5.9.0", "@testing-library/react": "^10.2.1", "@testing-library/user-event": "^12.0.2", + "@types/carbon__icons-react": "^11.8.0", "@types/jest": "^25.0.0", "@types/node": "17.0.21", "@types/nprogress": "^0.2.0", @@ -53,6 +63,7 @@ "eslint": "8.11.0", "eslint-config-next": "12.1.0", "prettier": "2.6.2", + "react-query-devtools": "^2.6.3", "typescript": "4.6.2" } } diff --git a/front/pages/_app.page.tsx b/front/pages/_app.page.tsx index 8b48bb4..0c3bb0b 100644 --- a/front/pages/_app.page.tsx +++ b/front/pages/_app.page.tsx @@ -1,29 +1,43 @@ import "../styles/globals.css"; import type { AppProps } from "next/app"; -import { ChakraProvider } from "@chakra-ui/react"; +import { ChakraProvider, useToast } from "@chakra-ui/react"; import "@fontsource/inter/variable.css"; - +import { ReactQueryDevtools } from "react-query/devtools"; import theme from "../theme/theme"; import { QueryClient, QueryClientProvider } from "react-query"; import Router, { useRouter } from "next/router"; -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import * as gtag from "../lib/gtag"; import NProgress from "nprogress"; import NextHead from "next/head"; import "../styles/nprogress.css"; import Header from "./components/Header"; -import Footer from "./components/Footer"; import Fonts from "./components/Fonts"; import { WalletSelectorContextProvider } from "../contexts/WalletSelectorContext"; import "@near-wallet-selector/modal-ui/styles.css"; import Script from "next/script"; -const isProduction = process.env.NEXT_PUBLIC_VERCEL_ENV == 'production'; +import { blockerStore } from "../stores/pageBlocker"; +import PageBlocker from "./components/PageBlocker"; + +const isProduction = process.env.NEXT_PUBLIC_VERCEL_ENV == "production"; +const queryClient = new QueryClient(); function App({ Component, pageProps }: AppProps) { - const queryClient = new QueryClient(); const router = useRouter(); + const [isActive, setIsActive] = useState(false); + const [message, setMessage] = useState(""); + const state = blockerStore.getState(); + + useEffect( + () => + blockerStore.subscribe((state) => { + setIsActive(state.isActive); + setMessage(state.message); + }), + [] +); useEffect(() => { const handleRouteChange = (url: URL) => { /* invoke analytics function only for production */ @@ -56,9 +70,10 @@ function App({ Component, pageProps }: AppProps) { staking on Meta Pool. +
-