bridge_ui: more context, don't autoconnect wallets
Change-Id: Ib4048193874f73ec413d4bcc8ac636964767018d
This commit is contained in:
parent
012c30b30b
commit
266daa228e
|
@ -1,33 +1,46 @@
|
|||
import { Typography } from "@material-ui/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button, Tooltip, Typography } from "@material-ui/core";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
|
||||
const EthereumSignerKey = () => {
|
||||
const provider = useEthereumProvider();
|
||||
const [pk, setPk] = useState("");
|
||||
// TODO: should this be moved to the context?
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
provider
|
||||
?.getSigner()
|
||||
.getAddress()
|
||||
.then((pk) => {
|
||||
if (mounted) {
|
||||
setPk(pk);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.error("Failed to get signer address");
|
||||
});
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, [provider]);
|
||||
if (!pk) return null;
|
||||
const { connect, disconnect, signerAddress, providerError } =
|
||||
useEthereumProvider();
|
||||
return (
|
||||
<>
|
||||
{signerAddress ? (
|
||||
<>
|
||||
<Tooltip title={signerAddress}>
|
||||
<Typography>
|
||||
{pk.substring(0, 6)}...{pk.substr(pk.length - 4)}
|
||||
{signerAddress.substring(0, 6)}...
|
||||
{signerAddress.substr(signerAddress.length - 4)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Button
|
||||
color="secondary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={disconnect}
|
||||
style={{ width: "100%", textTransform: "none" }}
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={connect}
|
||||
style={{ width: "100%", textTransform: "none" }}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
)}
|
||||
{providerError ? (
|
||||
<Typography variant="body2" color="error">
|
||||
{providerError}
|
||||
</Typography>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -15,9 +15,10 @@ function KeyAndBalance({
|
|||
tokenAddress?: string;
|
||||
}) {
|
||||
// TODO: more generic way to get balance
|
||||
const provider = useEthereumProvider();
|
||||
const ethBalance = useEthereumBalance(
|
||||
const { provider, signerAddress } = useEthereumProvider();
|
||||
const { uiAmountString: ethBalance } = useEthereumBalance(
|
||||
tokenAddress,
|
||||
signerAddress,
|
||||
provider,
|
||||
chainId === CHAIN_ID_ETH
|
||||
);
|
||||
|
|
|
@ -1,14 +1,40 @@
|
|||
import { Typography } from "@material-ui/core";
|
||||
import { Button, Tooltip, Typography } from "@material-ui/core";
|
||||
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
||||
|
||||
const SolanaWalletKey = () => {
|
||||
const { wallet } = useSolanaWallet();
|
||||
const pk = wallet?.publicKey?.toString();
|
||||
if (!pk) return null;
|
||||
const { connect, disconnect, connected, wallet } = useSolanaWallet();
|
||||
const pk = wallet?.publicKey?.toString() || "";
|
||||
return (
|
||||
<>
|
||||
{connected ? (
|
||||
<>
|
||||
<Tooltip title={pk}>
|
||||
<Typography>
|
||||
{pk.substring(0, 3)}...{pk.substr(pk.length - 3)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Button
|
||||
color="secondary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={disconnect}
|
||||
style={{ width: "100%", textTransform: "none" }}
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={connect}
|
||||
style={{ width: "100%", textTransform: "none" }}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -104,7 +104,14 @@ function Transfer() {
|
|||
const handleAmountChange = useCallback((event) => {
|
||||
setAmount(event.target.value);
|
||||
}, []);
|
||||
const provider = useEthereumProvider();
|
||||
const { provider, signer, signerAddress } = useEthereumProvider();
|
||||
const { decimals: ethDecimals, uiAmountString: ethBalance } =
|
||||
useEthereumBalance(
|
||||
assetAddress,
|
||||
signerAddress,
|
||||
provider,
|
||||
fromChain === CHAIN_ID_ETH
|
||||
);
|
||||
const { wallet } = useSolanaWallet();
|
||||
const solPK = wallet?.publicKey;
|
||||
const {
|
||||
|
@ -125,7 +132,7 @@ function Transfer() {
|
|||
fromChain === CHAIN_ID_ETH &&
|
||||
attestFrom[fromChain] === attestFromEth
|
||||
) {
|
||||
attestFromEth(provider, assetAddress);
|
||||
attestFromEth(provider, signer, assetAddress);
|
||||
}
|
||||
if (
|
||||
fromChain === CHAIN_ID_SOLANA &&
|
||||
|
@ -134,7 +141,7 @@ function Transfer() {
|
|||
attestFromSolana(wallet, solPK?.toString(), assetAddress, solDecimals);
|
||||
}
|
||||
}
|
||||
}, [fromChain, provider, wallet, solPK, assetAddress, solDecimals]);
|
||||
}, [fromChain, provider, signer, wallet, solPK, assetAddress, solDecimals]);
|
||||
// TODO: dynamically get "to" wallet
|
||||
const handleTransferClick = useCallback(() => {
|
||||
// TODO: more generic way of calling these
|
||||
|
@ -145,7 +152,9 @@ function Transfer() {
|
|||
) {
|
||||
transferFromEth(
|
||||
provider,
|
||||
signer,
|
||||
assetAddress,
|
||||
ethDecimals,
|
||||
amount,
|
||||
toChain,
|
||||
solPK?.toBytes()
|
||||
|
@ -162,7 +171,7 @@ function Transfer() {
|
|||
assetAddress,
|
||||
amount,
|
||||
solDecimals,
|
||||
provider,
|
||||
signerAddress,
|
||||
toChain
|
||||
);
|
||||
}
|
||||
|
@ -170,20 +179,18 @@ function Transfer() {
|
|||
}, [
|
||||
fromChain,
|
||||
provider,
|
||||
signer,
|
||||
signerAddress,
|
||||
wallet,
|
||||
solPK,
|
||||
solTokenPK,
|
||||
assetAddress,
|
||||
amount,
|
||||
ethDecimals,
|
||||
solDecimals,
|
||||
toChain,
|
||||
]);
|
||||
// update this as we develop, just setting expectations with the button state
|
||||
const ethBalance = useEthereumBalance(
|
||||
assetAddress,
|
||||
provider,
|
||||
fromChain === CHAIN_ID_ETH
|
||||
);
|
||||
const balance = Number(ethBalance) || solBalance;
|
||||
const isAttestImplemented = !!attestFrom[fromChain];
|
||||
const isTransferImplemented = !!transferFrom[fromChain];
|
||||
|
|
|
@ -1,52 +1,129 @@
|
|||
import detectEthereumProvider from "@metamask/detect-provider";
|
||||
import { ethers } from "ethers";
|
||||
import React, { ReactChildren, useContext, useEffect, useState } from "react";
|
||||
import React, {
|
||||
ReactChildren,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
type Provider = ethers.providers.Web3Provider | undefined;
|
||||
type Signer = ethers.Signer | undefined;
|
||||
type Network = ethers.providers.Network | undefined;
|
||||
|
||||
const EthereumProviderContext = React.createContext<Provider>(undefined);
|
||||
interface IEthereumProviderContext {
|
||||
connect(): void;
|
||||
disconnect(): void;
|
||||
provider: Provider;
|
||||
network: Network;
|
||||
signer: Signer;
|
||||
signerAddress: string | undefined;
|
||||
providerError: string | null;
|
||||
}
|
||||
|
||||
const EthereumProviderContext = React.createContext<IEthereumProviderContext>({
|
||||
connect: () => {},
|
||||
disconnect: () => {},
|
||||
provider: undefined,
|
||||
network: undefined,
|
||||
signer: undefined,
|
||||
signerAddress: undefined,
|
||||
providerError: null,
|
||||
});
|
||||
export const EthereumProviderProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactChildren;
|
||||
}) => {
|
||||
const [providerError, setProviderError] = useState<string | null>(null);
|
||||
const [provider, setProvider] = useState<Provider>(undefined);
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
const [network, setNetwork] = useState<Network>(undefined);
|
||||
const [signer, setSigner] = useState<Signer>(undefined);
|
||||
const [signerAddress, setSignerAddress] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const connect = useCallback(() => {
|
||||
setProviderError(null);
|
||||
detectEthereumProvider()
|
||||
.then((detectedProvider) => {
|
||||
if (detectedProvider) {
|
||||
if (mounted) {
|
||||
const ethersProvider = new ethers.providers.Web3Provider(
|
||||
const provider = new ethers.providers.Web3Provider(
|
||||
// @ts-ignore
|
||||
detectedProvider
|
||||
detectedProvider,
|
||||
"any" //TODO: should we only allow homestead? env perhaps?
|
||||
);
|
||||
ethersProvider
|
||||
provider
|
||||
.send("eth_requestAccounts", [])
|
||||
.then(() => {
|
||||
if (mounted) {
|
||||
setProvider(ethersProvider);
|
||||
}
|
||||
setProviderError(null);
|
||||
setProvider(provider);
|
||||
provider
|
||||
.getNetwork()
|
||||
.then((network) => {
|
||||
setNetwork(network);
|
||||
})
|
||||
.catch(() => {
|
||||
console.error(
|
||||
setProviderError(
|
||||
"An error occurred while getting the network"
|
||||
);
|
||||
});
|
||||
const signer = provider.getSigner();
|
||||
setSigner(signer);
|
||||
signer
|
||||
.getAddress()
|
||||
.then((address) => {
|
||||
setSignerAddress(address);
|
||||
})
|
||||
.catch(() => {
|
||||
setProviderError(
|
||||
"An error occurred while getting the signer address"
|
||||
);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setProviderError(
|
||||
"An error occurred while requesting eth accounts"
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log("Please install MetaMask");
|
||||
setProviderError("Please install MetaMask");
|
||||
}
|
||||
})
|
||||
.catch(() => console.log("Please install MetaMask"));
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
.catch(() => {
|
||||
setProviderError("Please install MetaMask");
|
||||
});
|
||||
}, []);
|
||||
const disconnect = useCallback(() => {
|
||||
setProviderError(null);
|
||||
setProvider(undefined);
|
||||
setNetwork(undefined);
|
||||
setSigner(undefined);
|
||||
setSignerAddress(undefined);
|
||||
}, []);
|
||||
//TODO: useEffect provider.on("network") to refresh on network changes
|
||||
//TODO: detect account change
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
connect,
|
||||
disconnect,
|
||||
provider,
|
||||
network,
|
||||
signer,
|
||||
signerAddress,
|
||||
providerError,
|
||||
}),
|
||||
[
|
||||
connect,
|
||||
disconnect,
|
||||
provider,
|
||||
network,
|
||||
signer,
|
||||
signerAddress,
|
||||
providerError,
|
||||
]
|
||||
);
|
||||
return (
|
||||
<EthereumProviderContext.Provider value={provider}>
|
||||
<EthereumProviderContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</EthereumProviderContext.Provider>
|
||||
);
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
import Wallet from "@project-serum/sol-wallet-adapter";
|
||||
import React, {
|
||||
ReactChildren,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import Wallet from "@project-serum/sol-wallet-adapter";
|
||||
import { SOLANA_HOST } from "../utils/consts";
|
||||
|
||||
interface ISolanaWalletContext {
|
||||
connect(): void;
|
||||
disconnect(): void;
|
||||
connected: boolean;
|
||||
wallet: Wallet | undefined;
|
||||
}
|
||||
|
||||
const getDefaultWallet = () => new Wallet("https://www.sollet.io", SOLANA_HOST);
|
||||
const SolanaWalletContext = React.createContext<ISolanaWalletContext>({
|
||||
connect: () => {},
|
||||
disconnect: () => {},
|
||||
connected: false,
|
||||
wallet: undefined,
|
||||
});
|
||||
|
@ -23,28 +26,27 @@ export const SolanaWalletProvider = ({
|
|||
}: {
|
||||
children: ReactChildren;
|
||||
}) => {
|
||||
const wallet = useMemo(getDefaultWallet, []);
|
||||
const [wallet, setWallet] = useState<Wallet | undefined>(undefined);
|
||||
const [connected, setConnected] = useState(false);
|
||||
useEffect(() => {
|
||||
const connect = useCallback(() => {
|
||||
const wallet = new Wallet("https://www.sollet.io", SOLANA_HOST);
|
||||
setWallet(wallet);
|
||||
wallet.on("connect", () => {
|
||||
setConnected(true);
|
||||
console.log("Connected to wallet " + wallet.publicKey?.toBase58());
|
||||
});
|
||||
wallet.on("disconnect", () => {
|
||||
console.log("disconnected");
|
||||
setConnected(false);
|
||||
console.log("Disconnected from wallet");
|
||||
setWallet(undefined);
|
||||
});
|
||||
wallet.connect();
|
||||
return () => {
|
||||
wallet.disconnect();
|
||||
};
|
||||
}, []);
|
||||
const disconnect = useCallback(() => {
|
||||
wallet?.disconnect();
|
||||
}, [wallet]);
|
||||
console.log(`Connected state: ${connected}`);
|
||||
//TODO: useEffect to refresh on network changes
|
||||
// ensure users of the context refresh on connect state change
|
||||
const contextValue = useMemo(
|
||||
() => ({ connected, wallet }),
|
||||
[wallet, connected]
|
||||
() => ({ connect, disconnect, connected, wallet }),
|
||||
[connect, disconnect, wallet, connected]
|
||||
);
|
||||
return (
|
||||
<SolanaWalletContext.Provider value={contextValue}>
|
||||
|
|
|
@ -3,16 +3,30 @@ import { formatUnits } from "ethers/lib/utils";
|
|||
import { useEffect, useState } from "react";
|
||||
import { TokenImplementation__factory } from "../ethers-contracts";
|
||||
|
||||
// TODO: can this be shared with other balances
|
||||
export interface Balance {
|
||||
decimals: number;
|
||||
uiAmountString: string;
|
||||
}
|
||||
|
||||
function createBalance(decimals: number, uiAmountString: string) {
|
||||
return {
|
||||
decimals,
|
||||
uiAmountString,
|
||||
};
|
||||
}
|
||||
|
||||
function useEthereumBalance(
|
||||
address: string | undefined,
|
||||
ownerAddress: string | undefined,
|
||||
provider: ethers.providers.Web3Provider | undefined,
|
||||
shouldCalculate?: boolean
|
||||
) {
|
||||
//TODO: should this check allowance too or subtract allowance?
|
||||
const [balance, setBalance] = useState<string>("");
|
||||
const [balance, setBalance] = useState<Balance>(createBalance(0, ""));
|
||||
useEffect(() => {
|
||||
if (!address || !provider || !shouldCalculate) {
|
||||
setBalance("");
|
||||
if (!address || !ownerAddress || !provider || !shouldCalculate) {
|
||||
setBalance(createBalance(0, ""));
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
|
@ -20,26 +34,21 @@ function useEthereumBalance(
|
|||
token
|
||||
.decimals()
|
||||
.then((decimals) => {
|
||||
provider
|
||||
?.getSigner()
|
||||
.getAddress()
|
||||
.then((pk) => {
|
||||
token.balanceOf(pk).then((n) => {
|
||||
token.balanceOf(ownerAddress).then((n) => {
|
||||
if (!cancelled) {
|
||||
setBalance(formatUnits(n, decimals));
|
||||
setBalance(createBalance(decimals, formatUnits(n, decimals)));
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) {
|
||||
setBalance("");
|
||||
setBalance(createBalance(0, ""));
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [address, provider, shouldCalculate]);
|
||||
}, [address, ownerAddress, provider, shouldCalculate]);
|
||||
return balance;
|
||||
}
|
||||
|
||||
|
|
|
@ -40,12 +40,17 @@ function useSolanaBalance(
|
|||
setBalance(createBalance(undefined, "", 0, 0, ""));
|
||||
return;
|
||||
}
|
||||
let mint;
|
||||
try {
|
||||
mint = new PublicKey(tokenAddress);
|
||||
} catch (e) {
|
||||
setBalance(createBalance(undefined, "", 0, 0, ""));
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
const connection = new Connection(SOLANA_HOST, "finalized");
|
||||
connection
|
||||
.getParsedTokenAccountsByOwner(ownerAddress, {
|
||||
mint: new PublicKey(tokenAddress),
|
||||
})
|
||||
.getParsedTokenAccountsByOwner(ownerAddress, { mint })
|
||||
.then(({ value }) => {
|
||||
if (!cancelled) {
|
||||
if (value.length) {
|
||||
|
|
|
@ -19,16 +19,14 @@ import {
|
|||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
} from "./consts";
|
||||
|
||||
// TODO: this should probably be extended from the context somehow so that the signatures match
|
||||
// TODO: allow for / handle cancellation?
|
||||
// TODO: overall better input checking and error handling
|
||||
export function attestFromEth(
|
||||
provider: ethers.providers.Web3Provider | undefined,
|
||||
signer: ethers.Signer | undefined,
|
||||
tokenAddress: string
|
||||
) {
|
||||
if (!provider) return;
|
||||
const signer = provider.getSigner();
|
||||
if (!signer) return;
|
||||
if (!provider || !signer) return;
|
||||
//TODO: more catches
|
||||
(async () => {
|
||||
const signerAddress = await signer.getAddress();
|
||||
|
|
|
@ -19,6 +19,7 @@ export async function getAttestedAssetEth(
|
|||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
provider
|
||||
);
|
||||
try {
|
||||
// TODO: address conversion may be more complex than this
|
||||
const originAssetBytes = zeroPad(
|
||||
originChain === CHAIN_ID_SOLANA
|
||||
|
@ -27,6 +28,9 @@ export async function getAttestedAssetEth(
|
|||
32
|
||||
);
|
||||
return await tokenBridge.wrappedAsset(originChain, originAssetBytes);
|
||||
} catch (e) {
|
||||
return ethers.constants.AddressZero;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAttestedAssetSol(
|
||||
|
|
|
@ -25,24 +25,22 @@ import {
|
|||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
} from "./consts";
|
||||
|
||||
// TODO: this should probably be extended from the context somehow so that the signatures match
|
||||
// TODO: allow for / handle cancellation?
|
||||
// TODO: overall better input checking and error handling
|
||||
export function transferFromEth(
|
||||
provider: ethers.providers.Web3Provider | undefined,
|
||||
// TODO: specify signer
|
||||
signer: ethers.Signer | undefined,
|
||||
tokenAddress: string,
|
||||
decimals: number,
|
||||
amount: string,
|
||||
recipientChain: ChainId,
|
||||
recipientAddress: Uint8Array | undefined
|
||||
) {
|
||||
if (!provider || !recipientAddress) return;
|
||||
const signer = provider.getSigner();
|
||||
if (!signer) return;
|
||||
if (!provider || !signer || !recipientAddress) return;
|
||||
//TODO: check if token attestation exists on the target chain
|
||||
//TODO: don't hardcode, fetch decimals / share them with balance, how do we determine recipient chain?
|
||||
//TODO: more catches
|
||||
const amountParsed = parseUnits(amount, 18);
|
||||
const amountParsed = parseUnits(amount, decimals);
|
||||
(async () => {
|
||||
const signerAddress = await signer.getAddress();
|
||||
console.log("Signer:", signerAddress);
|
||||
|
@ -63,7 +61,7 @@ export function transferFromEth(
|
|||
const nonceBuffer = Buffer.alloc(4);
|
||||
nonceBuffer.writeUInt32LE(nonceConst, 0);
|
||||
console.log("Initiating transfer");
|
||||
console.log("Amount:", formatUnits(amountParsed, 18));
|
||||
console.log("Amount:", formatUnits(amountParsed, decimals));
|
||||
console.log("To chain:", recipientChain);
|
||||
console.log("To address:", recipientAddress);
|
||||
console.log("Fees:", fee);
|
||||
|
@ -108,7 +106,7 @@ export function transferFromSolana(
|
|||
mintAddress: string,
|
||||
amount: string,
|
||||
decimals: number,
|
||||
targetProvider: ethers.providers.Web3Provider | undefined,
|
||||
targetAddressStr: string | undefined,
|
||||
targetChain: ChainId
|
||||
) {
|
||||
if (
|
||||
|
@ -116,13 +114,10 @@ export function transferFromSolana(
|
|||
!wallet.publicKey ||
|
||||
!payerAddress ||
|
||||
!fromAddress ||
|
||||
!targetProvider
|
||||
!targetAddressStr
|
||||
)
|
||||
return;
|
||||
const targetSigner = targetProvider.getSigner();
|
||||
if (!targetSigner) return;
|
||||
(async () => {
|
||||
const targetAddressStr = await targetSigner.getAddress();
|
||||
const targetAddress = zeroPad(arrayify(targetAddressStr), 32);
|
||||
const nonceConst = Math.random() * 100000;
|
||||
const nonceBuffer = Buffer.alloc(4);
|
||||
|
|
Loading…
Reference in New Issue