import React, {
createContext,
forwardRef,
useCallback,
useContext,
useState,
} from 'react';
import { Just, Nothing } from 'folktale/maybe';
import { includes } from 'lodash';
import {
WALLET_TYPES,
DEFAULT_HD_PATH,
addressFromSecp256k1Public,
walletFromMnemonic,
} from 'lib/wallet';
import { BRIDGE_ERROR } from 'lib/error';
export const WalletContext = createContext(null);
const DEFAULT_WALLET_TYPE = WALLET_TYPES.TICKET;
// NB (jtobin):
//
// Note that the 'wallet' prop has type depending on the 'walletType' prop.
// These could be bound together in a single structure (so that
// 'walletType' acted more explicitly as a data constructor of sorts) but
// it doesn't necessarily help us much, since we branch on 'walletType'
// before setting 'wallet'.
//
// Wallets are always wrapped in For most authentication
// mechanisms, Maybe wraps a BIP32 HD wallet; for Ethereum private keys,
// JSON keystore files, and Metamask authentication, it wraps an
// 'EthereumWallet'.
function _useWallet(initialWallet = Nothing(), initialMnemonic = Nothing()) {
const [walletType, _setWalletType] = useState(() =>
initialMnemonic.matchWith({
Nothing: () => DEFAULT_WALLET_TYPE,
Just: () => WALLET_TYPES.MNEMONIC,
})
);
const [walletHdPath, setWalletHdPath] = useState(DEFAULT_HD_PATH);
const [wallet, _setWallet] = useState(initialWallet);
const [urbitWallet, _setUrbitWallet] = useState(Nothing());
const [authMnemonic, setAuthMnemonic] = useState(initialMnemonic);
const [networkSeed, setNetworkSeed] = useState(Nothing());
const [networkRevision, setNetworkRevision] = useState(Nothing());
const setWalletType = useCallback(
walletType => {
if (!includes(WALLET_TYPES, walletType)) {
throw new Error(BRIDGE_ERROR.INVALID_WALLET_TYPE);
}
_setWalletType(walletType);
},
[_setWalletType]
);
const setWallet = useCallback(
wallet => {
// force that public addresses are derived for each wallet
// NOTE wallet is Maybe<> and .map is Maybe#map
wallet.map(wal => {
wal.address = wal.address || addressFromSecp256k1Public(wal.publicKey);
return wal;
});
_setWallet(wallet);
},
[_setWallet]
);
const setUrbitWallet = useCallback(
urbitWallet => {
if (Just.hasInstance(urbitWallet)) {
// when an urbit wallet is set, also derive
// a normal bip32 wallet using the ownership address
const wallet = walletFromMnemonic(
urbitWallet.value.ownership.seed,
DEFAULT_HD_PATH,
urbitWallet.value.meta.passphrase
);
setWallet(wallet);
} else {
setWallet(Nothing());
}
_setUrbitWallet(urbitWallet);
},
[setWallet]
);
const resetWallet = useCallback(() => {
_setWalletType(DEFAULT_WALLET_TYPE);
setWalletHdPath(DEFAULT_HD_PATH);
_setWallet(Nothing());
_setUrbitWallet(Nothing());
setAuthMnemonic(Nothing());
setNetworkSeed(Nothing());
setNetworkRevision(Nothing());
}, [
_setWalletType,
setWalletHdPath,
_setWallet,
_setUrbitWallet,
setAuthMnemonic,
setNetworkSeed,
setNetworkRevision,
]);
return {
//
walletType,
setWalletType,
//
walletHdPath,
setWalletHdPath,
//
wallet,
setWallet,
//
urbitWallet,
setUrbitWallet,
authMnemonic,
setAuthMnemonic,
//
networkSeed,
setNetworkSeed,
networkRevision,
setNetworkRevision,
//
resetWallet,
};
}
export function WalletProvider({ initialWallet, initialMnemonic, children }) {
const wallet = _useWallet(initialWallet, initialMnemonic);
return (
{children}
);
}
// Hook version
export function useWallet() {
return useContext(WalletContext);
}
// HOC version
export const withWallet = Component =>
forwardRef((props, ref) => (
{wallet => }
));