Skip to content

Commit

Permalink
Merge pull request #52 from near-examples/add_base_chain
Browse files Browse the repository at this point in the history
Add Base network to the chain selector
  • Loading branch information
matiasbenary authored Feb 7, 2025
2 parents aafcfe2 + 7a4c2a0 commit 0022e74
Show file tree
Hide file tree
Showing 7 changed files with 551 additions and 181 deletions.
7 changes: 6 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { useEffect, useState } from 'react';
import { NearContext } from './context';
import { Wallet } from './services/near-wallet';
import Navbar from './components/Navbar';
import { EthereumView } from './components/Ethereum/Ethereum';
import { EthereumView } from './components/EVM/Ethereum';
import { BaseView } from './components/EVM/Base';
import { BitcoinView } from './components/Bitcoin';
import { MPC_CONTRACT } from './services/kdf/mpc';

Expand Down Expand Up @@ -68,13 +69,17 @@ function App() {
onChange={(e) => setChain(e.target.value)}
>
<option value='eth'> Ξ Ethereum </option>
<option value='base'> Ξ Base </option>
<option value='btc'> ₿ BTC </option>
</select>
</div>

{chain === 'eth' && (
<EthereumView props={{ setStatus, MPC_CONTRACT }} />
)}
{chain === 'base' && (
<BaseView props={{ setStatus, MPC_CONTRACT }} />
)}
{chain === 'btc' && (
<BitcoinView props={{ setStatus, MPC_CONTRACT }} />
)}
Expand Down
325 changes: 325 additions & 0 deletions src/components/EVM/Base.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
import { useState, useEffect, useContext, useRef } from "react";
import PropTypes from "prop-types";

import { NearContext } from "../../context";
import { useDebounce } from "../../hooks/debounce";
import { getTransactionHashes } from "../../services/utils";
import { TransferForm } from "./Transfer";
import { FunctionCallForm } from "./FunctionCall";
import { EthereumVM } from "../../services/evm";

const Evm = new EthereumVM("https://base-sepolia.drpc.org");

const contractAddress = "0xCd3b988b216790C598d9AB85Eee189e446CE526D";

const transactions = getTransactionHashes();

export function BaseView({ props: { setStatus, MPC_CONTRACT } }) {
const { wallet, signedAccountId } = useContext(NearContext);

const [loading, setLoading] = useState(false);
const [step, setStep] = useState(transactions ? "relay" : "request");
const [signedTransaction, setSignedTransaction] = useState(null);
const [senderLabel, setSenderLabel] = useState("");
const [senderAddress, setSenderAddress] = useState("");
const [balance, setBalance] = useState(""); // Add balance state
const [action, setAction] = useState("transfer");
const [derivation, setDerivation] = useState(
sessionStorage.getItem("derivation") || "ethereum-1"
);
const [reloaded, setReloaded] = useState(transactions.length);

const [gasPriceInGwei, setGasPriceInGwei] = useState("");
const [txCost, setTxCost] = useState("");

const derivationPath = useDebounce(derivation, 1200);
const childRef = useRef();

useEffect(() => {
async function fetchEthereumGasPrice() {
try {
// Fetch gas price in Wei
const gasPriceInWei = await Evm.web3.eth.getGasPrice();

// Convert gas price from Wei to Gwei
const gasPriceInGwei = Evm.web3.utils.fromWei(gasPriceInWei, "gwei");

// Gas limit for a standard ETH transfer
const gasLimit = 21000;

// Calculate transaction cost in ETH (gwei * gasLimit) / 1e9
const txCost = (gasPriceInGwei * gasLimit) / 1000000000;

// Format both gas price and transaction cost to 7 decimal places
const formattedGasPriceInGwei = parseFloat(gasPriceInGwei).toFixed(7);
const formattedTxCost = parseFloat(txCost).toFixed(7);

console.log(
`Current Sepolia Gas Price: ${formattedGasPriceInGwei} Gwei`
);
console.log(`Estimated Transaction Cost: ${formattedTxCost} ETH`);

setTxCost(formattedTxCost);
setGasPriceInGwei(formattedGasPriceInGwei);
} catch (error) {
console.error("Error fetching gas price:", error);
}
}

fetchEthereumGasPrice();
}, []);

// Handle signing transaction when the page is reloaded and senderAddress is set
useEffect(() => {
if (reloaded && senderAddress) {
signTransaction();
}

async function signTransaction() {
const { big_r, s, recovery_id } = await wallet.getTransactionResult(
transactions[0]
);
const signedTransaction = await Evm.reconstructSignedTXFromLocalSession(
big_r,
s,
recovery_id,
senderAddress
);

setSignedTransaction(signedTransaction);
setStatus(
"✅ Signed payload ready to be relayed to the Base network"
);
setStep("relay");

setReloaded(false);
removeUrlParams();
}
}, [senderAddress, reloaded, wallet, setStatus]);

// Handle changes to derivation path and query Base address and balance
useEffect(() => {
resetAddressState();
fetchEthereumAddress();
}, [derivationPath, signedAccountId]);

const resetAddressState = () => {
setSenderLabel("Waiting for you to stop typing...");
setSenderAddress(null);
setStatus("");
setBalance(""); // Reset balance when derivation path changes
setStep("request");
};

const fetchEthereumAddress = async () => {
const { address } = await Evm.deriveAddress(
signedAccountId,
derivationPath
);
setSenderAddress(address);
setSenderLabel(address);

if (!reloaded) {
const balance = await Evm.getBalance(address);
setBalance(balance); // Update balance state
}
};

async function chainSignature() {
setStatus("🏗️ Creating transaction");

const { transaction } = await childRef.current.createTransaction();

setStatus(
`🕒 Asking ${MPC_CONTRACT} to sign the transaction, this might take a while`
);
try {
// to reconstruct on reload
sessionStorage.setItem("derivation", derivationPath);

const { big_r, s, recovery_id } = await Evm.requestSignatureToMPC({
wallet,
path: derivationPath,
transaction,
});
const signedTransaction = await Evm.reconstructSignedTransaction(
big_r,
s,
recovery_id,
transaction
);

setSignedTransaction(signedTransaction);
setStatus(
`✅ Signed payload ready to be relayed to the Base network`
);
setStep("relay");
} catch (e) {
setStatus(`❌ Error: ${e.message}`);
setLoading(false);
}
}

async function relayTransaction() {
setLoading(true);
setStatus(
"🔗 Relaying transaction to the Base network... this might take a while"
);

try {
const txHash = await Evm.broadcastTX(signedTransaction);
setStatus(
<>
<a href={`https://base-sepolia.blockscout.com/tx/${txHash}`} target="_blank">
{" "}
✅ Successful{" "}
</a>
</>
);
childRef.current.afterRelay();
} catch (e) {
setStatus(`❌ Error: ${e.message}`);
}

setStep("request");
setLoading(false);
}

const UIChainSignature = async () => {
setLoading(true);
await chainSignature();
setLoading(false);
};

function removeUrlParams() {
const url = new URL(window.location.href);
url.searchParams.delete("transactionHashes");
window.history.replaceState({}, document.title, url);
}

return (
<>
{/* Form Inputs */}
<div className="row mb-0">
<label className="col-sm-2 col-form-label"></label>
<div className="col-sm-10"></div>
</div>

<div className="input-group input-group-sm my-2 mb-2">
<span className="input-group-text bg-primary text-white" id="chain">
PATH
</span>
<input
type="text"
className="form-control form-control-sm"
value={derivation}
onChange={(e) => setDerivation(e.target.value)}
disabled={loading}
/>
</div>

{/* ADDRESS & BALANCE */}
<div className="card">
<div className="row mb-0">
<label className="col-sm-2 col-form-label text-end">Address:</label>
<div className="col-sm-10 fs-5">
<div className="form-text" id="eth-sender">
{senderLabel}
</div>
</div>
</div>
<div className="row mb-0">
<label className="col-sm-2 col-form-label text-end">Balance:</label>
<div className="col-sm-10 fs-5">
<div className="form-text text-muted ">
{balance ? (
`${balance} ETH`
) : (
<span className="text-warning">Fetching balance...</span>
)}
</div>
</div>
</div>
</div>

<div className="input-group input-group-sm my-2 mb-4">
<span className="input-group-text bg-info text-white" id="chain">
ACTION
</span>
<select
className="form-select"
aria-describedby="chain"
onChange={(e) => setAction(e.target.value)}
disabled={loading}
>
<option value="transfer">Ξ Transfer</option>
<option value="function-call">Ξ Call Counter</option>
</select>
</div>

{action === "transfer" ? (
<TransferForm ref={childRef} props={{ Evm, senderAddress, loading }} />
) : (
<FunctionCallForm
ref={childRef}
props={{ Evm, contractAddress, senderAddress, loading }}
/>
)}

<div className="text-center mt-4 d-flex justify-content-center">
<div className="table-responsive " style={{ maxWidth: "400px" }}>
<table className="table table-hover text-center w-auto">
<caption className="caption-top text-center">
Sepolia Gas Prices
</caption>
<thead>
<tr className="table-light">
<th scope="col">Price</th>
<th scope="col">Unit</th>
</tr>
</thead>
<tbody>
<tr>
<td>{gasPriceInGwei}</td>
<td>GWEI</td>
</tr>
<tr>
<td>{txCost}</td>
<td>ETH</td>
</tr>
</tbody>
</table>
</div>
</div>

{/* Execute Buttons */}
<div className="d-grid gap-2">
{step === "request" && (
<button
className="btn btn-outline-success text-center btn-lg"
onClick={UIChainSignature}
disabled={loading}
>
Request Signature
</button>
)}
{step === "relay" && (
<button
className="btn btn-success text-center"
onClick={relayTransaction}
disabled={loading}
>
Relay Transaction
</button>
)}
</div>
</>
);
}

BaseView.propTypes = {
props: PropTypes.shape({
setStatus: PropTypes.func.isRequired,
MPC_CONTRACT: PropTypes.string.isRequired,
}).isRequired,
};
Loading

0 comments on commit 0022e74

Please sign in to comment.