diff --git a/package.json b/package.json index 96d5eb8..3a54348 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@aptos-labs/ts-sdk": "^2.0.1", "@ethereumjs/common": "^4.3.0", "@ethereumjs/tx": "^5.3.0", "@ethereumjs/util": "^9.0.3", diff --git a/src/App.jsx b/src/App.jsx index 3df17af..48756b5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -6,13 +6,14 @@ import { BitcoinView } from './components/Bitcoin'; import { explorerForChain, MPC_CONTRACT, RPCforChain } from './config'; import { useWalletSelector } from '@near-wallet-selector/react-hook'; import { SolanaView } from './components/Solana'; +import { AptosView } from './components/Aptos'; import { XRPView } from './components/XRP'; function App() { const { signedAccountId } = useWalletSelector(); const [status, setStatus] = useState('Please login to request a signature'); - const [chain, setChain] = useState('eth'); + const [chain, setChain] = useState('eht'); return ( <> @@ -67,6 +68,7 @@ function App() { + @@ -80,10 +82,13 @@ function App() { }} /> )} {chain === 'btc' && ( - + )} {chain === 'sol' && ( - + + )} + {chain === 'apt' && ( + )} {chain === 'xrp' && ( diff --git a/src/components/Aptos.jsx b/src/components/Aptos.jsx new file mode 100644 index 0000000..b4c0cd3 --- /dev/null +++ b/src/components/Aptos.jsx @@ -0,0 +1,245 @@ +import PropTypes from "prop-types"; + +import { useWalletSelector } from "@near-wallet-selector/react-hook"; +import { useEffect, useState } from "react"; +import { useDebounce } from "../hooks/debounce"; +import { SIGNET_CONTRACT } from "../config"; +import { chainAdapters } from "chainsig.js"; +import { bigIntToDecimal } from "../utils/bigIntToDecimal"; +import { decimalToBigInt } from "../utils/decimalToBigInt"; +import { Aptos as AptosClient, AptosConfig, Network } from '@aptos-labs/ts-sdk' + +const aptosClient = new AptosClient( + new AptosConfig({ + network: Network.TESTNET, + }) +) + +const Aptos = new chainAdapters.aptos.Aptos({ + client: aptosClient, + contract: SIGNET_CONTRACT, +}) + +export function AptosView({ props: { setStatus } }) { + const { signedAccountId, signAndSendTransactions } = useWalletSelector(); + + const [receiver, setReceiver] = useState("0x3b0c3efaa16f5c7c53d3ca9c12622c90542ff36485f7f713ba8e76756a3fbbea"); + const [amount, setAmount] = useState(1); + const [loading, setLoading] = useState(false); + const [step, setStep] = useState("request"); + const [signedTransaction, setSignedTransaction] = useState(null); + const [senderAddress, setSenderAddress] = useState(""); + const [senderPK, setSenderPK] = useState(""); + + const [derivation, setDerivation] = useState("aptos-1"); + const derivationPath = useDebounce(derivation, 500); + + useEffect(() => { + setSenderAddress("Waiting for you to stop typing..."); + }, [derivation]); + + useEffect(() => { + setAptosAddress(); + + async function setAptosAddress() { + setStatus("Querying your address and balance"); + setSenderAddress(`Deriving address from path ${derivationPath}...`); + + const { address,publicKey } = await Aptos.deriveAddressAndPublicKey(signedAccountId, derivationPath); + + setSenderAddress(address); + setSenderPK(publicKey); + + const balance = await Aptos.getBalance(address); + + setStatus( + `Your Aptos address is: ${address}, balance: ${bigIntToDecimal(balance.balance, balance.decimals)} APT` + ); + } + + }, [signedAccountId, derivationPath, setStatus]); + + async function chainSignature() { + setStatus("🏗️ Creating transaction"); + + const transactionPayload = { + function: '0x1::aptos_account::transfer', + functionArguments: [ + receiver, + decimalToBigInt(amount, 8), + ], + }; + + const transaction = await aptosClient.transaction.build.simple({ + sender: senderAddress, + data: transactionPayload, + }) + + const { hashesToSign } = await Aptos.prepareTransactionForSigning(transaction) + setStatus( + "🕒 Asking MPC to sign the transaction, this might take a while..." + ); + + try { + const rsvSignatures = await SIGNET_CONTRACT.sign({ + payloads: hashesToSign, + path: derivationPath, + keyType: "Eddsa", + signerAccount: { + accountId: signedAccountId, + signAndSendTransactions + } + }); + + if (!rsvSignatures[0] || !rsvSignatures[0].signature) { + throw new Error("Failed to sign transaction"); + } + + const txSerialized = Aptos.finalizeTransactionSigning({ + transaction, + rsvSignatures: rsvSignatures[0], + publicKey: senderPK + }); + + setSignedTransaction(txSerialized); + setStatus("✅ Signed payload ready to be relayed to the Aptos network"); + setStep("relay"); + } catch (e) { + console.log(e); + setStatus(`❌ Error: ${e.message}`); + setLoading(false); + } + } + + async function relayTransaction() { + setLoading(true); + setStatus( + "🔗 Relaying transaction to the Aptos network..." + ); + + try { + const txHash = await Aptos.broadcastTx(signedTransaction); + + setStatus( + <> + + {" "} + ✅ Successfully Broadcasted{" "} + + + ); + } catch (e) { + if (e.message.includes("TRANSACTION_EXPIRED")) { + setStatus("⏰ Transaction expired, creating a new one..."); + setStep("request"); + setLoading(false); + return; + } + setStatus(`❌ Error: ${e.message}`); + } + + setStep("request"); + setLoading(false); + } + + const UIChainSignature = async () => { + setLoading(true); + await chainSignature(); + setLoading(false); + }; + + return (<> +
+ You are working with Aptos Testnet. +
+ You can get funds from the faucet: + + aptos.dev/network/faucet + +
+
+ +
+ setDerivation(e.target.value)} + disabled={loading} + /> +
+ {" "} + {senderAddress}{" "} +
+
+
+
+ +
+ setReceiver(e.target.value)} + disabled={loading} + /> +
+
+
+ +
+ setAmount(e.target.value)} + step="0.1" + min="0" + disabled={loading} + /> +
APT units
+
+
+ +
+ {step === "request" && ( + + )} + {step === "relay" && ( + + )} +
+ ) +} + +AptosView.propTypes = { + props: PropTypes.shape({ + setStatus: PropTypes.func.isRequired, + }).isRequired, +};