The kinds of operations we can perform with a Wallet
instance depend on
whether or not we have access to the wallet's private key.
In order to differentiate between Wallet
instances that know their private key
and those that do not, we use the WalletUnlocked
and WalletLocked
types
respectively.
The WalletUnlocked
type represents a wallet whose private key is known and
stored internally in memory. A wallet must be of type WalletUnlocked
in order
to perform operations that involve signing messages or transactions .
The WalletLocked
type represents a wallet whose private key is not known or stored
in memory. Instead, WalletLocked
only knows its public address. A WalletLocked
cannot be
used to sign transactions, however it may still perform a whole suite of useful
operations including listing transactions, assets, querying balances, and so on.
Note that the WalletUnlocked
type implements most methods available on the WalletLocked
type. In other words, WalletUnlocked
can be thought of as a thin wrapper around WalletLocked
that
provides greater access via its private key.
import type { WalletLocked, WalletUnlocked } from 'fuels';
import { Provider, Wallet } from 'fuels';
import { LOCAL_NETWORK_URL } from '../env';
// We can use the `generate` to create a new unlocked wallet.
const provider = await Provider.create(LOCAL_NETWORK_URL);
const myWallet: WalletUnlocked = Wallet.generate({ provider });
// or use an Address to create a wallet
const someWallet: WalletLocked = Wallet.fromAddress(myWallet.address, provider);
You can choose not to pass through a provider argument on Wallet
construction:
import type { WalletUnlocked } from 'fuels';
import { Wallet } from 'fuels';
// You can create a wallet, without a provider
let unlockedWalletWithoutProvider: WalletUnlocked = Wallet.generate();
unlockedWalletWithoutProvider = Wallet.fromPrivateKey(
unlockedWalletWithoutProvider.privateKey
);
// All non-provider dependent methods are available
unlockedWalletWithoutProvider.lock();
// All provider dependent methods will throw
try {
await unlockedWalletWithoutProvider.getCoins();
} catch (e) {
console.log('error', e);
}
A WalletLocked
instance can be unlocked by providing the private key:
import type { WalletLocked, WalletUnlocked } from 'fuels';
import { Provider, Wallet } from 'fuels';
import { LOCAL_NETWORK_URL } from '../env';
const provider = await Provider.create(LOCAL_NETWORK_URL);
const wallet: WalletUnlocked = Wallet.generate({ provider });
const PRIVATE_KEY = wallet.privateKey;
// Lock an existing wallet
const lockedWallet: WalletLocked = Wallet.fromAddress(wallet.address, provider);
// Unlock an existing wallet
const someUnlockedWallet: WalletUnlocked = lockedWallet.unlock(PRIVATE_KEY);
A WalletUnlocked
instance can be locked using the lock
method:
import type { WalletUnlocked } from 'fuels';
import { Provider, Wallet } from 'fuels';
import { LOCAL_NETWORK_URL } from '../env';
const provider = await Provider.create(LOCAL_NETWORK_URL);
const unlockedWallet: WalletUnlocked = Wallet.generate({ provider });
const newlyLockedWallet = unlockedWallet.lock();
Most wallet constructors that create or generate a new wallet are provided on
the WalletUnlocked
type. Consider locking the wallet with the lock
method after the new private
key has been handled in order to reduce the scope in which the wallet's private
key is stored in memory.
When designing APIs that accept a wallet as an input, we should think carefully
about the kind of access that we require. API developers should aim to minimise
their usage of WalletUnlocked
in order to ensure private keys are stored in
memory no longer than necessary to reduce the surface area for attacks and
vulnerabilities in downstream libraries and applications.
For a full example of how to lock and unlock a wallet, see the snippet below:
import type { WalletLocked, WalletUnlocked } from 'fuels';
import { Provider, Wallet } from 'fuels';
import { LOCAL_NETWORK_URL } from '../env';
// We can use the `generate` to create a new unlocked wallet.
const provider = await Provider.create(LOCAL_NETWORK_URL);
const myWallet: WalletUnlocked = Wallet.generate({ provider });
// or use an Address to create a wallet
const someWallet: WalletLocked = Wallet.fromAddress(myWallet.address, provider);
const wallet: WalletUnlocked = Wallet.generate({ provider });
const PRIVATE_KEY = wallet.privateKey;
// Lock an existing wallet
const lockedWallet: WalletLocked = Wallet.fromAddress(
myWallet.address,
provider
);
// Unlock an existing wallet
const someUnlockedWallet: WalletUnlocked = lockedWallet.unlock(PRIVATE_KEY);
const unlockedWallet: WalletUnlocked = Wallet.generate({ provider });
const newlyLockedWallet = unlockedWallet.lock();
// You can create a wallet, without a provider
let unlockedWalletWithoutProvider: WalletUnlocked = Wallet.generate();
unlockedWalletWithoutProvider = Wallet.fromPrivateKey(
unlockedWalletWithoutProvider.privateKey
);
// All non-provider dependent methods are available
unlockedWalletWithoutProvider.lock();
// All provider dependent methods will throw
await expect(() => unlockedWalletWithoutProvider.getCoins()).rejects.toThrow(
/Provider not set/
);