From 8ab6f88e3ba4fa074e7af2e4c655fbf8c8075265 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Fri, 20 Dec 2024 21:05:56 -0300 Subject: [PATCH 1/5] feat(ui): update packages and improve UX/UI --- frontend/package.json | 34 +++++--- frontend/src/config.js | 20 +++++ frontend/src/pages/_app.js | 10 ++- frontend/src/pages/index.js | 4 +- frontend/src/wallets/near.js | 126 +++++++++++++++++++++++++----- frontend/src/wallets/web3modal.js | 42 ++++++++++ 6 files changed, 204 insertions(+), 32 deletions(-) create mode 100644 frontend/src/wallets/web3modal.js diff --git a/frontend/package.json b/frontend/package.json index 0f31183..9114ed9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,22 +12,38 @@ "lint": "next lint" }, "dependencies": { - "@near-wallet-selector/core": "^8.9.11", - "@near-wallet-selector/here-wallet": "^8.9.11", - "@near-wallet-selector/modal-ui": "^8.9.11", - "@near-wallet-selector/my-near-wallet": "^8.9.11", + "@near-js/providers": "^1.0.1", + "@near-wallet-selector/bitte-wallet": "^8.9.14", + "@near-wallet-selector/core": "^8.9.14", + "@near-wallet-selector/ethereum-wallets": "^8.9.14", + "@near-wallet-selector/here-wallet": "^8.9.14", + "@near-wallet-selector/ledger": "^8.9.14", + "@near-wallet-selector/meteor-wallet": "^8.9.14", + "@near-wallet-selector/modal-ui": "^8.9.14", + "@near-wallet-selector/my-near-wallet": "^8.9.14", + "@near-wallet-selector/near-mobile-wallet": "^8.9.14", + "@near-wallet-selector/sender": "^8.9.14", + "@near-wallet-selector/welldone-wallet": "^8.9.14", + "@web3modal/wagmi": "^5.1.11", "bootstrap": "^5", "bootstrap-icons": "^1.11.3", - "near-api-js": "^4.0.3", - "next": "14.2.3", + "near-api-js": "^5.0.1", + "next": "14.2.5", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "wagmi": "^2.13.3" + }, + "overrides": { + "@near-wallet-selector/ethereum-wallets": { + "near-api-js": "4.0.3" + } }, "resolutions": { "near-api-js": "4.0.3" - }, + }, "devDependencies": { - "eslint": "^8", + "encoding": "^0.1.13", + "eslint": "^9", "eslint-config-next": "14.2.3" } } diff --git a/frontend/src/config.js b/frontend/src/config.js index 571fab8..b03b8dd 100644 --- a/frontend/src/config.js +++ b/frontend/src/config.js @@ -4,3 +4,23 @@ const contractPerNetwork = { export const NetworkId = "testnet"; export const CoinFlipContract = contractPerNetwork[NetworkId]; + +// Chains for EVM Wallets +const evmWalletChains = { + mainnet: { + chainId: 397, + name: 'Near Mainnet', + explorer: 'https://eth-explorer.near.org', + rpc: 'https://eth-rpc.mainnet.near.org', + }, + testnet: { + chainId: 398, + name: 'Near Testnet', + explorer: 'https://eth-explorer-testnet.near.org', + rpc: 'https://eth-rpc.testnet.near.org', + }, + }; + + + export const EVMWalletChain = evmWalletChains[NetworkId]; + \ No newline at end of file diff --git a/frontend/src/pages/_app.js b/frontend/src/pages/_app.js index a967392..1eb9d49 100644 --- a/frontend/src/pages/_app.js +++ b/frontend/src/pages/_app.js @@ -7,7 +7,15 @@ import { Navigation } from '@/components/Navigation'; import { Wallet } from '@/wallets/near'; import { NetworkId, CoinFlipContract } from '@/config'; -const wallet = new Wallet({ createAccessKeyFor: CoinFlipContract, networkId: NetworkId }); +// Wallet instance +// const wallet = new Wallet({ networkId: NetworkId }); + +// Optional: Create an access key so the user does not need to sign transactions. Read more about access keys here: https://docs.near.org/concepts/protocol/access-keys +const wallet = new Wallet({ + createAccessKeyFor: CoinFlipContract, + networkId: NetworkId, +}); + export default function MyApp({ Component, pageProps }) { const [signedAccountId, setSignedAccountId] = useState(''); diff --git a/frontend/src/pages/index.js b/frontend/src/pages/index.js index d4c8660..a51e990 100644 --- a/frontend/src/pages/index.js +++ b/frontend/src/pages/index.js @@ -32,11 +32,11 @@ export default function Home() { if (guess === outcome) { setStatus("You were right, you won a point!"); + setPoints(points+1); } else { setStatus("You were wrong, you lost a point"); + setPoints(points ? points - 1 : 0); } - - updateScore(); }; const updateScore = async () => { diff --git a/frontend/src/wallets/near.js b/frontend/src/wallets/near.js index f620195..1262160 100644 --- a/frontend/src/wallets/near.js +++ b/frontend/src/wallets/near.js @@ -1,13 +1,25 @@ -// near api js -import { providers } from 'near-api-js'; - // wallet selector -import { distinctUntilChanged, map } from 'rxjs'; import '@near-wallet-selector/modal-ui/styles.css'; -import { setupModal } from '@near-wallet-selector/modal-ui'; + +import { setupBitteWallet } from '@near-wallet-selector/bitte-wallet'; import { setupWalletSelector } from '@near-wallet-selector/core'; -import { setupHereWallet } from '@near-wallet-selector/here-wallet'; +import { setupEthereumWallets } from '@near-wallet-selector/ethereum-wallets'; +import { setupLedger } from '@near-wallet-selector/ledger'; +import { setupMeteorWallet } from '@near-wallet-selector/meteor-wallet'; +import { setupModal } from '@near-wallet-selector/modal-ui'; import { setupMyNearWallet } from '@near-wallet-selector/my-near-wallet'; +import { setupSender } from '@near-wallet-selector/sender'; +import { setupHereWallet } from '@near-wallet-selector/here-wallet'; +import { setupNearMobileWallet } from '@near-wallet-selector/near-mobile-wallet'; +import { setupWelldoneWallet } from '@near-wallet-selector/welldone-wallet'; + + +// near api js +import { providers, utils } from 'near-api-js'; +import { createContext } from 'react'; + +// ethereum wallets +import { wagmiConfig, web3Modal } from '@/wallets/web3modal'; const THIRTY_TGAS = '30000000000000'; const NO_DEPOSIT = '0'; @@ -30,27 +42,32 @@ export class Wallet { /** * To be called when the website loads * @param {Function} accountChangeHook - a function that is called when the user signs in or out# - * @returns {Promise} - the accountId of the signed-in user + * @returns {Promise} - the accountId of the signed-in user */ startUp = async (accountChangeHook) => { this.selector = setupWalletSelector({ network: this.networkId, - modules: [setupMyNearWallet(), setupHereWallet()] + modules: [ + setupMeteorWallet(), + setupEthereumWallets({ wagmiConfig, web3Modal, alwaysOnboardDuringSignIn: true }), + setupLedger(), + setupBitteWallet(), + setupHereWallet(), + setupSender(), + setupNearMobileWallet(), + setupWelldoneWallet(), + setupMyNearWallet(), + ], }); const walletSelector = await this.selector; const isSignedIn = walletSelector.isSignedIn(); const accountId = isSignedIn ? walletSelector.store.getState().accounts[0].accountId : ''; - walletSelector.store.observable - .pipe( - map(state => state.accounts), - distinctUntilChanged() - ) - .subscribe(accounts => { - const signedAccount = accounts.find((account) => account.active)?.accountId; - accountChangeHook(signedAccount); - }); + walletSelector.store.observable.subscribe(async (state) => { + const signedAccount = state?.accounts.find((account) => account.active)?.accountId; + accountChangeHook(signedAccount || ''); + }); return accountId; }; @@ -83,7 +100,7 @@ export class Wallet { const url = `https://rpc.${this.networkId}.near.org`; const provider = new providers.JsonRpcProvider({ url }); - let res = await provider.query({ + const res = await provider.query({ request_type: 'call_function', account_id: contractId, method_name: method, @@ -93,7 +110,6 @@ export class Wallet { return JSON.parse(Buffer.from(res.result).toString()); }; - /** * Makes a call to a contract * @param {Object} options - the options for the call @@ -126,7 +142,7 @@ export class Wallet { }; /** - * Retrieves transaction result from the network + * Makes a call to a contract * @param {string} txhash - the transaction hash * @returns {Promise} - the result of the transaction */ @@ -135,7 +151,77 @@ export class Wallet { const { network } = walletSelector.options; const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); + // Retrieve transaction result from the network const transaction = await provider.txStatus(txhash, 'unnused'); return providers.getTransactionLastResult(transaction); }; + + /** + * Gets the balance of an account + * @param {string} accountId - the account id to get the balance of + * @param {boolean} format - whether to format the balance + * @returns {Promise} - the balance of the account + * + */ + getBalance = async (accountId, format = false) => { + const walletSelector = await this.selector; + const { network } = walletSelector.options; + const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); + + // Retrieve account state from the network + const account = await provider.query({ + request_type: 'view_account', + account_id: accountId, + finality: 'final', + }); + + // Format the amount if needed + if (format) { + return account.amount ? utils.format.formatNearAmount(account.amount) : '0'; + } else { + return account.amount || '0'; + } + }; + + /** + * Signs and sends transactions + * @param {Object[]} transactions - the transactions to sign and send + * @returns {Promise} - the resulting transactions + * + */ + signAndSendTransactions = async ({ transactions }) => { + const selectedWallet = await (await this.selector).wallet(); + return selectedWallet.signAndSendTransactions({ transactions }); + }; + + /** + * + * @param {string} accountId + * @returns {Promise} - the access keys for the + */ + getAccessKeys = async (accountId) => { + const walletSelector = await this.selector; + const { network } = walletSelector.options; + const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); + + // Retrieve account state from the network + const keys = await provider.query({ + request_type: 'view_access_key_list', + account_id: accountId, + finality: 'final', + }); + return keys.keys; + }; } + +/** + * @typedef NearContext + * @property {import('./wallets/near').Wallet} wallet Current wallet + * @property {string} signedAccountId The AccountId of the signed user + */ + +/** @type {import ('react').Context} */ +export const NearContext = createContext({ + wallet: undefined, + signedAccountId: '', +}); diff --git a/frontend/src/wallets/web3modal.js b/frontend/src/wallets/web3modal.js new file mode 100644 index 0000000..0ce852e --- /dev/null +++ b/frontend/src/wallets/web3modal.js @@ -0,0 +1,42 @@ +import { injected,walletConnect } from '@wagmi/connectors'; +import { createConfig,http, reconnect } from '@wagmi/core'; +import { createWeb3Modal } from '@web3modal/wagmi'; + +import { EVMWalletChain,NetworkId } from '@/config'; + +// Config +const near = { + id: EVMWalletChain.chainId, + name: EVMWalletChain.name, + nativeCurrency: { + decimals: 18, + name: 'NEAR', + symbol: 'NEAR', + }, + rpcUrls: { + default: { http: [EVMWalletChain.rpc] }, + public: { http: [EVMWalletChain.rpc] }, + }, + blockExplorers: { + default: { + name: 'NEAR Explorer', + url: EVMWalletChain.explorer, + }, + }, + testnet: NetworkId === 'testnet', +}; + +// Get your projectId at https://cloud.reown.com +const projectId = '5bb0fe33763b3bea40b8d69e4269b4ae'; + +export const wagmiConfig = createConfig({ + chains: [near], + transports: { [near.id]: http() }, + connectors: [walletConnect({ projectId, showQrModal: false }), injected({ shimDisconnect: true })], +}); + +// Preserve login state on page reload +reconnect(wagmiConfig); + +// Modal for login +export const web3Modal = createWeb3Modal({ wagmiConfig, projectId }); From 6b410bf52873d44deb6be66cbdcdfa66efb33ab0 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Fri, 20 Dec 2024 21:07:08 -0300 Subject: [PATCH 2/5] feat(ui): update packages and improve UX/UI --- frontend/src/pages/_app.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/_app.js b/frontend/src/pages/_app.js index 1eb9d49..15ea045 100644 --- a/frontend/src/pages/_app.js +++ b/frontend/src/pages/_app.js @@ -8,13 +8,13 @@ import { Wallet } from '@/wallets/near'; import { NetworkId, CoinFlipContract } from '@/config'; // Wallet instance -// const wallet = new Wallet({ networkId: NetworkId }); +const wallet = new Wallet({ networkId: NetworkId }); // Optional: Create an access key so the user does not need to sign transactions. Read more about access keys here: https://docs.near.org/concepts/protocol/access-keys -const wallet = new Wallet({ - createAccessKeyFor: CoinFlipContract, - networkId: NetworkId, -}); +// const wallet = new Wallet({ +// createAccessKeyFor: CoinFlipContract, +// networkId: NetworkId, +// }); export default function MyApp({ Component, pageProps }) { From 415b07004563498e99023bc946b82d8dcdefdde2 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Fri, 27 Dec 2024 18:06:29 -0300 Subject: [PATCH 3/5] fix: correct parameter 'unnused' to 'unused' in txStatus --- frontend/src/wallets/near.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/wallets/near.js b/frontend/src/wallets/near.js index 1262160..81f0166 100644 --- a/frontend/src/wallets/near.js +++ b/frontend/src/wallets/near.js @@ -152,7 +152,7 @@ export class Wallet { const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); // Retrieve transaction result from the network - const transaction = await provider.txStatus(txhash, 'unnused'); + const transaction = await provider.txStatus(txhash, 'unused'); return providers.getTransactionLastResult(transaction); }; From b7ced3d27a6bfbaa3b6cfb8c6e8505d67b7e8ed8 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Sat, 4 Jan 2025 18:46:07 +0100 Subject: [PATCH 4/5] minor fixes --- frontend/package.json | 12 ++---------- frontend/src/pages/_app.js | 13 +++++-------- frontend/src/pages/index.js | 32 +++++++++++++------------------- 3 files changed, 20 insertions(+), 37 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 9114ed9..828218a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,23 +27,15 @@ "@web3modal/wagmi": "^5.1.11", "bootstrap": "^5", "bootstrap-icons": "^1.11.3", - "near-api-js": "^5.0.1", + "near-api-js": "^4.0.3", "next": "14.2.5", "react": "^18", "react-dom": "^18", "wagmi": "^2.13.3" }, - "overrides": { - "@near-wallet-selector/ethereum-wallets": { - "near-api-js": "4.0.3" - } - }, - "resolutions": { - "near-api-js": "4.0.3" - }, "devDependencies": { "encoding": "^0.1.13", - "eslint": "^9", + "eslint": "^8", "eslint-config-next": "14.2.3" } } diff --git a/frontend/src/pages/_app.js b/frontend/src/pages/_app.js index 15ea045..b4ffded 100644 --- a/frontend/src/pages/_app.js +++ b/frontend/src/pages/_app.js @@ -7,14 +7,11 @@ import { Navigation } from '@/components/Navigation'; import { Wallet } from '@/wallets/near'; import { NetworkId, CoinFlipContract } from '@/config'; -// Wallet instance -const wallet = new Wallet({ networkId: NetworkId }); - -// Optional: Create an access key so the user does not need to sign transactions. Read more about access keys here: https://docs.near.org/concepts/protocol/access-keys -// const wallet = new Wallet({ -// createAccessKeyFor: CoinFlipContract, -// networkId: NetworkId, -// }); +// Create an access key so the user does not need to sign transactions. Read more about access keys here: https://docs.near.org/concepts/protocol/access-keys +const wallet = new Wallet({ + createAccessKeyFor: CoinFlipContract, + networkId: NetworkId, +}); export default function MyApp({ Component, pageProps }) { diff --git a/frontend/src/pages/index.js b/frontend/src/pages/index.js index a51e990..885ce83 100644 --- a/frontend/src/pages/index.js +++ b/frontend/src/pages/index.js @@ -13,7 +13,14 @@ export default function Home() { const [choice, setChoice] = useState(); useEffect(() => { - if (signedAccountId) updateScore(); + if (!signedAccountId) return; + + wallet.viewMethod({ + contractId: CoinFlipContract, + method: "points_of", + args: { player: signedAccountId }, + }).then((score) => setPoints(score)) + }, [signedAccountId]); const handleChoice = async (guess) => { @@ -32,24 +39,13 @@ export default function Home() { if (guess === outcome) { setStatus("You were right, you won a point!"); - setPoints(points+1); + setPoints(points + 1); } else { setStatus("You were wrong, you lost a point"); setPoints(points ? points - 1 : 0); } }; - const updateScore = async () => { - - const score = await wallet.viewMethod({ - contractId: CoinFlipContract, - method: "points_of", - args: { player: signedAccountId }, - }); - - setPoints(score); - }; - let color = choice === side ? "btn-success" : "btn-danger"; return ( @@ -68,17 +64,15 @@ export default function Home() {