Skip to content

Commit

Permalink
Merge pull request #29 from PiVortex/add-NFTs-to-seperate-file
Browse files Browse the repository at this point in the history
Restructure and add nft-chain-keys
  • Loading branch information
gagdiez authored Jul 25, 2024
2 parents 5cb169f + 00a723c commit 002ebaa
Show file tree
Hide file tree
Showing 39 changed files with 1,011 additions and 31 deletions.
43 changes: 16 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
# NEAR Multichain Examples w/ Chain Signatures

![alpha badge](https://img.shields.io/badge/status-alpha-red)
[![PRs welcome](https://img.shields.io/badge/PRs-welcome-green)](https://github.com/near-examples/near-multichain/pulls)
[![Chain Signatures Docs](https://img.shields.io/badge/Chain_Signatures_Docs-blue)](https://near-docs.onrender.com/concepts/abstraction/chain-signatures)
[![Dev Support](https://img.shields.io/badge/DEV_SUPPORT-red)](https://t.me/neardev)

An example on signing and executing transactions across multiple blockchain protocols from one NEAR account

> Currently only available on **testnet**, and should be used to control only `testnet` accounts on other chains
---

## Requirements

- `npm` or `yarn`
- NEAR `testnet` account using [MyNEARWallet](https://mynearwallet.com/)

## Installation

```bash
npm install # or yarn
npm run dev # or yarn dev
```

> [!CAUTION]
> Chain Signatures are currently in `alpha` and should only be used in a `testnet` environment.
# NEAR Multichain Examples w/ Chain Signatures

![alpha badge](https://img.shields.io/badge/status-alpha-red)
[![PRs welcome](https://img.shields.io/badge/PRs-welcome-green)](https://github.com/near-examples/near-multichain/pulls)
[![Chain Signatures Docs](https://img.shields.io/badge/Chain_Signatures_Docs-blue)](https://docs.near.org/concepts/abstraction/chain-signatures)
[![Dev Support](https://img.shields.io/badge/DEV_SUPPORT-red)](https://t.me/neardev)

This repo contains multiple examples of signing and executing transactions across multiple blockchain protocols from a NEAR account

- [standard-multichain](./standard-multichain/): send transactions to foreign chains from a NEAR account.
- [nft-chain-keys](./nft-chain-keys/): send transactions to Ethereum through transferable NFT Chain Keys.

> Currently only available on **testnet**, and should be used to control only `testnet` accounts on other chains
> [!CAUTION]
> Chain Signatures are currently in `alpha` and should only be used in a `testnet` environment.
File renamed without changes.
26 changes: 26 additions & 0 deletions nft-chain-keys/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# nft-chain-keys

An example of signing and executing transactions for Ethereum from a NEAR account through NFT Chain Keys. This enables Ethereum accounts to be swapped via NFT standards on NEAR.

> Currently only available on **testnet**, and should be used to control only `testnet` accounts on other chains
---

## Requirements

- `npm` or `yarn`
- NEAR `testnet` account using [MyNEARWallet](https://testnet.mynearwallet.com/) or [Meteor Wallet](https://wallet.meteorwallet.app/wallet)

## Installation

```bash
npm install # or yarn
npm run dev # or yarn dev
```

---

For a detailed explanation of how this example works, please refer to the [documentation](https://docs.near.org/build/chain-abstraction/nft-chain-keys).

> [!CAUTION]
> Chain Signatures are currently in `alpha` and should only be used in a `testnet` environment.
File renamed without changes.
49 changes: 49 additions & 0 deletions nft-chain-keys/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "new",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@ethereumjs/common": "^4.3.0",
"@ethereumjs/tx": "^5.3.0",
"@ethereumjs/util": "^9.0.3",
"@near-wallet-selector/core": "^8.9.5",
"@near-wallet-selector/meteor-wallet": "^8.9.5",
"@near-wallet-selector/modal-ui": "^8.9.5",
"@near-wallet-selector/my-near-wallet": "^8.9.5",
"axios": "^1.6.8",
"bitcoinjs-lib": "^6.1.5",
"bn.js": "^5.2.1",
"bs58check": "^3.0.1",
"elliptic": "^6.5.5",
"ethers": "^6.11.1",
"hash.js": "^1.1.7",
"keccak": "^3.0.4",
"near-api-js": "^3.0.4",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rxjs": "^7.8.1",
"vite-plugin-node-polyfills": "^0.21.0",
"web3": "^4.6.0"
},
"overrides": {
"near-api-js": "^3.0.4"
},
"devDependencies": {
"@types/react": "^18.2.64",
"@types/react-dom": "^18.2.21",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"vite": "^5.1.6"
}
}
File renamed without changes
75 changes: 75 additions & 0 deletions nft-chain-keys/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { NearContext } from './context';

import { useEffect, useState } from "react";
import Navbar from "./components/Navbar"
import { Wallet } from "./services/near-wallet";
import { NFTView } from "./components/NFT";
import { EthereumView } from "./components/Ethereum";

// CONSTANTS
const NFT_CONTRACT = 'v2.nft.kagi.testnet';

// NEAR WALLET
const wallet = new Wallet({ network: 'testnet', createAccessKeyFor: NFT_CONTRACT });

function App() {
const [signedAccountId, setSignedAccountId] = useState('');
const [status, setStatus] = useState("Please login to request a signature");
const [tokenId, setTokenId] = useState('');
const [transactionHash, setTransactionHash] = useState('');

useEffect(() => {
wallet.startUp(setSignedAccountId)
}, []);

useEffect(() => {
if (signedAccountId && tokenId == '') {
setStatus("Please select a NFT");
} else if (! signedAccountId) {
setStatus("Please login to request a signature");
}
}, [signedAccountId]);


useEffect(() => {
// Get transaction hash from URL when using web wallet
const getTransactionHashFromUrl = () => {
const urlParams = new URLSearchParams(window.location.search);
const hash = urlParams.get('transactionHashes');
setTransactionHash(hash);
};

getTransactionHashFromUrl();
}, []);

return (
<NearContext.Provider value={{ wallet, signedAccountId, tokenId, setTokenId }}>
<Navbar />
<div className="container">
<h4> 🔗 Ethereum NFT accounts </h4>
<p className="small">
Send Ethereum transactions through NFT Chain Keys. Learn more in the <a href="https://docs.near.org/build/chain-abstraction/nft-chain-keys"> <b>documentation</b></a>.
</p>

{signedAccountId &&
<div style={{ width: '50%', minWidth: '400px' }}>

<div className="input-group input-group-sm mt-3 mb-4">
<input className="form-control text-center" type="text" value={`NFT Chain Keys Contract: ${NFT_CONTRACT}`} disabled />
</div>

<NFTView props={{ NFT_CONTRACT }} />

<EthereumView props={{ setStatus, NFT_CONTRACT, transactionHash }} />
</div>
}

<div className="mt-3 small text-center">
{status}
</div>
</div>
</NearContext.Provider>
)
}

export default App
160 changes: 160 additions & 0 deletions nft-chain-keys/src/components/Ethereum.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { useState, useEffect, useContext } from "react";
import { NearContext } from "../context";

import { Ethereum } from "../services/ethereum";
import { useDebounce } from "../hooks/debounce";
import PropTypes from 'prop-types';

const Sepolia = 11155111;
const Eth = new Ethereum('https://rpc2.sepolia.org', Sepolia);

export function EthereumView({ props: { setStatus, NFT_CONTRACT, transactionHash } }) {
const { wallet, signedAccountId, tokenId, setTokenId } = useContext(NearContext);

const [receiver, setReceiver] = useState("0xe0f3B7e68151E9306727104973752A415c2bcbEb");
const [amount, setAmount] = useState(0.01);
const [loading, setLoading] = useState(false);
const [step, setStep] = useState("request");
const [signedTransaction, setSignedTransaction] = useState(null);
const [senderAddress, setSenderAddress] = useState('');

const [derivation, setDerivation] = useState("ethereum-1");
const derivation_path = useDebounce(derivation, 1000);

useEffect(() => {
// Only called if using web wallet
if (transactionHash != null && sessionStorage.getItem('chain') == "ETH") {
resetParams();
handleCallback();
}
}, [transactionHash]);

// Reset params to before asking for signature if using web wallet
async function resetParams() {
try {
const args = await wallet.getTransactionArgs(transactionHash);
setTokenId(args.token_id);
setDerivation(args.path);
setAmount(sessionStorage.getItem('amount'));
} catch (e) {
setStatus(`❌ Error: ${e.message}`);
}
}

// Handles the rest of the signature method if using web wallet
async function handleCallback() {
try {
setLoading(true);
const signedTransaction = await Eth.requestSignatureFromNFTCallback(wallet, transactionHash);
setSignedTransaction(signedTransaction);
setStatus(`✅ Signed payload ready to be relayed to the Ethereum network`);
setStep('relay');
setLoading(false);
} catch (e) {
setStatus(`❌ Error: ${e.message}`);
setLoading(false);
}
}

useEffect(() => {
setSenderAddress('Waiting for you to stop typing...')
}, [derivation]);

useEffect(() => {
if (tokenId == '') {
setSenderAddress('Select NFT');
} else {
setEthAddress();
}

async function setEthAddress() {
setStatus('Querying your address and balance');
setSenderAddress(`Deriving address from path ${derivation_path}...`);

const { address } = await Eth.deriveAddress(NFT_CONTRACT, derivation_path, tokenId);
setSenderAddress(address);

const balance = await Eth.getBalance(address);
setStatus(`Your Ethereum address is: ${address}, balance: ${balance} ETH`);
}
}, [signedAccountId, derivation_path, tokenId]);

async function chainSignature() {
setStatus('🏗️ Creating transaction');
const { transaction, payload } = await Eth.createPayload(senderAddress, receiver, amount);

setStatus(`🕒 Asking ${NFT_CONTRACT} to sign the transaction, this might take a while`);
try {
const signedTransaction = await Eth.requestSignatureFromNFT(wallet, tokenId, NFT_CONTRACT, derivation_path, payload, transaction, senderAddress);
setSignedTransaction(signedTransaction);
setStatus(`✅ Signed payload ready to be relayed to the Ethereum network`);
setStep('relay');
} catch (e) {
setStatus(`❌ Error: ${e.message}`);
setLoading(false);
}
}

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

try {
const txHash = await Eth.relayTransaction(signedTransaction);
setStatus(<>
<a href={`https://sepolia.etherscan.io/tx/${txHash}`} target="_blank"> ✅ Successful </a>
</>
);
} catch (e) {
setStatus(`❌ Error: ${e.message}`);
}

setStep('request');
setLoading(false);
window.history.pushState({}, '', window.location.origin);
}

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

return (
<>
<div className="row mb-3">
<label className="col-sm-2 col-form-label col-form-label-sm">Path:</label>
<div className="col-sm-10">
<input type="text" className="form-control form-control-sm" value={derivation} onChange={(e) => setDerivation(e.target.value)} disabled={loading} />
<div className="form-text" id="eth-sender"> {senderAddress} </div>
</div>
</div>
<div className="row mb-3">
<label className="col-sm-2 col-form-label col-form-label-sm">To:</label>
<div className="col-sm-10">
<input type="text" className="form-control form-control-sm" value={receiver} onChange={(e) => setReceiver(e.target.value)} disabled={loading} />
</div>
</div>
<div className="row mb-3">
<label className="col-sm-2 col-form-label col-form-label-sm">Amount:</label>
<div className="col-sm-10">
<input type="number" className="form-control form-control-sm" value={amount} onChange={(e) => setAmount(e.target.value)} step="0.01" disabled={loading} />
<div className="form-text"> Ethereum units </div>
</div>
</div>

<div className="text-center">
{step === 'request' && <button className="btn btn-primary text-center" onClick={UIChainSignature} disabled={loading}> Request Signature </button>}
{step === 'relay' && <button className="btn btn-success text-center" onClick={relayTransaction} disabled={loading}> Relay Transaction </button>}
</div>

</>
)
}

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

0 comments on commit 002ebaa

Please sign in to comment.