Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): update packages and improve UX/UI #28

Merged
merged 5 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions contract-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ crate-type = ["cdylib", "rlib"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
near-sdk = { version = "5.1.0", features = ["legacy"] }
near-sdk = { version = "5.7.0", features = ["legacy"] }

[dev-dependencies]
near-sdk = { version = "5.1.0", features = ["unit-testing"] }
near-workspaces = { version = "0.10.0", features = ["unstable"] }
near-sdk = { version = "5.7.0", features = ["unit-testing"] }
near-workspaces = { version = "0.16.0", features = ["unstable"] }
tokio = { version = "1.12.0", features = ["full"] }
serde_json = "1"

Expand Down
26 changes: 17 additions & 9 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,29 @@
"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",
"next": "14.2.5",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"wagmi": "^2.13.3"
},
"resolutions": {
"near-api-js": "4.0.3"
},
"devDependencies": {
"encoding": "^0.1.13",
"eslint": "^8",
"eslint-config-next": "14.2.3"
}
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];

7 changes: 6 additions & 1 deletion frontend/src/pages/_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import { Navigation } from '@/components/Navigation';
import { Wallet } from '@/wallets/near';
import { NetworkId, CoinFlipContract } from '@/config';

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 }) {
const [signedAccountId, setSignedAccountId] = useState('');
Expand Down
34 changes: 14 additions & 20 deletions frontend/src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -32,22 +39,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 () => {

const score = await wallet.viewMethod({
contractId: CoinFlipContract,
method: "points_of",
args: { player: signedAccountId },
});

setPoints(score);
};

let color = choice === side ? "btn-success" : "btn-danger";
Expand All @@ -68,17 +64,15 @@ export default function Home() {
</h2>
<div className="d-flex justify-content-center">
<button
className={`btn me-2 ${
choice === "heads" && side !== 'loading' ? color : "btn-primary"
}`}
className={`btn me-2 ${choice === "heads" && side !== 'loading' ? color : "btn-primary"
}`}
onClick={() => handleChoice("heads")}
>
Heads
</button>
<button
className={`btn ${
choice === "tails" && side !== 'loading' ? color : "btn-primary"
}`}
className={`btn ${choice === "tails" && side !== 'loading' ? color : "btn-primary"
}`}
onClick={() => handleChoice("tails")}
>
Tails
Expand Down
128 changes: 107 additions & 21 deletions frontend/src/wallets/near.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<string>} - the accountId of the signed-in user
* @returns {Promise<string>} - 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;
};
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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<JSON.value>} - the result of the transaction
*/
Expand All @@ -135,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, 'unused');
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 });
Loading