Skip to content

Commit

Permalink
feat(web-extension): added support to coin purpose in accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
AngelCastilloB committed Jun 5, 2024
1 parent 08e85b0 commit c4df189
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export class SigningCoordinator<WalletMetadata extends {}, AccountMetadata exten
],
extendedAccountPublicKey: account.extendedAccountPublicKey,
getPassphrase: async () => passphrase,
purpose: KeyPurpose.STANDARD
purpose: account.purpose || KeyPurpose.STANDARD
})
);
clearPassphrase(passphrase);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -28,16 +29,24 @@ const cloneSplice = <T>(array: T[], start: number, deleteCount: number, ...items
const findAccount = <WalletMetadata extends {}, AccountMetadata extends {}>(
wallets: AnyWallet<WalletMetadata, AccountMetadata>[],
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
};
Expand Down Expand Up @@ -96,6 +105,8 @@ export class WalletRepository<WalletMetadata extends {}, AccountMetadata extends
addAccount(props: AddAccountProps<AccountMetadata>): Promise<AddAccountProps<AccountMetadata>> {
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) => {
Expand All @@ -107,8 +118,16 @@ export class WalletRepository<WalletMetadata extends {}, AccountMetadata extends
if (wallet.type === WalletType.Script) {
throw new WalletConflictError('addAccount for script wallets is not supported');
}
if (wallet.accounts.some((acc) => 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
Expand All @@ -120,7 +139,8 @@ export class WalletRepository<WalletMetadata extends {}, AccountMetadata extends
{
accountIndex,
extendedAccountPublicKey,
metadata
metadata,
purpose: props.purpose
}
]
})
Expand All @@ -136,6 +156,7 @@ export class WalletRepository<WalletMetadata extends {}, AccountMetadata extends
): Promise<UpdateWalletMetadataProps<WalletMetadata>> {
const { walletId, metadata } = props;
this.#logger.debug('updateWalletMetadata', walletId, metadata);

return firstValueFrom(
this.#getWallets().pipe(
switchMap((wallets) => {
Expand All @@ -160,14 +181,17 @@ export class WalletRepository<WalletMetadata extends {}, AccountMetadata extends
props: UpdateAccountMetadataProps<AccountMetadata>
): Promise<UpdateAccountMetadataProps<AccountMetadata>> {
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, {
Expand All @@ -185,23 +209,29 @@ export class WalletRepository<WalletMetadata extends {}, AccountMetadata extends
}

removeAccount(props: RemoveAccountProps): Promise<RemoveAccountProps> {
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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Metadata extends {}> = {
walletId: WalletId;
/** account' in cip1852 */
accountIndex: number;
purpose?: KeyPurpose;
metadata: Metadata;
extendedAccountPublicKey: Bip32PublicKeyHex;
};
Expand All @@ -24,6 +27,7 @@ export type UpdateWalletMetadataProps<Metadata extends {}> = {
export type UpdateAccountMetadataProps<Metadata extends {}> = {
/** account' in cip1852; must be specified for bip32 wallets */
walletId: WalletId;
purpose?: KeyPurpose;
accountIndex: number;
metadata: Metadata;
};
Expand Down
4 changes: 3 additions & 1 deletion packages/web-extension/src/walletManager/types.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -15,6 +15,7 @@ export type WalletId = string;

export type Bip32WalletAccount<Metadata extends {}> = {
accountIndex: number;
purpose?: KeyPurpose;
/** e.g. account name, picture */
metadata: Metadata;
extendedAccountPublicKey: Bip32PublicKeyHex;
Expand Down Expand Up @@ -55,6 +56,7 @@ export type AnyBip32Wallet<WalletMetadata extends {}, AccountMetadata extends {}

export type OwnSignerAccount = {
walletId: WalletId;
purpose: KeyPurpose;
accountIndex: number;
stakingScriptKeyPath: AccountKeyDerivationPath;
paymentScriptKeyPath: AccountKeyDerivationPath;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '../../src';
import { Cardano, Serialization } from '@cardano-sdk/core';
import { Hash28ByteBase16 } from '@cardano-sdk/crypto';
import { KeyRole } from '@cardano-sdk/key-management';
import { KeyPurpose, KeyRole } from '@cardano-sdk/key-management';
import { firstValueFrom, of } from 'rxjs';
import { logger } from '@cardano-sdk/util-dev';
import pick from 'lodash/pick';
Expand Down Expand Up @@ -41,6 +41,7 @@ const createScriptWalletProps = {
index: 0,
role: KeyRole.External
},
purpose: KeyPurpose.STANDARD,
stakingScriptKeyPath: {
index: 0,
role: KeyRole.External
Expand Down Expand Up @@ -136,6 +137,7 @@ describe('WalletRepository', () => {
index: 0,
role: KeyRole.External
},
purpose: KeyPurpose.STANDARD,
stakingScriptKeyPath: {
index: 0,
role: KeyRole.External
Expand All @@ -158,6 +160,7 @@ describe('WalletRepository', () => {
index: 0,
role: KeyRole.External
},
purpose: KeyPurpose.STANDARD,
stakingScriptKeyPath: {
index: 0,
role: KeyRole.External
Expand Down

0 comments on commit c4df189

Please sign in to comment.