nexa-wallet-sdk 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.parcel-cache/3e09f086f3c4d605-AssetGraph +0 -0
  2. package/.parcel-cache/5eac57ec674cdae8-AssetGraph +0 -0
  3. package/.parcel-cache/data.mdb +0 -0
  4. package/.parcel-cache/e43547b6c9167b58-RequestGraph +0 -0
  5. package/.parcel-cache/ecfe15d74834bbfd-BundleGraph +0 -0
  6. package/.parcel-cache/lock.mdb +0 -0
  7. package/.parcel-cache/snapshot-e43547b6c9167b58.txt +2 -0
  8. package/README.md +445 -0
  9. package/dist/browser/index.js +2456 -0
  10. package/dist/browser/index.js.map +1 -0
  11. package/dist/index.d.ts +918 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +2915 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/index.mjs +2456 -0
  16. package/dist/index.mjs.map +1 -0
  17. package/package.json +90 -0
  18. package/spec.md +257 -0
  19. package/src/index.ts +93 -0
  20. package/src/models/rostrum.entities.ts +159 -0
  21. package/src/models/transaction.entities.ts +46 -0
  22. package/src/models/wallet.entities.ts +42 -0
  23. package/src/network/RostrumProvider.ts +137 -0
  24. package/src/types.ts +0 -0
  25. package/src/utils/CommonUtils.ts +123 -0
  26. package/src/utils/TXUtils.ts +445 -0
  27. package/src/utils/TokenUtils.ts +75 -0
  28. package/src/utils/ValidationUtils.ts +86 -0
  29. package/src/utils/WalletUtils.ts +522 -0
  30. package/src/utils/WatchOnlyTXUtils.ts +275 -0
  31. package/src/wallet/Wallet.ts +397 -0
  32. package/src/wallet/WatchOnlyWallet.ts +169 -0
  33. package/src/wallet/accounts/AccountStore.ts +173 -0
  34. package/src/wallet/accounts/interfaces/BaseAccountInterface.ts +56 -0
  35. package/src/wallet/accounts/models/DappAccount.ts +80 -0
  36. package/src/wallet/accounts/models/DefaultAccount.ts +96 -0
  37. package/src/wallet/accounts/models/VaultAccount.ts +81 -0
  38. package/src/wallet/transactions/WalletTransactionCreator.ts +145 -0
  39. package/src/wallet/transactions/WatchOnlyTransactionCreator.ts +189 -0
  40. package/src/wallet/transactions/interfaces/TransactionCreator.ts +438 -0
  41. package/tests/core/tx/transactioncreator.test.ts +455 -0
  42. package/tests/core/tx/wallettransactioncreator.test.ts +362 -0
  43. package/tests/core/tx/watchonlytransactioncreator.test.ts +258 -0
  44. package/tests/core/wallet/accountstore.test.ts +341 -0
  45. package/tests/core/wallet/wallet.test.ts +69 -0
  46. package/tests/core/watchonlywallet/watchonlywallet.test.ts +251 -0
  47. package/tests/index.test.ts +12 -0
  48. package/tsconfig.json +113 -0
