Skip to content

Commit

Permalink
feat(ui): update packages and improve UX/UI
Browse files Browse the repository at this point in the history
  • Loading branch information
matiasbenary committed Dec 21, 2024
1 parent c28b068 commit 992f489
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 68 deletions.
33 changes: 23 additions & 10 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,38 @@
"lint": "next lint"
},
"dependencies": {
"@near-wallet-selector/bitte-wallet": "^8.9.12",
"@near-wallet-selector/core": "^8.9.11",
"@near-wallet-selector/here-wallet": "^8.9.11",
"@near-wallet-selector/meteor-wallet": "^8.9.12",
"@near-wallet-selector/mintbase-wallet": "^8.9.12",
"@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",
"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"
}
}
19 changes: 19 additions & 0 deletions frontend/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,22 @@ const contractPerNetwork = {

export const NetworkId = "testnet";
export const GuestbookNearContract = 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];
12 changes: 8 additions & 4 deletions frontend/src/pages/_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import { Navigation } from "@/components/Navigation";
import { Wallet } from "@/wallets/near";
import { NetworkId, GuestbookNearContract } from "@/config";

const wallet = new Wallet({
createAccessKeyFor: GuestbookNearContract,
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: GuestbookNearContract,
// networkId: NetworkId,
// });

export default function MyApp({ Component, pageProps }) {
const [signedAccountId, setSignedAccountId] = useState("");
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,20 @@ export default function Home() {
const { fieldset, message, donation } = e.target.elements;

fieldset.disabled = true;

// Add message to the guest book
const deposit = utils.format.parseNearAmount(donation.value);
await wallet.callMethod({
wallet.callMethod({
contractId: GuestbookNearContract,
method: "add_message",
args: { text: message.value },
deposit,
}).catch(()=>{
setMessages(messages.slice(1));
});

// Get updated messages
const messages = await getLast10Messages();
setMessages(messages.reverse());

await new Promise(resolve => setTimeout(resolve, 300));
setMessages([{ sender: signedAccountId, text: message.value, premium: donation.value >= 1 }, ...messages]);
message.value = "";
donation.value = "0";
fieldset.disabled = false;
Expand Down
168 changes: 120 additions & 48 deletions frontend/src/wallets/near.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
// wallet selector
import '@near-wallet-selector/modal-ui/styles.css';

import { setupBitteWallet } from '@near-wallet-selector/bitte-wallet';
import { setupWalletSelector } from '@near-wallet-selector/core';
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 } from "near-api-js";
import { providers, utils } from 'near-api-js';
import { createContext } from 'react';

// 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 { setupHereWallet } from "@near-wallet-selector/here-wallet";
import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet";
import { setupMeteorWallet } from "@near-wallet-selector/meteor-wallet";
import { setupMintbaseWallet } from "@near-wallet-selector/mintbase-wallet";
import { setupBitteWallet } from "@near-wallet-selector/bitte-wallet";

const THIRTY_TGAS = "30000000000000";
const NO_DEPOSIT = "0";
// ethereum wallets
import { wagmiConfig, web3Modal } from '@/wallets/web3modal';

const THIRTY_TGAS = '30000000000000';
const NO_DEPOSIT = '0';

export class Wallet {
/**
Expand All @@ -25,7 +34,7 @@ export class Wallet {
* const wallet = new Wallet({ networkId: 'testnet', createAccessKeyFor: 'contractId' });
* wallet.startUp((signedAccountId) => console.log(signedAccountId));
*/
constructor({ networkId = "testnet", createAccessKeyFor = undefined }) {
constructor({ networkId = 'testnet', createAccessKeyFor = undefined }) {
this.createAccessKeyFor = createAccessKeyFor;
this.networkId = networkId;
}
Expand All @@ -38,26 +47,27 @@ export class Wallet {
startUp = async (accountChangeHook) => {
this.selector = setupWalletSelector({
network: this.networkId,
modules: [setupMyNearWallet(), setupHereWallet(), setupBitteWallet(), setupMeteorWallet(), setupMintbaseWallet()],
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);
});
const accountId = isSignedIn ? walletSelector.store.getState().accounts[0].accountId : '';

walletSelector.store.observable.subscribe(async (state) => {
const signedAccount = state?.accounts.find((account) => account.active)?.accountId;
accountChangeHook(signedAccount || '');
});

return accountId;
};
Expand All @@ -66,9 +76,7 @@ export class Wallet {
* Displays a modal to login the user
*/
signIn = async () => {
const modal = setupModal(await this.selector, {
contractId: this.createAccessKeyFor,
});
const modal = setupModal(await this.selector, { contractId: this.createAccessKeyFor });
modal.show();
};

Expand All @@ -92,12 +100,12 @@ export class Wallet {
const url = `https://rpc.${this.networkId}.near.org`;
const provider = new providers.JsonRpcProvider({ url });

let res = await provider.query({
request_type: "call_function",
const res = await provider.query({
request_type: 'call_function',
account_id: contractId,
method_name: method,
args_base64: Buffer.from(JSON.stringify(args)).toString("base64"),
finality: "optimistic",
args_base64: Buffer.from(JSON.stringify(args)).toString('base64'),
finality: 'optimistic',
});
return JSON.parse(Buffer.from(res.result).toString());
};
Expand All @@ -112,20 +120,14 @@ export class Wallet {
* @param {string} options.deposit - the amount of yoctoNEAR to deposit
* @returns {Promise<Transaction>} - the resulting transaction
*/
callMethod = async ({
contractId,
method,
args = {},
gas = THIRTY_TGAS,
deposit = NO_DEPOSIT,
}) => {
callMethod = async ({ contractId, method, args = {}, gas = THIRTY_TGAS, deposit = NO_DEPOSIT }) => {
// Sign a transaction with the "FunctionCall" action
const selectedWallet = await (await this.selector).wallet();
const outcome = await selectedWallet.signAndSendTransaction({
receiverId: contractId,
actions: [
{
type: "FunctionCall",
type: 'FunctionCall',
params: {
methodName: method,
args,
Expand All @@ -140,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<JSON.value>} - the result of the transaction
*/
Expand All @@ -149,7 +151,77 @@ export class Wallet {
const { network } = walletSelector.options;
const provider = new providers.JsonRpcProvider({ url: network.nodeUrl });

const transaction = await provider.txStatus(txhash, "unnused");
// 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<number>} - 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<Transaction[]>} - the resulting transactions
*
*/
signAndSendTransactions = async ({ transactions }) => {
const selectedWallet = await (await this.selector).wallet();
return selectedWallet.signAndSendTransactions({ transactions });
};

/**
*
* @param {string} accountId
* @returns {Promise<Object[]>} - 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<NearContext>} */
export const NearContext = createContext({
wallet: undefined,
signedAccountId: '',
});
42 changes: 42 additions & 0 deletions frontend/src/wallets/web3modal.js
Original file line number Diff line number Diff line change
@@ -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 });

0 comments on commit 992f489

Please sign in to comment.