diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json
index bffb357..38e898a 100644
--- a/frontend/.eslintrc.json
+++ b/frontend/.eslintrc.json
@@ -1,3 +1,10 @@
{
- "extends": "next/core-web-vitals"
+ "extends": [
+ "next/core-web-vitals" // extended set of recommended rules from Next.js
+ ],
+ "plugins": ["simple-import-sort"],
+ "root": true,
+ "rules": {
+ "simple-import-sort/imports": "warn"
+ }
}
diff --git a/frontend/next.config.js b/frontend/next.config.js
index 25f7984..91ef62f 100644
--- a/frontend/next.config.js
+++ b/frontend/next.config.js
@@ -1,14 +1,6 @@
-const isProduction = process.env.NODE_ENV === 'production'
-
/** @type {import('next').NextConfig} */
const nextConfig = {
- images: {
- unoptimized: true,
- },
- basePath: isProduction ? '/hello-near-examples' : '',
- output: "export",
- distDir: 'build',
reactStrictMode: true,
-}
+};
-module.exports = nextConfig;
\ No newline at end of file
+module.exports = nextConfig;
diff --git a/frontend/package.json b/frontend/package.json
index 0008e24..cddf082 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -16,25 +16,33 @@
"@near-wallet-selector/bitte-wallet": "^8.9.13",
"@near-wallet-selector/core": "^8.9.13",
"@near-wallet-selector/ethereum-wallets": "^8.9.13",
+ "@near-wallet-selector/here-wallet": "^8.9.13",
+ "@near-wallet-selector/ledger": "^8.9.13",
"@near-wallet-selector/meteor-wallet": "^8.9.13",
"@near-wallet-selector/modal-ui": "^8.9.13",
"@near-wallet-selector/my-near-wallet": "^8.9.13",
- "@web3modal/wagmi": "^5.1.9",
+ "@near-wallet-selector/sender": "^8.9.13",
+ "@web3modal/wagmi": "^5.1.10",
"bootstrap": "^5",
"bootstrap-icons": "^1.11.3",
- "near-api-js": "^5",
+ "near-api-js": "^4.0.3",
"next": "14.2.13",
"react": "^18",
"react-dom": "^18",
- "wagmi": "^2.12.14"
+ "wagmi": "^2.12.16"
},
- "resolutions": {
- "near-api-js": "4.0.3"
+ "overrides": {
+ "@near-wallet-selector/ethereum-wallets": {
+ "near-api-js": "4.0.3"
+ }
},
"devDependencies": {
+ "@babel/preset-env": "^7.25.4",
+ "@babel/preset-react": "^7.24.7",
"encoding": "^0.1.13",
- "eslint": "^9",
+ "eslint": "^8.57.1",
"eslint-config-next": "14.2.13",
+ "eslint-plugin-simple-import-sort": "^12.1.1",
"pino-pretty": "^11.2.2"
}
}
diff --git a/frontend/src/components/cards.js b/frontend/src/components/cards.js
index 604a493..ad5307c 100644
--- a/frontend/src/components/cards.js
+++ b/frontend/src/components/cards.js
@@ -8,7 +8,7 @@ export const Cards = () => {
@@ -17,11 +17,7 @@ export const Cards = () => {
Learn how this application works, and what you can build on Near.
-
+
Near Integration ->
@@ -29,4 +25,4 @@ export const Cards = () => {
);
-};
\ No newline at end of file
+};
diff --git a/frontend/src/components/navigation.js b/frontend/src/components/navigation.js
index 6849b8b..101f9f0 100644
--- a/frontend/src/components/navigation.js
+++ b/frontend/src/components/navigation.js
@@ -1,13 +1,13 @@
import Image from 'next/image';
import Link from 'next/link';
-import { useEffect, useState, useContext } from 'react';
+import { useContext,useEffect, useState } from 'react';
-import { NearContext } from '@/context';
import NearLogo from '/public/near-logo.svg';
+import { NearContext } from '@/wallets/near';
export const Navigation = () => {
const { signedAccountId, wallet } = useContext(NearContext);
- const [action, setAction] = useState(() => { });
+ const [action, setAction] = useState(() => {});
const [label, setLabel] = useState('Loading...');
useEffect(() => {
@@ -28,10 +28,12 @@ export const Navigation = () => {
-
-
+
+
);
-};
\ No newline at end of file
+};
diff --git a/frontend/src/config.js b/frontend/src/config.js
index eb0ffd9..bbdaaf5 100644
--- a/frontend/src/config.js
+++ b/frontend/src/config.js
@@ -3,22 +3,22 @@ const contractPerNetwork = {
testnet: 'hello.near-examples.testnet',
};
-// Chains for EVM Wallets
+// 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",
+ 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",
+ name: 'Near Testnet',
+ explorer: 'https://eth-explorer-testnet.near.org',
+ rpc: 'https://eth-rpc.testnet.near.org',
},
-}
+};
export const NetworkId = 'testnet';
export const HelloNearContract = contractPerNetwork[NetworkId];
-export const EVMWalletChain = evmWalletChains[NetworkId];
\ No newline at end of file
+export const EVMWalletChain = evmWalletChains[NetworkId];
diff --git a/frontend/src/context.js b/frontend/src/context.js
deleted file mode 100644
index 7422309..0000000
--- a/frontend/src/context.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { createContext } from 'react';
-
-/**
- * @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: ''
-});
\ No newline at end of file
diff --git a/frontend/src/pages/_app.js b/frontend/src/pages/_app.js
index 6294258..93be4b1 100644
--- a/frontend/src/pages/_app.js
+++ b/frontend/src/pages/_app.js
@@ -1,18 +1,19 @@
+import '@/styles/globals.css';
+
import { useEffect, useState } from 'react';
-import '@/styles/globals.css';
-import { NearContext } from '@/context';
import { Navigation } from '@/components/navigation';
-
-import { Wallet } from '@/wallets/near';
import { NetworkId } from '@/config';
+import { NearContext,Wallet } from '@/wallets/near';
const wallet = new Wallet({ networkId: NetworkId });
export default function MyApp({ Component, pageProps }) {
const [signedAccountId, setSignedAccountId] = useState('');
- useEffect(() => { wallet.startUp(setSignedAccountId) }, []);
+ useEffect(() => {
+ wallet.startUp(setSignedAccountId);
+ }, []);
return (
diff --git a/frontend/src/pages/hello-near/index.js b/frontend/src/pages/hello-near/index.js
index 055d42e..54c19b4 100644
--- a/frontend/src/pages/hello-near/index.js
+++ b/frontend/src/pages/hello-near/index.js
@@ -1,9 +1,10 @@
-import { useState, useEffect, useContext } from 'react';
+import { useContext,useEffect, useState } from 'react';
-import { NearContext } from '@/context';
+import { Cards } from '@/components/cards';
import styles from '@/styles/app.module.css';
+import { NearContext } from '@/wallets/near';
+
import { HelloNearContract } from '../../config';
-import { Cards } from '@/components/cards';
// Contract that the app will interact with
const CONTRACT = HelloNearContract;
@@ -19,9 +20,7 @@ export default function HelloNear() {
useEffect(() => {
if (!wallet) return;
- wallet.viewMethod({ contractId: CONTRACT, method: 'get_greeting' }).then(
- greeting => setGreeting(greeting)
- );
+ wallet.viewMethod({ contractId: CONTRACT, method: 'get_greeting' }).then((greeting) => setGreeting(greeting));
}, [wallet]);
useEffect(() => {
@@ -30,11 +29,9 @@ export default function HelloNear() {
const saveGreeting = async () => {
setShowSpinner(true);
- try {
- await wallet.callMethod({ contractId: CONTRACT, method: 'set_greeting', args: { greeting: newGreeting } });
- const greeting = await wallet.viewMethod({ contractId: CONTRACT, method: 'get_greeting' });
- setGreeting(greeting);
- } catch (e) { console.error(e); }
+ await wallet.callMethod({ contractId: CONTRACT, method: 'set_greeting', args: { greeting: newGreeting } });
+ const greeting = await wallet.viewMethod({ contractId: CONTRACT, method: 'get_greeting' });
+ setGreeting(greeting);
setShowSpinner(false);
};
@@ -56,15 +53,12 @@ export default function HelloNear() {
type="text"
className="form-control w-20"
placeholder="Store a new greeting"
- onChange={t => setNewGreeting(t.target.value)}
+ onChange={(t) => setNewGreeting(t.target.value)}
/>
@@ -75,4 +69,4 @@ export default function HelloNear() {
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/pages/index.js b/frontend/src/pages/index.js
index b90e0cc..acb1990 100644
--- a/frontend/src/pages/index.js
+++ b/frontend/src/pages/index.js
@@ -2,8 +2,8 @@ import Image from 'next/image';
import NearLogo from '/public/near.svg';
import NextLogo from '/public/next.svg';
-import styles from '@/styles/app.module.css';
import { Cards } from '@/components/cards';
+import styles from '@/styles/app.module.css';
export default function Home() {
return (
@@ -11,21 +11,14 @@ export default function Home() {
-
+
+
@@ -35,4 +28,4 @@ export default function Home() {
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/styles/app.module.css b/frontend/src/styles/app.module.css
index 85e5c04..be4a369 100644
--- a/frontend/src/styles/app.module.css
+++ b/frontend/src/styles/app.module.css
@@ -51,7 +51,9 @@
border-radius: var(--border-radius);
background: rgba(var(--card-rgb), 0);
border: 1px solid rgba(var(--card-border-rgb), 0);
- transition: background 200ms, border 200ms;
+ transition:
+ background 200ms,
+ border 200ms;
}
.card span {
@@ -180,9 +182,7 @@
border-radius: 0;
border: none;
border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
- background: linear-gradient(to bottom,
- rgba(var(--background-start-rgb), 1),
- rgba(var(--callout-rgb), 0.5));
+ background: linear-gradient(to bottom, rgba(var(--background-start-rgb), 1), rgba(var(--callout-rgb), 0.5));
background-clip: padding-box;
backdrop-filter: blur(24px);
}
@@ -193,9 +193,7 @@
inset: auto 0 0;
padding: 2rem;
height: 200px;
- background: linear-gradient(to bottom,
- transparent 0%,
- rgb(var(--background-end-rgb)) 40%);
+ background: linear-gradient(to bottom, transparent 0%, rgb(var(--background-end-rgb)) 40%);
z-index: 1;
}
}
@@ -225,4 +223,4 @@
to {
transform: rotate(0deg);
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css
index 31b5368..c89a275 100644
--- a/frontend/src/styles/globals.css
+++ b/frontend/src/styles/globals.css
@@ -9,24 +9,19 @@
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
- --primary-glow: conic-gradient(from 180deg at 50% 50%,
- #16abff33 0deg,
- #0885ff33 55deg,
- #54d6ff33 120deg,
- #0071ff33 160deg,
- transparent 360deg);
- --secondary-glow: radial-gradient(rgba(255, 255, 255, 1),
- rgba(255, 255, 255, 0));
+ --primary-glow: conic-gradient(
+ from 180deg at 50% 50%,
+ #16abff33 0deg,
+ #0885ff33 55deg,
+ #54d6ff33 120deg,
+ #0071ff33 160deg,
+ transparent 360deg
+ );
+ --secondary-glow: radial-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
--tile-start-rgb: 239, 245, 249;
--tile-end-rgb: 228, 232, 233;
- --tile-border: conic-gradient(#00000080,
- #00000040,
- #00000030,
- #00000020,
- #00000010,
- #00000010,
- #00000080);
+ --tile-border: conic-gradient(#00000080, #00000040, #00000030, #00000020, #00000010, #00000010, #00000080);
--callout-rgb: 238, 240, 241;
--callout-border-rgb: 172, 175, 176;
@@ -41,20 +36,11 @@
--background-end-rgb: 0, 0, 0;
--primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
- --secondary-glow: linear-gradient(to bottom right,
- rgba(1, 65, 255, 0),
- rgba(1, 65, 255, 0),
- rgba(1, 65, 255, 0.3));
+ --secondary-glow: linear-gradient(to bottom right, rgba(1, 65, 255, 0), rgba(1, 65, 255, 0), rgba(1, 65, 255, 0.3));
--tile-start-rgb: 2, 13, 46;
--tile-end-rgb: 2, 5, 19;
- --tile-border: conic-gradient(#ffffff80,
- #ffffff40,
- #ffffff30,
- #ffffff20,
- #ffffff10,
- #ffffff10,
- #ffffff80);
+ --tile-border: conic-gradient(#ffffff80, #ffffff40, #ffffff30, #ffffff20, #ffffff10, #ffffff10, #ffffff80);
--callout-rgb: 20, 20, 20;
--callout-border-rgb: 108, 108, 108;
@@ -77,10 +63,17 @@ body {
body {
color: rgb(var(--foreground-rgb));
- background: linear-gradient(to bottom,
- transparent,
- rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb));
- font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Noto Sans, Ubuntu, Droid Sans, Helvetica Neue, sans-serif;
+ background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb));
+ font-family:
+ -apple-system,
+ BlinkMacSystemFont,
+ Segoe UI,
+ Roboto,
+ Noto Sans,
+ Ubuntu,
+ Droid Sans,
+ Helvetica Neue,
+ sans-serif;
}
a {
@@ -92,4 +85,4 @@ a {
html {
color-scheme: dark;
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/wallets/near.js b/frontend/src/wallets/near.js
index 1515ae3..6a066ab 100644
--- a/frontend/src/wallets/near.js
+++ b/frontend/src/wallets/near.js
@@ -1,18 +1,21 @@
-// 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 { setupWalletSelector } from '@near-wallet-selector/core';
-import { setupMyNearWallet } from '@near-wallet-selector/my-near-wallet';
+
import { setupBitteWallet } from '@near-wallet-selector/bitte-wallet';
+import { setupWalletSelector } from '@near-wallet-selector/core';
+import { setupEthereumWallets } from '@near-wallet-selector/ethereum-wallets';
+import { setupHereWallet } from '@near-wallet-selector/here-wallet';
+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';
+// near api js
+import { providers, utils } from 'near-api-js';
+import { createContext } from 'react';
// ethereum wallets
import { wagmiConfig, web3Modal } from '@/wallets/web3modal';
-import { setupEthereumWallets } from "@near-wallet-selector/ethereum-wallets";
const THIRTY_TGAS = '30000000000000';
const NO_DEPOSIT = '0';
@@ -22,9 +25,9 @@ export class Wallet {
* @constructor
* @param {Object} options - the options for the wallet
* @param {string} options.networkId - the network id to connect to
- * @param {string} options.createAccessKeyFor - (optional) create a function call key for a contract
+ * @param {string} options.createAccessKeyFor - the contract to create an access key for
* @example
- * const wallet = new Wallet({ networkId: 'testnet'});
+ * const wallet = new Wallet({ networkId: 'testnet', createAccessKeyFor: 'contractId' });
* wallet.startUp((signedAccountId) => console.log(signedAccountId));
*/
constructor({ networkId = 'testnet', createAccessKeyFor = undefined }) {
@@ -35,32 +38,30 @@ 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(),
- setupBitteWallet(),
+ setupHereWallet(),
+ setupLedger(),
setupMeteorWallet(),
+ setupSender(),
+ setupBitteWallet(),
setupEthereumWallets({ wagmiConfig, web3Modal, alwaysOnboardDuringSignIn: true }),
- ]
+ ],
});
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;
};
@@ -93,7 +94,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,
@@ -103,7 +104,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
@@ -136,7 +136,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
*/
@@ -145,7 +145,71 @@ 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
+ * @returns {Promise} - the balance of the account
+ *
+ */
+ getBalance = 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 account = await provider.query({
+ request_type: 'view_account',
+ account_id: accountId,
+ finality: 'final',
+ });
+ // return amount on NEAR
+ return account.amount ? Number(utils.format.formatNearAmount(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