@@ -0,0 +1,169 @@
1
+ import {Networkish, Networks, TransactionBuilder} from "libnexa-ts";
2
+ import WatchOnlyTransactionCreator from "./transactions/WatchOnlyTransactionCreator";
3
+ import {rostrumProvider} from "../network/RostrumProvider";
4
+ import {WatchOnlyAddress} from "../models/wallet.entities";
5
+ import {isArray, isBuffer, isNil, isObject, isString} from "lodash-es";
6
+ import ValidationUtils from "../utils/ValidationUtils";
7
+ import {isValidNexaAddress} from "../utils/WalletUtils";
8
+ import {SubscribeCallback} from "@vgrunner/electrum-cash";
9
+ import {isNullOrEmpty} from "../utils/CommonUtils";
10
+
11
+ /**
12
+ * WatchOnlyWallet provides functionality for monitoring and creating transactions
13
+ * for addresses without storing private keys. This allows users to track balances
14
+ * and create unsigned transactions that can be signed elsewhere.
15
+ */
16
+ export default class WatchOnlyWallet {
17
+ /** The blockchain network this wallet operates on */
18
+ private readonly _network: Networkish
19
+ /** Array of addresses to monitor without their private keys */
20
+ private readonly _addressesToWatch: WatchOnlyAddress[];
21
+
22
+ /**
23
+ * Creates a new WatchOnlyWallet instance
24
+ * @param addressesToWatch Array of addresses to monitor
25
+ * @param network Optional network name (defaults to mainnet)
26
+ * @throws Error if validation fails
27
+ */
28
+ constructor(addressesToWatch: WatchOnlyAddress[], network?: string) {
29
+ // Validate network parameter
30
+ if (network !== undefined && !isString(network)) {
31
+ throw new Error('Network must be a string');
32
+ }
33
+
34
+ if (network !== undefined && isString(network)) {
35
+ const n = Networks.get(network)
36
+ if (n === undefined) {
37
+ throw new Error(`Invalid network: ${network}`);
38
+ }
39
+ }
40
+
41
+ this._network = Networks.get(network) ?? Networks.mainnet;
42
+ // Validate addressesToWatch parameter
43
+ this._addressesToWatch = this.validateAddressesToWatch(addressesToWatch);
44
+ }
45
+
46
+ /**
47
+ * Validates the addresses to watch array
48
+ * @param addressesToWatch Array of addresses to validate
49
+ * @returns Validated array of WatchOnlyAddress objects
50
+ * @throws Error if validation fails
51
+ */
52
+ private validateAddressesToWatch(addressesToWatch: WatchOnlyAddress[]): WatchOnlyAddress[] {
53
+ if (addressesToWatch === null || addressesToWatch === undefined) {
54
+ throw new Error('addresesToWatch is required');
55
+ }
56
+
57
+ if (!isArray(addressesToWatch)) {
58
+ throw new Error('addressesToWatch must be an array');
59
+ }
60
+
61
+ // Check if array is not empty
62
+ if (addressesToWatch.length === 0) {
63
+ throw new Error('addressesToWatch cannot be empty');
64
+ }
65
+
66
+ // Validate each address object
67
+ const validatedAddresses: WatchOnlyAddress[] = [];
68
+
69
+ for (let i = 0; i < addressesToWatch.length; i++) {
70
+ const addr = addressesToWatch[i];
71
+
72
+ // Check if address is an object
73
+ if (!isObject(addr) || isArray(addr)) {
74
+ throw new Error(`addressesToWatch[${i}] must be an object`);
75
+ }
76
+
77
+ // Check if address property exists and is a string
78
+ if (!addr.hasOwnProperty('address') || !isString(addr.address)) {
79
+ throw new Error(`addressesToWatch[${i}].address must be a string`);
80
+ }
81
+
82
+ // Check if address is not empty
83
+ if (addr.address.trim() === '') {
84
+ throw new Error(`addressesToWatch[${i}].address cannot be empty`);
85
+ }
86
+
87
+ // Validate address format
88
+ if (!isValidNexaAddress(addr.address, this._network)) {
89
+ throw new Error(`addressesToWatch[${i}].address is not a valid NEXA address: ${addr.address}`);
90
+ }
91
+
92
+ // Validate optional xPub property
93
+ if (addr.xPub !== undefined && !isObject(addr.xPub)) {
94
+ throw new Error(`addressesToWatch[${i}].xPub must be a PublicKey object`);
95
+ }
96
+
97
+ // Validate optional derivationPath property
98
+ if (addr.derivationPath !== undefined && !isString(addr.derivationPath)) {
99
+ throw new Error(`addressesToWatch[${i}].derivationPath must be a string`);
100
+ }
101
+
102
+ // Check for duplicate addresses
103
+ const isDuplicate = validatedAddresses.some(existingAddr =>
104
+ existingAddr.address === addr.address
105
+ );
106
+ if (isDuplicate) {
107
+ throw new Error(`Duplicate address found: ${addr.address}`);
108
+ }
109
+
110
+ validatedAddresses.push({
111
+ address: addr.address.trim(),
112
+ xPub: addr.xPub,
113
+ derivationPath: addr.derivationPath
114
+ });
115
+ }
116
+
117
+ return validatedAddresses;
118
+ }
119
+
120
+ /**
121
+ * Creates a new transaction creator for this watch-only wallet
122
+ * @param x Optional transaction data - can be a TransactionBuilder, hex string, or Buffer
123
+ * @returns WatchOnlyTransactionCreator configured with wallet's addresses and network
124
+ */
125
+ public newTransaction(x?: TransactionBuilder | string | Buffer): WatchOnlyTransactionCreator {
126
+ let tx: WatchOnlyTransactionCreator;
127
+
128
+ // Handle different input types for creating transactions
129
+ if (x instanceof TransactionBuilder) {
130
+ // Use existing TransactionBuilder instance
131
+ tx = new WatchOnlyTransactionCreator(x);
132
+ } else if (isString(x)) {
133
+ // Parse transaction from hex string
134
+ tx = new WatchOnlyTransactionCreator().parseTxHex(x);
135
+ } else if (isBuffer(x) && !isNil(x)) {
136
+ // Parse transaction from buffer
137
+ tx = new WatchOnlyTransactionCreator().parseTxBuffer(x);
138
+ } else {
139
+ // Create new empty transaction
140
+ tx = new WatchOnlyTransactionCreator();
141
+ }
142
+
143
+ // Configure transaction with wallet's addresses and network
144
+ return tx.from(this._addressesToWatch).onNetwork(this._network);
145
+ }
146
+
147
+ /**
148
+ * Broadcasts a signed transaction to the network
149
+ * @param transaction Hex-encoded signed transaction
150
+ * @returns Promise resolving to transaction ID
151
+ * @throws Error if transaction is invalid or broadcast fails
152
+ */
153
+ public async sendTransaction(transaction: string): Promise<string> {
154
+ ValidationUtils.validateArgument(isString(transaction), 'transaction must be present and valid')
155
+ return rostrumProvider.broadcast(transaction)
156
+ }
157
+
158
+ public async subscribeToAddressNotifications(callback: SubscribeCallback): Promise<void>{
159
+ await rostrumProvider.subscribeToAddresses(this._addressesToWatch.map(addr => addr.address), callback)
160
+ }
161
+
162
+ /**
163
+ * Gets the list of addresses being watched
164
+ * @returns Array of watched addresses (copy to prevent mutation)
165
+ */
166
+ public getWatchedAddresses(): WatchOnlyAddress[] {
167
+ return [...this._addressesToWatch];
168
+ }
169
+ }
@@ -0,0 +1,173 @@
1
+ import bigDecimal from "js-big-decimal";
2
+ import {BaseAccount} from "./interfaces/BaseAccountInterface";
3
+ import {
4
+ AccountType,
5
+ discoverNexaAccount,
6
+ generateAccountKey,
7
+ generateKeyAndAddress, generateKeysAndAddresses,
8
+ getNextAccountIndex
9
+ } from "../../utils/WalletUtils";
10
+ import {HDPrivateKey} from "libnexa-ts";
11
+ import DAppAccount from "./models/DappAccount";
12
+ import VaultAccount from "./models/VaultAccount";
13
+ import DefaultAccount from "./models/DefaultAccount";
14
+ import {AccountIndexes, AddressKey} from "../../models/wallet.entities";
15
+
16
+ /**
17
+ * AccountStore manages a collection of wallet accounts of different types.
18
+ * It provides functionality to create, import, export, and manage accounts
19
+ * including DApp accounts, Vault accounts, and Default NEXA accounts.
20
+ */
21
+ export default class AccountStore {
22
+
23
+ /** Map storing all accounts indexed by their unique store key */
24
+ private readonly _accounts: Map<string, BaseAccount>
25
+
26
+ /**
27
+ * Creates a new AccountStore instance
28
+ * Initializes an empty map to store accounts
29
+ */
30
+ public constructor() {
31
+ this._accounts = new Map<string, BaseAccount>()
32
+ }
33
+
34
+ /**
35
+ * Generates a unique store key for an account based on its type and index
36
+ * @param accountType The type of account (DAPP, VAULT, or DEFAULT)
37
+ * @param index The account index
38
+ * @returns Unique string key for storing the account
39
+ */
40
+ private getAccountStoreKey(accountType: AccountType, index: number): String {
41
+ switch (accountType){
42
+ case AccountType.DAPP_ACCOUNT:
43
+ // DApp accounts use format: "2.index"
44
+ return String(accountType + '.' + index);
45
+ case AccountType.VAULT_ACCOUNT:
46
+ // Vault accounts use format: "1.index"
47
+ return String(accountType + '.' + index);
48
+ default:
49
+ // Default accounts use just the index
50
+ return String(index)
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Creates a new account of the specified type
56
+ * @param accountType Type of account to create (DAPP, VAULT, or DEFAULT)
57
+ * @param masterKey Master HD private key for deriving account keys
58
+ * @returns Promise resolving to the created account
59
+ */
60
+ async createAccount(accountType: AccountType, masterKey: HDPrivateKey): Promise<BaseAccount> {
61
+ // Get the next available index for this account type
62
+ const nextIndex = await getNextAccountIndex(accountType, masterKey);
63
+ const accountStoreKey = this.getAccountStoreKey(accountType, nextIndex)
64
+
65
+ // Check if account already exists
66
+ const indexExists = this._accounts.get(String(accountStoreKey))
67
+ if(indexExists) {
68
+ return indexExists
69
+ }
70
+
71
+ switch (accountType){
72
+ case AccountType.DAPP_ACCOUNT:
73
+ // Create DApp account (purpose 2)
74
+ let dappAccountKey = generateAccountKey(masterKey, 2);
75
+ const dAppAccount = new DAppAccount(2, nextIndex, generateKeyAndAddress(dappAccountKey, nextIndex))
76
+ await dAppAccount.loadBalances();
77
+ this._accounts.set(dAppAccount.getAccountStoreKey(), dAppAccount)
78
+ return dAppAccount
79
+ case AccountType.VAULT_ACCOUNT:
80
+ // Create Vault account (purpose 1)
81
+ let vaultAccountKey = generateAccountKey(masterKey, 1);
82
+ const vaultAccount = new VaultAccount(1, nextIndex, generateKeyAndAddress(vaultAccountKey, nextIndex))
83
+ await vaultAccount.loadBalances();
84
+ this._accounts.set(vaultAccount.getAccountStoreKey(), vaultAccount)
85
+ return vaultAccount
86
+ default:
87
+ // Create default NEXA account with receive and change addresses
88
+ let nexaAccountKey = generateAccountKey(masterKey, nextIndex);
89
+ const nexaAccountIndexes: AccountIndexes = { rIndex: 0, cIndex: 0 };
90
+ const nexaAccount = new DefaultAccount(nextIndex, nexaAccountIndexes, generateKeysAndAddresses(nexaAccountKey, nexaAccountIndexes.rIndex + 1, nexaAccountIndexes.rIndex + 20, nexaAccountIndexes.cIndex + 1, nexaAccountIndexes.cIndex + 20))
91
+ await nexaAccount.loadBalances()
92
+ this._accounts.set(nexaAccount.getAccountStoreKey(), nexaAccount)
93
+ return nexaAccount;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Finds the private key associated with a given address across all accounts
99
+ * @param address The address to search for
100
+ * @returns The AddressKey containing the private key, or null if not found
101
+ */
102
+ findKeyForAddress(address: string): AddressKey | null {
103
+ // Search through all accounts
104
+ for (const [_, account] of this._accounts.entries()) {
105
+ // Combine receive and change keys for this account
106
+ const allKeys = account.accountKeys.receiveKeys.concat(account.accountKeys.changeKeys)
107
+
108
+ // Check each key for a matching address
109
+ for(const key of allKeys){
110
+ if(key.address == address) {
111
+ return key
112
+ }
113
+ }
114
+ }
115
+ return null
116
+ }
117
+
118
+ /**
119
+ * Imports an existing account into the store
120
+ * @param accountData The account data to import
121
+ * @throws Error if an account with the same key already exists
122
+ */
123
+ importAccount(accountData: BaseAccount): void {
124
+ let index: string = accountData.getAccountStoreKey()
125
+ if(this._accounts.get(index)) {
126
+ throw Error('Account already exists!')
127
+ }
128
+ this._accounts.set(String(index), accountData)
129
+ }
130
+
131
+ /**
132
+ * Exports account data by index
133
+ * @param accountIndex The account index to export
134
+ * @returns The account data
135
+ * @throws Error if the account doesn't exist
136
+ */
137
+ exportAccount(accountIndex: string): BaseAccount {
138
+ if(!this._accounts.get(accountIndex)) {
139
+ throw Error('Cannot find account!')
140
+ }
141
+
142
+ return this._accounts.get(accountIndex)!
143
+ }
144
+
145
+ /**
146
+ * Removes an account from the store
147
+ * @param accountIndex The account index to remove
148
+ * @throws Error if the account doesn't exist
149
+ */
150
+ removeAccount(accountIndex: string): void {
151
+ if(!this._accounts.get(accountIndex)) {
152
+ throw Error('Cannot find account!')
153
+ }
154
+ this._accounts.delete(accountIndex)
155
+ }
156
+
157
+ /**
158
+ * Returns all accounts in the store
159
+ * @returns Map of account store keys to BaseAccount objects
160
+ */
161
+ listAccounts(): Map<string, BaseAccount> {
162
+ return this._accounts
163
+ }
164
+
165
+ /**
166
+ * Retrieves a specific account by its index
167
+ * @param index The account index to retrieve
168
+ * @returns The account if found, undefined otherwise
169
+ */
170
+ getAccount(index:string): BaseAccount | undefined {
171
+ return this._accounts.get(index)
172
+ }
173
+ }
@@ -0,0 +1,56 @@
1
+ import {AccountIndexes, AccountKeys, AddressKey, Balance} from "../../../models/wallet.entities";
2
+ import DefaultAccount from "../models/DefaultAccount";
3
+ import bigDecimal from "js-big-decimal";
4
+ import {AccountType} from "../../../utils/WalletUtils";
5
+ import {PrivateKey} from "libnexa-ts";
6
+ import {TransactionEntity} from "../../../models/transaction.entities";
7
+
8
+ export abstract class BaseAccount {
9
+ get transactions(): Map<string, TransactionEntity> {
10
+ return this._transactions;
11
+ }
12
+
13
+ set transactions(value: Map<string, TransactionEntity>) {
14
+ this._transactions = value;
15
+ }
16
+
17
+ protected readonly _bip44Account: number;
18
+ private _balance?: Balance;
19
+ private _tokenBalances?: Record<string, Balance> = {};
20
+ private _transactions : Map<string, TransactionEntity> = new Map<string, TransactionEntity>()
21
+
22
+ protected constructor(_bip44Account: number) {
23
+ this._bip44Account = _bip44Account;
24
+ this._balance = {
25
+ confirmed: 0,
26
+ unconfirmed: 0
27
+ }
28
+ this._tokenBalances = {};
29
+ }
30
+
31
+ abstract get accountIndexes(): AccountIndexes;
32
+ abstract get accountKeys(): AccountKeys;
33
+ abstract getNewChangeAddress(): string;
34
+ abstract getNewAddress(): string;
35
+ abstract getAccountStoreKey(): string;
36
+ abstract getAccountType(): AccountType;
37
+ abstract loadBalances(): Promise<void>;
38
+ abstract getKeyFromAddress(address:string): AddressKey;
39
+ abstract getTransactions(fromHeight?: number, address?:string): Promise<Map<string, TransactionEntity>>;
40
+
41
+ get balance(): Balance {
42
+ return this._balance!;
43
+ }
44
+
45
+ set balance(value: Balance) {
46
+ this._balance = value;
47
+ }
48
+
49
+ get tokenBalances(): Record<string, Balance> {
50
+ return this._tokenBalances!;
51
+ }
52
+
53
+ set tokenBalances(value: Record<string, Balance>) {
54
+ this._tokenBalances = value;
55
+ }
56
+ }
@@ -0,0 +1,80 @@
1
+ import {AccountIndexes, AccountKeys, AddressKey} from "../../../models/wallet.entities";
2
+ import {BaseAccount} from "../interfaces/BaseAccountInterface";
3
+ import {
4
+ AccountType, classifyTransaction,
5
+ fetchTotalBalance,
6
+ fetchTransactionsHistory,
7
+ sumBalance,
8
+ sumTokensBalance
9
+ } from "../../../utils/WalletUtils";
10
+ import {PrivateKey} from "libnexa-ts";
11
+ import {TransactionEntity} from "../../../models/transaction.entities";
12
+
13
+ export default class DAppAccount extends BaseAccount {
14
+
15
+ private readonly _accountIndex: number;
16
+ private readonly _accountKey: AddressKey;
17
+
18
+ constructor(bip44Account: number, accountIndex: number, addressKey: AddressKey) {
19
+ super(bip44Account)
20
+ this._accountIndex = accountIndex;
21
+ this._accountKey = addressKey
22
+ }
23
+
24
+ // this is used in AccountStore.ts to get the key to be used in the map for this account
25
+ public getAccountStoreKey(): string {
26
+ return String(this._bip44Account + '.' + this._accountIndex);
27
+ }
28
+
29
+ public getAccountType(): AccountType {
30
+ return AccountType.DAPP_ACCOUNT;
31
+ }
32
+
33
+ public getNewAddress() {
34
+ return this._accountKey.address;
35
+ }
36
+
37
+ public getNewChangeAddress(): string {
38
+ return this._accountKey.address;
39
+ }
40
+
41
+ get accountIndexes(): AccountIndexes {
42
+ return {
43
+ rIndex: this._accountIndex,
44
+ cIndex: 0
45
+ };
46
+ }
47
+
48
+ get accountKeys(): AccountKeys {
49
+ return {
50
+ receiveKeys: [this._accountKey],
51
+ changeKeys: [this._accountKey]
52
+ };
53
+ }
54
+
55
+ async loadBalances(): Promise<void> {
56
+ let balances = await fetchTotalBalance([this._accountKey]);
57
+ let tokenBalances = [this._accountKey].map(k => k.tokensBalance);
58
+ super.balance = sumBalance(balances)
59
+ super.tokenBalances = sumTokensBalance(tokenBalances)
60
+ }
61
+
62
+ getKeyFromAddress(address: string): AddressKey {
63
+ return this._accountKey;
64
+ }
65
+
66
+ async getTransactions(fromHeight?: number, address?: string): Promise<Map<string, TransactionEntity>> {
67
+ let txPromises:TransactionEntity[] = []
68
+
69
+ const transactions = await fetchTransactionsHistory([address ?? this._accountKey.address], fromHeight ?? 0)
70
+ for (let tx of transactions.txs.values()) {
71
+ let t = await classifyTransaction(tx, [address ?? this._accountKey.address])
72
+ txPromises.push(t)
73
+ }
74
+ await Promise.all(txPromises)
75
+ for (let txEntity of txPromises){
76
+ this.transactions.set(txEntity.txId, txEntity)
77
+ }
78
+ return this.transactions
79
+ }
80
+ }
@@ -0,0 +1,96 @@
1
+ import {AccountIndexes, AccountKeys, AddressKey} from "../../../models/wallet.entities";
2
+ import {BaseAccount} from "../interfaces/BaseAccountInterface";
3
+ import {
4
+ AccountType, classifyTransaction,
5
+ fetchTotalBalance,
6
+ fetchTransactionsHistory,
7
+ sumBalance,
8
+ sumTokensBalance
9
+ } from "../../../utils/WalletUtils";
10
+ import {PrivateKey} from "libnexa-ts";
11
+ import {TransactionEntity} from "../../../models/transaction.entities";
12
+
13
+ export default class DefaultAccount extends BaseAccount {
14
+
15
+ private readonly _accountIndexes: AccountIndexes; // either a list of indexes or a single bip44 account index
16
+ private _accountKeys: AccountKeys; // either a list of keys or a single key
17
+
18
+ constructor(bip44Account: number, accountIndexes: AccountIndexes, accountKeys: AccountKeys) {
19
+ super(bip44Account)
20
+ if (accountIndexes.rIndex < 0) {
21
+ throw new Error(`Can not create nexa account with an rindex of ${accountIndexes.rIndex}. must be >= 0.`);
22
+ }
23
+ if (accountIndexes.cIndex < 0) {
24
+ throw new Error(`Can not create nexa account with an cindex of ${accountIndexes.cIndex}. must be >= 0.`);
25
+ }
26
+ this._accountIndexes = accountIndexes;
27
+ this._accountKeys = accountKeys;
28
+ }
29
+
30
+ // this is used in AccountStore.ts to get the key to be used in the map for this account
31
+ public getAccountStoreKey() {
32
+ return String(this._bip44Account);
33
+ }
34
+
35
+ public getAccountType(): AccountType {
36
+ return AccountType.NEXA_ACCOUNT
37
+ }
38
+
39
+ public getNewAddress() {
40
+ return this._accountKeys.receiveKeys[this._accountKeys.receiveKeys.length - 1]?.address ?? '';
41
+ }
42
+
43
+ public getNewChangeAddress(): string {
44
+ return this._accountKeys.changeKeys[this._accountKeys.changeKeys.length - 1]?.address ?? ''
45
+ }
46
+
47
+ get accountIndexes(): AccountIndexes {
48
+ return this._accountIndexes;
49
+ }
50
+
51
+ get accountKeys(): AccountKeys {
52
+ return this._accountKeys;
53
+ }
54
+
55
+ async loadBalances(): Promise<void> {
56
+ let balances = await fetchTotalBalance(this._accountKeys.receiveKeys.concat(this._accountKeys.changeKeys));
57
+ let tokenBalances = this._accountKeys.receiveKeys.concat(this._accountKeys.changeKeys).map(k => k.tokensBalance);
58
+ super.balance = sumBalance(balances)
59
+ super.tokenBalances = sumTokensBalance(tokenBalances)
60
+ }
61
+
62
+ getKeyFromAddress(address: string): AddressKey {
63
+ const allKeys = this._accountKeys.receiveKeys.concat(this._accountKeys.changeKeys)
64
+ const keyFound = allKeys.find(key => key.address === address)
65
+ return keyFound!
66
+ }
67
+
68
+ async getTransactions(fromHeight?: number, address?: string): Promise<Map<string, TransactionEntity>> {
69
+
70
+ let receiveAddresses = this.accountKeys.receiveKeys.map(ak => ak.address);
71
+ let changeAddresses = this.accountKeys.changeKeys.map(ak => ak.address);
72
+ let allAddresses = receiveAddresses.concat(changeAddresses);
73
+
74
+ let rTxs = fetchTransactionsHistory(address != null ? [address]: receiveAddresses, fromHeight ?? 0);
75
+ let cTxs = fetchTransactionsHistory(address != null ? [address] : receiveAddresses, fromHeight ?? 0);
76
+
77
+ let [rData, cData] = await Promise.all([rTxs, cTxs]);
78
+
79
+ let txHistory = rData.txs;
80
+ for (let tx of cData.txs.values()) {
81
+ txHistory.set(tx.tx_hash, tx);
82
+ }
83
+
84
+ let txPromises: TransactionEntity[] = [];
85
+ for (let tx of txHistory.values()) {
86
+ let t = await classifyTransaction(tx, allAddresses);
87
+ txPromises.push(t);
88
+ }
89
+ await Promise.all(txPromises)
90
+
91
+ for (let txEntity of txPromises){
92
+ this.transactions.set(txEntity.txId, txEntity)
93
+ }
94
+ return this.transactions
95
+ }
96
+ }
@@ -0,0 +1,81 @@
1
+ import {AccountIndexes, AccountKeys, AddressKey} from "../../../models/wallet.entities";
2
+ import {BaseAccount} from "../interfaces/BaseAccountInterface";
3
+ import {
4
+ AccountType, classifyTransaction,
5
+ fetchTotalBalance,
6
+ fetchTransactionsHistory,
7
+ sumBalance,
8
+ sumTokensBalance
9
+ } from "../../../utils/WalletUtils";
10
+ import {PrivateKey} from "libnexa-ts";
11
+ import {TransactionEntity} from "../../../models/transaction.entities";
12
+ import {rostrumProvider} from "../../../network/RostrumProvider";
13
+
14
+ export default class VaultAccount extends BaseAccount {
15
+
16
+ private readonly _accountIndex: number;
17
+ private readonly _accountKey: AddressKey;
18
+
19
+ constructor(bip44Account: number, accountIndex: number, addressKey: AddressKey) {
20
+ super(bip44Account)
21
+ this._accountIndex = accountIndex;
22
+ this._accountKey = addressKey;
23
+ }
24
+
25
+ // this is used in AccountStore.ts to get the key to be used in the map for this account
26
+ public getAccountStoreKey(): string {
27
+ return String(this._bip44Account + '.' + this._accountIndex);
28
+ }
29
+
30
+ public getAccountType(): AccountType {
31
+ return AccountType.VAULT_ACCOUNT
32
+ }
33
+
34
+ public getNewAddress() {
35
+ return this._accountKey.address
36
+ }
37
+
38
+ public getNewChangeAddress(): string {
39
+ return this._accountKey.address
40
+ }
41
+
42
+ get accountIndexes(): AccountIndexes {
43
+ return {
44
+ rIndex: this._accountIndex,
45
+ cIndex: 0
46
+ };
47
+ }
48
+
49
+ get accountKeys(): AccountKeys {
50
+ return {
51
+ receiveKeys: [this._accountKey],
52
+ changeKeys: [this._accountKey]
53
+ };
54
+ }
55
+
56
+ async loadBalances(): Promise<void> {
57
+ let balances = await fetchTotalBalance([this._accountKey]);
58
+ let tokenBalances = [this._accountKey].map(k => k.tokensBalance);
59
+ super.balance = sumBalance(balances)
60
+ super.tokenBalances = sumTokensBalance(tokenBalances)
61
+ }
62
+
63
+ getKeyFromAddress(address: string): AddressKey {
64
+ return this._accountKey;
65
+ }
66
+
67
+ async getTransactions(fromHeight?: number, address?: string): Promise<Map<string, TransactionEntity>> {
68
+ let txPromises:TransactionEntity[] = []
69
+
70
+ const transactions = await fetchTransactionsHistory([address ?? this._accountKey.address], fromHeight ?? 0)
71
+ for (let tx of transactions.txs.values()) {
72
+ let t = await classifyTransaction(tx, [address ?? this._accountKey.address])
73
+ txPromises.push(t)
74
+ }
75
+ await Promise.all(txPromises)
76
+ for (let txEntity of txPromises){
77
+ this.transactions.set(txEntity.txId, txEntity)
78
+ }
79
+ return this.transactions
80
+ }
81
+ }