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,145 @@
1
+ import {PrivateKey, TransactionBuilder} from "libnexa-ts";
2
+ import {rostrumProvider} from "../../network/RostrumProvider";
3
+ import {
4
+ buildCreateGroupTransaction,
5
+ populateAndDuplicateTokenAuths,
6
+ populateNexaInputsAndChange,
7
+ populateTokenAuth,
8
+ populateTokenInputsAndChange,
9
+ prepareDeleteTransaction
10
+ } from "../../utils/TXUtils";
11
+ import {BaseAccount} from "../accounts/interfaces/BaseAccountInterface";
12
+ import {AddressKey} from "../../models/wallet.entities";
13
+ import {TransactionCreator} from "./interfaces/TransactionCreator";
14
+ import {keys} from "lodash-es";
15
+ import {PermissionLabel} from "../../models/transaction.entities";
16
+
17
+ export default class WalletTransactionCreator extends TransactionCreator {
18
+
19
+ private _keysToSign: PrivateKey[] = [];
20
+ private _account!: BaseAccount;
21
+
22
+ constructor(fromAccount: BaseAccount, tx?: TransactionBuilder | string | Buffer) {
23
+ super(tx)
24
+ this._account = fromAccount
25
+ this.validateAccount()
26
+ }
27
+
28
+ public fromAccount(fromAccount: BaseAccount): this {
29
+ this._account = fromAccount
30
+ return this
31
+ }
32
+
33
+ public parseTxHex(tx: string) {
34
+ this.builder.push(async () => {
35
+ const txBuilder = new TransactionBuilder(tx)
36
+ const newTxBuilder = new TransactionBuilder()
37
+ const oldInputs = txBuilder.transaction.inputs
38
+ for (const input of oldInputs) {
39
+ const utxo = await rostrumProvider.getUtxo(input.outpoint.toString('hex'))
40
+ newTxBuilder.from({
41
+ outpoint: utxo.tx_hash,
42
+ amount: utxo.amount,
43
+ scriptPubKey: utxo.scriptpubkey
44
+ })
45
+ const foundKey = this.findPrivateKeyFromAddress(utxo.addresses[0])
46
+ if(foundKey) {
47
+ this._keysToSign.push(foundKey.key.privateKey)
48
+ }
49
+
50
+ }
51
+ newTxBuilder.transaction.outputs = txBuilder.transaction.outputs
52
+
53
+ })
54
+
55
+ return this
56
+ }
57
+
58
+ public parseTxBuffer(tx: Buffer) {
59
+ this.builder.push(async () => {
60
+ this.transactionBuilder = new TransactionBuilder(tx);
61
+ })
62
+
63
+ return this
64
+ }
65
+
66
+ public mint(token: string, amount: string): this {
67
+ this.builder.push(async() => {
68
+ let toAddr = this._account.accountKeys.receiveKeys.at(-1)!.address;
69
+ this.tokenAction(toAddr, amount, token, 'mint')
70
+ })
71
+
72
+ return this
73
+ }
74
+
75
+ public melt(token: string, amount: string): this {
76
+ this.builder.push(async() => {
77
+ let toAddr = this._account.accountKeys.receiveKeys.at(-1)!.address;
78
+ this.tokenAction(toAddr, amount, token, 'melt')
79
+ })
80
+
81
+ return this
82
+ }
83
+
84
+ public populate(): this {
85
+ this.validateAccount();
86
+ this.builder.push(async () => {
87
+ let tK: PrivateKey[] = []
88
+ let nK: PrivateKey[] = []
89
+ if(this.tokens.size > 0) {
90
+ for (const tokenAction of this.tokens) {
91
+ if(tokenAction.action == 'mint' || tokenAction.action == 'melt') {
92
+ tK = tK.concat(await populateTokenAuth(this.transactionBuilder, this._account.accountKeys, tokenAction.token!, tokenAction.action))
93
+ } else if (tokenAction.action == 'group') {
94
+ tK = tK.concat(await buildCreateGroupTransaction(this.transactionBuilder, this._account.accountKeys, tokenAction.extraData?.opReturnData!, this.network))
95
+ } else if (tokenAction.action == 'subgroup') {
96
+ tK = tK.concat(await populateTokenAuth(this.transactionBuilder, this._account.accountKeys, tokenAction.parentToken!, 'subgroup', tokenAction.token, this._account.accountKeys.receiveKeys.at(-1)!.address));
97
+ } else if(tokenAction.action == 'renew') {
98
+ tK = tK.concat(await populateAndDuplicateTokenAuths(this.transactionBuilder, this._account.accountKeys, tokenAction.token!, tokenAction.extraData!.perms!, tokenAction.extraData!.address!))
99
+ } else if(tokenAction.action == 'delete') {
100
+ tK = tK.concat(await prepareDeleteTransaction(this.transactionBuilder, this._account.accountKeys, tokenAction.extraData!.outpoint!))
101
+ } else {
102
+ tK = tK.concat(await populateTokenInputsAndChange(this.transactionBuilder, this._account.accountKeys, tokenAction.token!, tokenAction.amount))
103
+ }
104
+
105
+ this._keysToSign!.concat(tK)
106
+ }
107
+ }
108
+
109
+ nK = nK.concat(await populateNexaInputsAndChange(this.transactionBuilder, this._account.accountKeys, this.totalValue, this.txOptions))
110
+ this._keysToSign! = tK.concat(nK)
111
+ })
112
+
113
+ return this
114
+ }
115
+
116
+ public sign(): this {
117
+ this.builder.push(async () => {
118
+ const blockHeight = await rostrumProvider.getBlockTip()
119
+ this.transactionBuilder.lockUntilBlockHeight(blockHeight.height).sign(this._keysToSign!)
120
+ })
121
+
122
+ return this
123
+ }
124
+
125
+ /**
126
+ * Validates that the account has the necessary keys before performing operations
127
+ * @throws Error if account or keys are not properly initialized
128
+ */
129
+ private validateAccount(): void {
130
+ if (!this._account) {
131
+ throw new Error('Account must be set before performing transactions');
132
+ }
133
+ if (!this._account.accountKeys) {
134
+ throw new Error('Account keys are not initialized');
135
+ }
136
+ if (!this._account.accountKeys.receiveKeys || this._account.accountKeys.receiveKeys.length === 0) {
137
+ throw new Error('No receive keys available in account');
138
+ }
139
+ }
140
+
141
+ private findPrivateKeyFromAddress(addr: string): AddressKey | undefined {
142
+ const keys: AddressKey[] = this._account.accountKeys.receiveKeys.concat(this._account.accountKeys.changeKeys)
143
+ return keys.find(key => key.address === addr)
144
+ }
145
+ }
@@ -0,0 +1,189 @@
1
+ import {
2
+ AddressType,
3
+ TransactionBuilder,
4
+ } from "libnexa-ts";
5
+ import {WatchOnlyAddress} from "../../models/wallet.entities";
6
+ import {TransactionCreator} from "./interfaces/TransactionCreator";
7
+ import {
8
+ watchOnlyBuildCreateGroupTransaction, watchOnlyPopulateAndDuplicateTokenAuths,
9
+ watchOnlyPopulateNexaInputsAndChange,
10
+ watchOnlyPopulateTokenAuth, watchOnlyPopulateTokenInputsAndChange, watchOnlyPrepareDeleteTransaction
11
+ } from "../../utils/WatchOnlyTXUtils";
12
+ import {isString} from "lodash-es";
13
+ import {rostrumProvider} from "../../network/RostrumProvider";
14
+ import {PermissionLabel} from "../../models/transaction.entities";
15
+ import {isValidNexaAddress} from "../../utils/WalletUtils";
16
+
17
+ /**
18
+ * WatchOnlyTransactionCreator extends TransactionCreator to handle transaction creation
19
+ * for watch-only wallets. It manages addresses without private keys and delegates
20
+ * UTXO selection and input population to specialized utility functions.
21
+ */
22
+ export default class WatchOnlyTransactionCreator extends TransactionCreator {
23
+
24
+
25
+ /** Addresses that need to be signed with (populated during transaction building) */
26
+ private _addressesToSignWith: string[] = [];
27
+ /** Available addresses for input selection and change */
28
+ private _availableAddresses: WatchOnlyAddress[] = []
29
+
30
+ /**
31
+ * Creates a new WatchOnlyTransactionCreator
32
+ * @param tx Optional existing transaction builder or transaction data
33
+ */
34
+ constructor(tx?: TransactionBuilder | string | Buffer) {
35
+ super(tx)
36
+ }
37
+
38
+ /**
39
+ * Sets the source addresses for transaction inputs
40
+ * @param address Single address string, array of addresses, or WatchOnlyAddress objects
41
+ * @returns This instance for chaining
42
+ */
43
+ public from(address: string | string[] | WatchOnlyAddress[] | WatchOnlyAddress): this {
44
+ if (isString(address)) {
45
+
46
+ if (!isValidNexaAddress(address, this.network) && !isValidNexaAddress(address, this.network, AddressType.PayToPublicKeyHash)) {
47
+ throw new Error('Invalid Address.');
48
+ }
49
+ // Single address string
50
+ this._availableAddresses.push({address: address});
51
+ } else if(Array.isArray(address)) {
52
+ // Array of addresses or WatchOnlyAddress objects
53
+ address.forEach((addr) => {
54
+ if (isString(addr)) {
55
+ if (!isValidNexaAddress(addr, this.network) && !isValidNexaAddress(addr, this.network, AddressType.PayToPublicKeyHash)) {
56
+ throw new Error('Invalid Address.');
57
+ }
58
+ // String address
59
+ this._availableAddresses.push({address: addr});
60
+ } else if (addr && typeof addr === 'object' && 'address' in addr) {
61
+ // WatchOnlyAddress object
62
+ this._availableAddresses.push(<WatchOnlyAddress>addr);
63
+ }
64
+ })
65
+ } else if(address.address != null) {
66
+ // Single WatchOnlyAddress object
67
+ this._availableAddresses.push(<WatchOnlyAddress>address)
68
+ }
69
+ return this
70
+ }
71
+
72
+ /**
73
+ * Adds a token minting operation to the transaction
74
+ * @param token Token ID to mint
75
+ * @param amount Amount to mint
76
+ * @param toAddr Destination address for minted tokens
77
+ * @returns This instance for chaining
78
+ */
79
+ public mint(token: string, amount: string, toAddr:string): this {
80
+ this.builder.push(async() => {
81
+ this.tokenAction(toAddr, amount, token, 'mint')
82
+ })
83
+
84
+ return this
85
+ }
86
+
87
+ /**
88
+ * Adds a token melting operation to the transaction
89
+ * @param token Token ID to melt
90
+ * @param amount Amount to melt
91
+ * @param toAddr Destination address for melted tokens
92
+ * @returns This instance for chaining
93
+ */
94
+ public melt(token: string, amount: string, toAddr:string): this {
95
+ this.builder.push(async() => {
96
+ this.tokenAction(toAddr, amount, token, 'melt')
97
+ })
98
+
99
+ return this
100
+ }
101
+
102
+ /**
103
+ * Populates the transaction with inputs and outputs based on the configured actions.
104
+ * Handles different token operations (mint, melt, group creation, etc.) and
105
+ * populates NEXA inputs for transaction fees.
106
+ * @returns This instance for chaining
107
+ */
108
+ public populate(): this {
109
+ this.builder.push(async () => {
110
+ let tokenAddresses: string[] = []
111
+ let nexaAddresses: string[] = []
112
+
113
+ // Process token operations if any are configured
114
+ if(this.tokens.size > 0) {
115
+ for (const tokenAction of this.tokens) {
116
+ if(tokenAction.action == 'mint' || tokenAction.action == 'melt') {
117
+ // Handle token minting/melting - requires authority
118
+ tokenAddresses = tokenAddresses.concat(await watchOnlyPopulateTokenAuth(this.transactionBuilder, this._availableAddresses, tokenAction.token!, tokenAction.action))
119
+ } else if (tokenAction.action == 'group') {
120
+ // Handle group token creation
121
+ tokenAddresses = tokenAddresses.concat(await watchOnlyBuildCreateGroupTransaction(this.transactionBuilder, this._availableAddresses, tokenAction.token!, this.network))
122
+ } else if (tokenAction.action == 'subgroup') {
123
+ // Handle subgroup token creation
124
+ tokenAddresses = tokenAddresses.concat(await watchOnlyPopulateTokenAuth(this.transactionBuilder, this._availableAddresses, tokenAction.parentToken!, tokenAction.action, tokenAction.token));
125
+ } else if(tokenAction.action == 'renew') {
126
+ // Handle authority renewal
127
+ tokenAddresses = tokenAddresses.concat(await watchOnlyPopulateAndDuplicateTokenAuths(this.transactionBuilder, this._availableAddresses, tokenAction.token!, tokenAction.extraData!.perms!, tokenAction.extraData?.address!))
128
+ } else if(tokenAction.action == 'delete') {
129
+ // Handle authority deletion
130
+ tokenAddresses = tokenAddresses.concat(await watchOnlyPrepareDeleteTransaction(this.transactionBuilder, this._availableAddresses, tokenAction.extraData!.outpoint!))
131
+ } else {
132
+ // Handle regular token transfers
133
+ tokenAddresses = tokenAddresses.concat(await watchOnlyPopulateTokenInputsAndChange(this.transactionBuilder, this._availableAddresses, tokenAction.token!, tokenAction.amount))
134
+ }
135
+
136
+ // Accumulate addresses that need signing
137
+ this._addressesToSignWith!.concat(tokenAddresses)
138
+ }
139
+ }
140
+
141
+ // Populate NEXA inputs for transaction fees and change
142
+ nexaAddresses = nexaAddresses.concat(await watchOnlyPopulateNexaInputsAndChange(this.transactionBuilder, this._availableAddresses, this.totalValue, this.txOptions))
143
+
144
+ // Combine all addresses that need signing
145
+ this._addressesToSignWith! = tokenAddresses.concat(nexaAddresses)
146
+ })
147
+
148
+ return this
149
+ }
150
+
151
+ /**
152
+ * Parse transaction from buffer (not implemented for watch-only)
153
+ * @param tx Transaction buffer
154
+ * @returns This instance for chaining
155
+ * @throws Error indicating method not implemented
156
+ */
157
+ public parseTxBuffer(tx: Buffer): this {
158
+ this.builder.push(async () => {
159
+ this.transactionBuilder = new TransactionBuilder(tx);
160
+ })
161
+ return this
162
+ }
163
+
164
+ /**
165
+ * Parse transaction from hex string (not implemented for watch-only)
166
+ * @param tx Transaction hex string
167
+ * @returns This instance for chaining
168
+ * @throws Error indicating method not implemented
169
+ */
170
+ public parseTxHex(tx: string) {
171
+ this.builder.push(async () => {
172
+ const txBuilder = new TransactionBuilder(tx)
173
+ const newTxBuilder = new TransactionBuilder()
174
+ const oldInputs = txBuilder.transaction.inputs
175
+ for (const input of oldInputs) {
176
+ const utxo = await rostrumProvider.getUtxo(input.outpoint.toString('hex'))
177
+ newTxBuilder.from({
178
+ outpoint: utxo.tx_hash,
179
+ amount: utxo.amount,
180
+ scriptPubKey: utxo.scriptpubkey
181
+ })
182
+ }
183
+ newTxBuilder.transaction.outputs = txBuilder.transaction.outputs
184
+ this.transactionBuilder = newTxBuilder;
185
+ })
186
+
187
+ return this
188
+ }
189
+ }