1 import { useEffect } from 'react';
3 import { useGetAddresses } from '@proton/account/addresses/hooks';
4 import { useGetOrganization } from '@proton/account/organization/hooks';
5 import { useUser } from '@proton/account/user/hooks';
6 import { useGetUserKeys } from '@proton/account/userKeys/hooks';
7 import type andromedaModule from '@proton/andromeda';
11 WasmFiatCurrencySymbol,
13 WasmProtonWalletApiClient,
16 } from '@proton/andromeda';
17 import useAuthentication from '@proton/components/hooks/useAuthentication';
18 import useConfig from '@proton/components/hooks/useConfig';
19 import { getClientID } from '@proton/shared/lib/apps/helper';
20 import { getAppVersionStr } from '@proton/shared/lib/fetch/headers';
21 import { getDecryptedAddressKeysHelper } from '@proton/shared/lib/keys';
23 import { WalletType } from '../types/api';
24 import { encryptWalletData } from '../utils/crypto';
25 import { getDefaultWalletName } from '../utils/wallet';
26 import { isWasmSupported, loadWasmModule } from '../utils/wasm';
28 const DEFAULT_ACCOUNT_LABEL = 'Primary Account';
30 const FIRST_INDEX = 0;
32 // Flag to tell the API the wallet was autocreated
33 const WALLET_AUTOCREATE_FLAG = true;
35 let userWalletSettings: WasmUserSettings;
36 let wasm: typeof andromedaModule;
39 * Utility hook creating a wallet if user don't have any
42 * - this hook needs to be called inside ExtendedApiContext (see @proton/wallet/contexts/ExtendedApiContext)
43 * - this hook need to be called inside a Redux context which walletReducers (see @proton/wallet/store/slices/index.ts)
46 * - For now we create a new wallet for any user without one. Later a field will be introduced by the API so that we can filter only user that never had a wallet
48 export const useWalletAutoCreate = ({ higherLevelPilot = true }: { higherLevelPilot?: boolean }) => {
49 const getUserKeys = useGetUserKeys();
50 const getOrganization = useGetOrganization();
51 const getAddresses = useGetAddresses();
52 const config = useConfig();
54 const [user] = useUser();
55 const authentication = useAuthentication();
57 const getWalletApi = async () => {
58 const appVersion = getAppVersionStr(getClientID(config.APP_NAME), config.APP_VERSION);
59 return new wasm.WasmProtonWalletApiClient(
63 window.location.origin,
68 const defaultScriptType = () => wasm.WasmScriptType.NativeSegwit;
70 const enableBitcoinViaEmail = async ({
77 wallet: WasmApiWallet;
78 walletAccount: WasmApiWalletAccount;
79 wasmWallet: WasmWallet;
80 walletApi: ReturnType<WasmProtonWalletApiClient['clients']>;
81 derivationPathParts: readonly [number, WasmNetwork, 0];
83 const addresses = await getAddresses();
84 const userKeys = await getUserKeys();
86 const wasmAccount = new wasm.WasmAccount(
89 wasm.WasmDerivationPath.fromParts(...derivationPathParts)
92 const [primaryAddress] = addresses;
93 const [primaryAddressKey] = await getDecryptedAddressKeysHelper(
97 authentication.getPassword()
100 await walletApi.wallet.addEmailAddress(wallet.ID, walletAccount.ID, primaryAddress.ID);
102 const account = await import('../utils/account');
104 // Fill bitcoin address pool
105 const addressesPoolPayload = await account.generateBitcoinAddressesPayloadToFillPool({
106 addressesToCreate: walletAccount.PoolSize,
108 walletAccountAddressKey: primaryAddressKey,
111 if (addressesPoolPayload?.[0]?.length) {
112 await walletApi.bitcoin_address.addBitcoinAddresses(wallet.ID, walletAccount.ID, addressesPoolPayload);
116 const setupWalletAccount = async ({
123 wallet: WasmApiWallet;
125 derivationPathParts: readonly [number, WasmNetwork, 0];
126 walletApi: ReturnType<WasmProtonWalletApiClient['clients']>;
127 fiatCurrency: WasmFiatCurrencySymbol;
129 const account = await walletApi.wallet.createWalletAccount(
131 wasm.WasmDerivationPath.fromParts(...derivationPathParts),
136 await walletApi.wallet.updateWalletAccountFiatCurrency(wallet.ID, account.Data.ID, fiatCurrency);
141 const autoCreateWallet = async () => {
143 const walletApi = await getWalletApi();
145 const userKeys = await getUserKeys();
146 const network = await walletApi.network.getNetwork();
148 const [primaryUserKey] = userKeys;
150 const mnemonic = new wasm.WasmMnemonic(wasm.WasmWordCount.Words12).asString();
151 const hasPassphrase = false;
153 const compelledWalletName = getDefaultWalletName(false, []);
155 // Encrypt wallet data
157 [encryptedName, encryptedMnemonic, encryptedFirstAccountLabel],
158 [walletKey, walletKeySignature /** decryptedWalletKey */, , userKeyId],
159 ] = await encryptWalletData([compelledWalletName, mnemonic, DEFAULT_ACCOUNT_LABEL], primaryUserKey);
161 const wasmWallet = new wasm.WasmWallet(network, mnemonic, '');
162 const fingerprint = wasmWallet.getFingerprint();
164 const derivationPathParts = [defaultScriptType(), network, FIRST_INDEX] as const;
166 const { Wallet } = await walletApi.wallet.createWallet(
177 WALLET_AUTOCREATE_FLAG
180 const account = await setupWalletAccount({
182 label: encryptedFirstAccountLabel,
185 fiatCurrency: userWalletSettings.FiatCurrency,
188 await enableBitcoinViaEmail({
190 walletAccount: account.Data,
196 console.error('Could not autocreate wallet from Mail', e);
200 const shouldCreateWallet = async () => {
202 if (!higherLevelPilot || !isWasmSupported()) {
206 let isUserCompatible = user.isFree;
208 if (!isUserCompatible) {
209 const organization = await getOrganization();
210 isUserCompatible = organization?.MaxMembers === 1;
213 if (!isUserCompatible) {
217 // lazy load wasm here
218 wasm = await loadWasmModule();
220 const walletApi = await getWalletApi();
221 userWalletSettings = (await walletApi.settings.getUserSettings())[0];
223 return !userWalletSettings.WalletCreated;
225 console.error('Could not check whether or not wallet autocreation is needed', e);
231 const run = async () => {
232 if (await shouldCreateWallet()) {
233 await autoCreateWallet();