@dolaned/wallet-sdk-ts
Version:
Wallet SDK for the Nexa blockchain
174 lines (158 loc) • 6.93 kB
text/typescript
import bigDecimal from "js-big-decimal";
import {BaseAccount} from "./interfaces/BaseAccountInterface";
import {
AccountType,
discoverNexaAccount,
generateAccountKey,
generateKeyAndAddress, generateKeysAndAddresses,
getNextAccountIndex
} from "../../utils/WalletUtils";
import {HDPrivateKey} from "libnexa-ts";
import DAppAccount from "./models/DappAccount";
import VaultAccount from "./models/VaultAccount";
import DefaultAccount from "./models/DefaultAccount";
import {AccountIndexes, AddressKey} from "../../models/wallet.entities";
/**
* AccountStore manages a collection of wallet accounts of different types.
* It provides functionality to create, import, export, and manage accounts
* including DApp accounts, Vault accounts, and Default NEXA accounts.
*/
export default class AccountStore {
/** Map storing all accounts indexed by their unique store key */
private readonly _accounts: Map<string, BaseAccount>
/**
* Creates a new AccountStore instance
* Initializes an empty map to store accounts
*/
public constructor() {
this._accounts = new Map<string, BaseAccount>()
}
/**
* Generates a unique store key for an account based on its type and index
* @param accountType The type of account (DAPP, VAULT, or DEFAULT)
* @param index The account index
* @returns Unique string key for storing the account
*/
private getAccountStoreKey(accountType: AccountType, index: number): String {
switch (accountType){
case AccountType.DAPP_ACCOUNT:
// DApp accounts use format: "2.index"
return String(accountType + '.' + index);
case AccountType.VAULT_ACCOUNT:
// Vault accounts use format: "1.index"
return String(accountType + '.' + index);
default:
// Default accounts use just the index
return String(index)
}
}
/**
* Creates a new account of the specified type
* @param accountType Type of account to create (DAPP, VAULT, or DEFAULT)
* @param masterKey Master HD private key for deriving account keys
* @returns Promise resolving to the created account
*/
async createAccount(accountType: AccountType, masterKey: HDPrivateKey): Promise<BaseAccount> {
// Get the next available index for this account type
const nextIndex = await getNextAccountIndex(accountType, masterKey);
const accountStoreKey = this.getAccountStoreKey(accountType, nextIndex)
// Check if account already exists
const indexExists = this._accounts.get(String(accountStoreKey))
if(indexExists) {
return indexExists
}
switch (accountType){
case AccountType.DAPP_ACCOUNT:
// Create DApp account (purpose 2)
let dappAccountKey = generateAccountKey(masterKey, 2);
const dAppAccount = new DAppAccount(2, nextIndex, generateKeyAndAddress(dappAccountKey, nextIndex))
await dAppAccount.loadBalances();
this._accounts.set(dAppAccount.getAccountStoreKey(), dAppAccount)
return dAppAccount
case AccountType.VAULT_ACCOUNT:
// Create Vault account (purpose 1)
let vaultAccountKey = generateAccountKey(masterKey, 1);
const vaultAccount = new VaultAccount(1, nextIndex, generateKeyAndAddress(vaultAccountKey, nextIndex))
await vaultAccount.loadBalances();
this._accounts.set(vaultAccount.getAccountStoreKey(), vaultAccount)
return vaultAccount
default:
// Create default NEXA account with receive and change addresses
let nexaAccountKey = generateAccountKey(masterKey, nextIndex);
const nexaAccountIndexes: AccountIndexes = { rIndex: 0, cIndex: 0 };
const nexaAccount = new DefaultAccount(nextIndex, nexaAccountIndexes, generateKeysAndAddresses(nexaAccountKey, nexaAccountIndexes.rIndex + 1, nexaAccountIndexes.rIndex + 20, nexaAccountIndexes.cIndex + 1, nexaAccountIndexes.cIndex + 20))
await nexaAccount.loadBalances()
this._accounts.set(nexaAccount.getAccountStoreKey(), nexaAccount)
return nexaAccount;
}
}
/**
* Finds the private key associated with a given address across all accounts
* @param address The address to search for
* @returns The AddressKey containing the private key, or null if not found
*/
findKeyForAddress(address: string): AddressKey | null {
// Search through all accounts
for (const [_, account] of this._accounts.entries()) {
// Combine receive and change keys for this account
const allKeys = account.accountKeys.receiveKeys.concat(account.accountKeys.changeKeys)
// Check each key for a matching address
for(const key of allKeys){
if(key.address == address) {
return key
}
}
}
return null
}
/**
* Imports an existing account into the store
* @param accountData The account data to import
* @throws Error if an account with the same key already exists
*/
importAccount(accountData: BaseAccount): void {
let index: string = accountData.getAccountStoreKey()
if(this._accounts.get(index)) {
throw Error('Account already exists!')
}
this._accounts.set(String(index), accountData)
}
/**
* Exports account data by index
* @param accountIndex The account index to export
* @returns The account data
* @throws Error if the account doesn't exist
*/
exportAccount(accountIndex: string): BaseAccount {
if(!this._accounts.get(accountIndex)) {
throw Error('Cannot find account!')
}
return this._accounts.get(accountIndex)!
}
/**
* Removes an account from the store
* @param accountIndex The account index to remove
* @throws Error if the account doesn't exist
*/
removeAccount(accountIndex: string): void {
if(!this._accounts.get(accountIndex)) {
throw Error('Cannot find account!')
}
this._accounts.delete(accountIndex)
}
/**
* Returns all accounts in the store
* @returns Map of account store keys to BaseAccount objects
*/
listAccounts(): Map<string, BaseAccount> {
return this._accounts
}
/**
* Retrieves a specific account by its index
* @param index The account index to retrieve
* @returns The account if found, undefined otherwise
*/
getAccount(index:string): BaseAccount | undefined {
return this._accounts.get(index)
}
}