From c4df189f376460f0acbc47e0e1ca02f3b21f79cc Mon Sep 17 00:00:00 2001 From: Angel Castillo Date: Wed, 5 Jun 2024 16:37:31 +0800 Subject: [PATCH] feat(web-extension): added support to coin purpose in accounts --- .../SigningCoordinator/SigningCoordinator.ts | 2 +- .../WalletRepository/WalletRepository.ts | 58 ++++++++++++++----- .../walletManager/WalletRepository/types.ts | 4 ++ .../web-extension/src/walletManager/types.ts | 4 +- .../walletManager/WalletRepository.test.ts | 5 +- 5 files changed, 56 insertions(+), 17 deletions(-) diff --git a/packages/web-extension/src/walletManager/SigningCoordinator/SigningCoordinator.ts b/packages/web-extension/src/walletManager/SigningCoordinator/SigningCoordinator.ts index afc4992c072..d8ab92d0709 100644 --- a/packages/web-extension/src/walletManager/SigningCoordinator/SigningCoordinator.ts +++ b/packages/web-extension/src/walletManager/SigningCoordinator/SigningCoordinator.ts @@ -165,7 +165,7 @@ export class SigningCoordinator passphrase, - purpose: KeyPurpose.STANDARD + purpose: account.purpose || KeyPurpose.STANDARD }) ); clearPassphrase(passphrase); diff --git a/packages/web-extension/src/walletManager/WalletRepository/WalletRepository.ts b/packages/web-extension/src/walletManager/WalletRepository/WalletRepository.ts index 527e635a547..ee757a44b73 100644 --- a/packages/web-extension/src/walletManager/WalletRepository/WalletRepository.ts +++ b/packages/web-extension/src/walletManager/WalletRepository/WalletRepository.ts @@ -7,6 +7,7 @@ import { WalletRepositoryApi } from './types'; import { AnyWallet, ScriptWallet, WalletId, WalletType } from '../types'; +import { KeyPurpose } from '@cardano-sdk/key-management'; import { Logger } from 'ts-log'; import { Observable, defer, firstValueFrom, map, shareReplay, switchMap, take } from 'rxjs'; import { WalletConflictError } from '../errors'; @@ -28,16 +29,24 @@ const cloneSplice = (array: T[], start: number, deleteCount: number, ...items const findAccount = ( wallets: AnyWallet[], walletId: WalletId, - accountIndex: number + accountIndex: number, + purpose: KeyPurpose ) => { const walletIdx = wallets.findIndex((w) => w.walletId === walletId); const wallet = wallets[walletIdx]; + if (!wallet || wallet.type === WalletType.Script) return; - const accountIdx = wallet.accounts.findIndex((acc) => acc.accountIndex === accountIndex); + + const accountIdx = wallet.accounts.findIndex((acc) => { + const accountPurpose = acc.purpose || KeyPurpose.STANDARD; + return acc.accountIndex === accountIndex && accountPurpose === purpose; + }); + if (accountIdx < 0) return; return { account: wallet.accounts[accountIdx], accountIdx, + purpose, wallet, walletIdx }; @@ -96,6 +105,8 @@ export class WalletRepository): Promise> { const { walletId, accountIndex, metadata, extendedAccountPublicKey } = props; this.#logger.debug('addAccount', walletId, accountIndex, metadata); + const purpose = props.purpose || KeyPurpose.STANDARD; + return firstValueFrom( this.#getWallets().pipe( switchMap((wallets) => { @@ -107,8 +118,16 @@ export class WalletRepository acc.accountIndex === accountIndex)) { - throw new WalletConflictError(`Account #${accountIndex} for wallet '${walletId}' already exists`); + + if ( + wallet.accounts.some((acc) => { + const accountPurpose = acc.purpose || KeyPurpose.STANDARD; + return acc.accountIndex === accountIndex && accountPurpose === purpose; + }) + ) { + throw new WalletConflictError( + `Account #${accountIndex} with purpose ${purpose} for wallet '${walletId}' already exists` + ); } return this.#store @@ -120,7 +139,8 @@ export class WalletRepository> { const { walletId, metadata } = props; this.#logger.debug('updateWalletMetadata', walletId, metadata); + return firstValueFrom( this.#getWallets().pipe( switchMap((wallets) => { @@ -160,14 +181,17 @@ export class WalletRepository ): Promise> { const { walletId, accountIndex, metadata } = props; - this.#logger.debug('updateAccountMetadata', walletId, accountIndex, metadata); + const purpose = props.purpose || KeyPurpose.STANDARD; + + this.#logger.debug('updateAccountMetadata', walletId, accountIndex, metadata, purpose); + return firstValueFrom( this.#getWallets().pipe( switchMap((wallets) => { // update account - const bip32Account = findAccount(wallets, walletId, accountIndex); + const bip32Account = findAccount(wallets, walletId, accountIndex, purpose); if (!bip32Account) { - throw new WalletConflictError(`Account not found: ${walletId}/${accountIndex}`); + throw new WalletConflictError(`Account not found: ${walletId}/${purpose}/${accountIndex}`); } return this.#store.setAll( cloneSplice(wallets, bip32Account.walletIdx, 1, { @@ -185,23 +209,29 @@ export class WalletRepository { - const { walletId, accountIndex } = props; - this.#logger.debug('removeAccount', walletId, accountIndex); + const { walletId, accountIndex, purpose: maybePurpose } = props; + + const purpose = maybePurpose || KeyPurpose.STANDARD; + + this.#logger.debug('removeAccount', walletId, accountIndex, purpose); return firstValueFrom( this.#getWallets().pipe( switchMap((wallets) => { - const bip32Account = findAccount(wallets, walletId, accountIndex); + const bip32Account = findAccount(wallets, walletId, accountIndex, purpose); if (!bip32Account) { - throw new WalletConflictError(`Account '${walletId}/${accountIndex}' does not exist`); + throw new WalletConflictError(`Account '${walletId}/${purpose}/${accountIndex}' does not exist`); } const dependentWallet = wallets.find( (wallet) => wallet.type === WalletType.Script && - wallet.ownSigners.some((signer) => signer.walletId === walletId && signer.accountIndex === accountIndex) + wallet.ownSigners.some( + (signer) => + signer.walletId === walletId && signer.accountIndex === accountIndex && signer.purpose === purpose + ) ); if (dependentWallet) { throw new WalletConflictError( - `Wallet '${dependentWallet.walletId}' depends on account '${walletId}/${accountIndex}'` + `Wallet '${dependentWallet.walletId}' depends on account '${walletId}/${purpose}/${accountIndex}'` ); } return this.#store.setAll( diff --git a/packages/web-extension/src/walletManager/WalletRepository/types.ts b/packages/web-extension/src/walletManager/WalletRepository/types.ts index be732e4658e..ea8a2d2b9d6 100644 --- a/packages/web-extension/src/walletManager/WalletRepository/types.ts +++ b/packages/web-extension/src/walletManager/WalletRepository/types.ts @@ -1,17 +1,20 @@ import { AnyWallet, HardwareWallet, InMemoryWallet, ScriptWallet, WalletId } from '../types'; import { Bip32PublicKeyHex } from '@cardano-sdk/crypto'; +import { KeyPurpose } from '@cardano-sdk/key-management'; import { Observable } from 'rxjs'; export type RemoveAccountProps = { walletId: WalletId; /** account' in cip1852 */ accountIndex: number; + purpose?: KeyPurpose; }; export type AddAccountProps = { walletId: WalletId; /** account' in cip1852 */ accountIndex: number; + purpose?: KeyPurpose; metadata: Metadata; extendedAccountPublicKey: Bip32PublicKeyHex; }; @@ -24,6 +27,7 @@ export type UpdateWalletMetadataProps = { export type UpdateAccountMetadataProps = { /** account' in cip1852; must be specified for bip32 wallets */ walletId: WalletId; + purpose?: KeyPurpose; accountIndex: number; metadata: Metadata; }; diff --git a/packages/web-extension/src/walletManager/types.ts b/packages/web-extension/src/walletManager/types.ts index db34f51a486..bef32173c20 100644 --- a/packages/web-extension/src/walletManager/types.ts +++ b/packages/web-extension/src/walletManager/types.ts @@ -1,4 +1,4 @@ -import { AccountKeyDerivationPath } from '@cardano-sdk/key-management'; +import { AccountKeyDerivationPath, KeyPurpose } from '@cardano-sdk/key-management'; import { Bip32PublicKeyHex } from '@cardano-sdk/crypto'; import { Cardano } from '@cardano-sdk/core'; import { HexBlob } from '@cardano-sdk/util'; @@ -15,6 +15,7 @@ export type WalletId = string; export type Bip32WalletAccount = { accountIndex: number; + purpose?: KeyPurpose; /** e.g. account name, picture */ metadata: Metadata; extendedAccountPublicKey: Bip32PublicKeyHex; @@ -55,6 +56,7 @@ export type AnyBip32Wallet { index: 0, role: KeyRole.External }, + purpose: KeyPurpose.STANDARD, stakingScriptKeyPath: { index: 0, role: KeyRole.External @@ -158,6 +160,7 @@ describe('WalletRepository', () => { index: 0, role: KeyRole.External }, + purpose: KeyPurpose.STANDARD, stakingScriptKeyPath: { index: 0, role: KeyRole.External