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.
- package/.parcel-cache/3e09f086f3c4d605-AssetGraph +0 -0
- package/.parcel-cache/5eac57ec674cdae8-AssetGraph +0 -0
- package/.parcel-cache/data.mdb +0 -0
- package/.parcel-cache/e43547b6c9167b58-RequestGraph +0 -0
- package/.parcel-cache/ecfe15d74834bbfd-BundleGraph +0 -0
- package/.parcel-cache/lock.mdb +0 -0
- package/.parcel-cache/snapshot-e43547b6c9167b58.txt +2 -0
- package/README.md +445 -0
- package/dist/browser/index.js +2456 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/index.d.ts +918 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2915 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2456 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +90 -0
- package/spec.md +257 -0
- package/src/index.ts +93 -0
- package/src/models/rostrum.entities.ts +159 -0
- package/src/models/transaction.entities.ts +46 -0
- package/src/models/wallet.entities.ts +42 -0
- package/src/network/RostrumProvider.ts +137 -0
- package/src/types.ts +0 -0
- package/src/utils/CommonUtils.ts +123 -0
- package/src/utils/TXUtils.ts +445 -0
- package/src/utils/TokenUtils.ts +75 -0
- package/src/utils/ValidationUtils.ts +86 -0
- package/src/utils/WalletUtils.ts +522 -0
- package/src/utils/WatchOnlyTXUtils.ts +275 -0
- package/src/wallet/Wallet.ts +397 -0
- package/src/wallet/WatchOnlyWallet.ts +169 -0
- package/src/wallet/accounts/AccountStore.ts +173 -0
- package/src/wallet/accounts/interfaces/BaseAccountInterface.ts +56 -0
- package/src/wallet/accounts/models/DappAccount.ts +80 -0
- package/src/wallet/accounts/models/DefaultAccount.ts +96 -0
- package/src/wallet/accounts/models/VaultAccount.ts +81 -0
- package/src/wallet/transactions/WalletTransactionCreator.ts +145 -0
- package/src/wallet/transactions/WatchOnlyTransactionCreator.ts +189 -0
- package/src/wallet/transactions/interfaces/TransactionCreator.ts +438 -0
- package/tests/core/tx/transactioncreator.test.ts +455 -0
- package/tests/core/tx/wallettransactioncreator.test.ts +362 -0
- package/tests/core/tx/watchonlytransactioncreator.test.ts +258 -0
- package/tests/core/wallet/accountstore.test.ts +341 -0
- package/tests/core/wallet/wallet.test.ts +69 -0
- package/tests/core/watchonlywallet/watchonlywallet.test.ts +251 -0
- package/tests/index.test.ts +12 -0
- 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
|
+
}
|