@neuraiproject/neurai-assets 1.0.0 → 1.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neuraiproject/neurai-assets",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Non-custodial Neurai asset management library for JavaScript",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
@@ -24,27 +24,45 @@ class BaseAssetTransactionBuilder {
24
24
  * @param {string[]|Function} addresses - Wallet addresses or function that returns addresses
25
25
  * @param {object} params - Transaction parameters
26
26
  */
27
- constructor(rpc, network, addresses, params) {
27
+ constructor(rpc, networkOrParams, addresses, params) {
28
28
  if (!rpc || typeof rpc !== 'function') {
29
29
  throw new Error('RPC function is required');
30
30
  }
31
31
 
32
+ // Support both (rpc, network, addresses, params) and (rpc, params) calling forms.
33
+ // NeuraiAssets passes a single merged params object as the second argument.
34
+ let network, actualAddresses, actualParams;
35
+ if (
36
+ typeof networkOrParams === 'object' &&
37
+ networkOrParams !== null &&
38
+ !Array.isArray(networkOrParams) &&
39
+ addresses === undefined
40
+ ) {
41
+ network = networkOrParams.network;
42
+ actualAddresses = networkOrParams.addresses;
43
+ actualParams = networkOrParams;
44
+ } else {
45
+ network = networkOrParams;
46
+ actualAddresses = addresses;
47
+ actualParams = params;
48
+ }
49
+
32
50
  if (!network) {
33
51
  throw new Error('Network is required');
34
52
  }
35
53
 
36
54
  // Addresses can be an array or a function that returns addresses
37
- if (typeof addresses === 'function') {
38
- this.getAddresses = addresses;
39
- } else if (Array.isArray(addresses)) {
40
- this.getAddresses = () => addresses;
55
+ if (typeof actualAddresses === 'function') {
56
+ this.getAddresses = actualAddresses;
57
+ } else if (Array.isArray(actualAddresses)) {
58
+ this.getAddresses = () => actualAddresses;
41
59
  } else {
42
60
  throw new Error('Addresses must be an array or a function');
43
61
  }
44
62
 
45
63
  this.rpc = rpc;
46
64
  this.network = network;
47
- this.params = params || {};
65
+ this.params = actualParams || {};
48
66
 
49
67
  // Initialize managers
50
68
  this.burnManager = new BurnManager(network);
@@ -155,11 +155,11 @@ class FreezeAddressBuilder extends BaseAssetTransactionBuilder {
155
155
  });
156
156
 
157
157
  // 12. Build outputs (ORDER CRITICAL!)
158
- const outputs = {};
158
+ const outputs = [];
159
159
 
160
160
  // First: XNA change (if any)
161
161
  if (xnaChange > 0.00000001) {
162
- outputs[changeAddress] = parseFloat(xnaChange.toFixed(8));
162
+ outputs.push({ [changeAddress]: parseFloat(xnaChange.toFixed(8)) });
163
163
  }
164
164
 
165
165
  // Second: Owner token return (CRITICAL - must return or lost forever!)
@@ -167,7 +167,7 @@ class FreezeAddressBuilder extends BaseAssetTransactionBuilder {
167
167
  ownerTokenName,
168
168
  changeAddress
169
169
  );
170
- Object.assign(outputs, ownerTokenReturn);
170
+ outputs.push(ownerTokenReturn);
171
171
 
172
172
  // Last: Freeze/Unfreeze operation
173
173
  let operationOutput;
@@ -180,7 +180,7 @@ class FreezeAddressBuilder extends BaseAssetTransactionBuilder {
180
180
  asset_name: assetName,
181
181
  addresses: targetAddresses
182
182
  });
183
- outputs[targetAddresses[0]] = operationOutput;
183
+ outputs.push({ [targetAddresses[0]]: operationOutput });
184
184
  break;
185
185
 
186
186
  case 'UNFREEZE_ADDRESSES':
@@ -189,17 +189,17 @@ class FreezeAddressBuilder extends BaseAssetTransactionBuilder {
189
189
  asset_name: assetName,
190
190
  addresses: targetAddresses
191
191
  });
192
- outputs[targetAddresses[0]] = operationOutput;
192
+ outputs.push({ [targetAddresses[0]]: operationOutput });
193
193
  break;
194
194
 
195
195
  case 'FREEZE_ASSET':
196
196
  operationOutput = OutputFormatter.formatFreezeAssetOutput(assetName);
197
- outputs[changeAddress] = operationOutput;
197
+ outputs.push({ [changeAddress]: operationOutput });
198
198
  break;
199
199
 
200
200
  case 'UNFREEZE_ASSET':
201
201
  operationOutput = OutputFormatter.formatUnfreezeAssetOutput(assetName);
202
- outputs[changeAddress] = operationOutput;
202
+ outputs.push({ [changeAddress]: operationOutput });
203
203
  break;
204
204
 
205
205
  default:
@@ -184,14 +184,14 @@ class IssueQualifierBuilder extends BaseAssetTransactionBuilder {
184
184
  }
185
185
 
186
186
  // 14. Build outputs (ORDER CRITICAL!)
187
- const outputs = {};
187
+ const outputs = [];
188
188
 
189
189
  // First: Burn output
190
- outputs[burnInfo.address] = burnInfo.amount;
190
+ outputs.push({ [burnInfo.address]: burnInfo.amount });
191
191
 
192
192
  // Second: XNA change (if any)
193
193
  if (xnaChange > 0.00000001) {
194
- outputs[changeAddress] = parseFloat(xnaChange.toFixed(8));
194
+ outputs.push({ [changeAddress]: parseFloat(xnaChange.toFixed(8)) });
195
195
  }
196
196
 
197
197
  // Third: Owner token return (if sub-qualifier)
@@ -200,7 +200,7 @@ class IssueQualifierBuilder extends BaseAssetTransactionBuilder {
200
200
  ownerTokenName,
201
201
  changeAddress
202
202
  );
203
- Object.assign(outputs, ownerTokenReturn);
203
+ outputs.push(ownerTokenReturn);
204
204
  }
205
205
 
206
206
  // Last: Issue qualifier operation
@@ -211,7 +211,7 @@ class IssueQualifierBuilder extends BaseAssetTransactionBuilder {
211
211
  ipfs_hash: ipfsHash
212
212
  });
213
213
 
214
- outputs[toAddress] = issueQualifierOutput;
214
+ outputs.push({ [toAddress]: issueQualifierOutput });
215
215
 
216
216
  // 15. Order outputs (protocol requirement)
217
217
  const orderedOutputs = this.outputOrderer.order(outputs);
@@ -136,14 +136,14 @@ class IssueRestrictedBuilder extends BaseAssetTransactionBuilder {
136
136
  }));
137
137
 
138
138
  // 13. Build outputs (ORDER CRITICAL!)
139
- const outputs = {};
139
+ const outputs = [];
140
140
 
141
141
  // First: Burn output
142
- outputs[burnInfo.address] = burnInfo.amount;
142
+ outputs.push({ [burnInfo.address]: burnInfo.amount });
143
143
 
144
144
  // Second: XNA change (if any)
145
145
  if (xnaChange > 0.00000001) {
146
- outputs[changeAddress] = parseFloat(xnaChange.toFixed(8));
146
+ outputs.push({ [changeAddress]: parseFloat(xnaChange.toFixed(8)) });
147
147
  }
148
148
 
149
149
  // Last: Issue restricted operation
@@ -157,7 +157,7 @@ class IssueRestrictedBuilder extends BaseAssetTransactionBuilder {
157
157
  ipfs_hash: ipfsHash
158
158
  });
159
159
 
160
- outputs[toAddress] = issueRestrictedOutput;
160
+ outputs.push({ [toAddress]: issueRestrictedOutput });
161
161
 
162
162
  // 14. Order outputs (protocol requirement)
163
163
  const orderedOutputs = this.outputOrderer.order(outputs);
@@ -124,15 +124,15 @@ class IssueRootBuilder extends BaseAssetTransactionBuilder {
124
124
  }));
125
125
 
126
126
  // 12. Build outputs (ORDER MATTERS!)
127
- const outputs = {};
127
+ const outputs = [];
128
128
 
129
129
  // First: Burn output
130
- outputs[burnInfo.address] = burnInfo.amount;
130
+ outputs.push({ [burnInfo.address]: burnInfo.amount });
131
131
 
132
132
  // Second: XNA change (if any)
133
133
  if (xnaChange > 0.00000001) {
134
134
  // Only add change if meaningful amount
135
- outputs[changeAddress] = parseFloat(xnaChange.toFixed(8));
135
+ outputs.push({ [changeAddress]: parseFloat(xnaChange.toFixed(8)) });
136
136
  }
137
137
 
138
138
  // Last: Issue operation
@@ -145,7 +145,7 @@ class IssueRootBuilder extends BaseAssetTransactionBuilder {
145
145
  ipfs_hash: ipfsHash
146
146
  });
147
147
 
148
- outputs[toAddress] = issueOutput;
148
+ outputs.push({ [toAddress]: issueOutput });
149
149
 
150
150
  // 13. Order outputs (critical for protocol)
151
151
  const orderedOutputs = this.outputOrderer.order(outputs);
@@ -175,14 +175,14 @@ class IssueSubBuilder extends BaseAssetTransactionBuilder {
175
175
  });
176
176
 
177
177
  // 15. Build outputs (ORDER CRITICAL!)
178
- const outputs = {};
178
+ const outputs = [];
179
179
 
180
180
  // First: Burn output
181
- outputs[burnInfo.address] = burnInfo.amount;
181
+ outputs.push({ [burnInfo.address]: burnInfo.amount });
182
182
 
183
183
  // Second: XNA change (if any)
184
184
  if (xnaChange > 0.00000001) {
185
- outputs[changeAddress] = parseFloat(xnaChange.toFixed(8));
185
+ outputs.push({ [changeAddress]: parseFloat(xnaChange.toFixed(8)) });
186
186
  }
187
187
 
188
188
  // Third: Owner token return (CRITICAL - must return or lost forever!)
@@ -190,7 +190,7 @@ class IssueSubBuilder extends BaseAssetTransactionBuilder {
190
190
  ownerTokenName,
191
191
  changeAddress // Return owner token to change address
192
192
  );
193
- Object.assign(outputs, ownerTokenReturn);
193
+ outputs.push(ownerTokenReturn);
194
194
 
195
195
  // Last: Issue operation
196
196
  const issueOutput = OutputFormatter.formatIssueOutput({
@@ -202,7 +202,7 @@ class IssueSubBuilder extends BaseAssetTransactionBuilder {
202
202
  ipfs_hash: ipfsHash
203
203
  });
204
204
 
205
- outputs[toAddress] = issueOutput;
205
+ outputs.push({ [toAddress]: issueOutput });
206
206
 
207
207
  // 16. Order outputs (protocol requirement)
208
208
  const orderedOutputs = this.outputOrderer.order(outputs);
@@ -192,14 +192,14 @@ class IssueUniqueBuilder extends BaseAssetTransactionBuilder {
192
192
  });
193
193
 
194
194
  // 14. Build outputs (ORDER CRITICAL!)
195
- const outputs = {};
195
+ const outputs = [];
196
196
 
197
197
  // First: Burn output
198
- outputs[burnInfo.address] = burnInfo.amount;
198
+ outputs.push({ [burnInfo.address]: burnInfo.amount });
199
199
 
200
200
  // Second: XNA change (if any)
201
201
  if (xnaChange > 0.00000001) {
202
- outputs[changeAddress] = parseFloat(xnaChange.toFixed(8));
202
+ outputs.push({ [changeAddress]: parseFloat(xnaChange.toFixed(8)) });
203
203
  }
204
204
 
205
205
  // Third: Owner token return (CRITICAL - must return or lost forever!)
@@ -207,7 +207,7 @@ class IssueUniqueBuilder extends BaseAssetTransactionBuilder {
207
207
  ownerTokenName,
208
208
  changeAddress
209
209
  );
210
- Object.assign(outputs, ownerTokenReturn);
210
+ outputs.push(ownerTokenReturn);
211
211
 
212
212
  // Last: Issue unique operation
213
213
  const issueUniqueOutput = OutputFormatter.formatIssueUniqueOutput({
@@ -216,7 +216,7 @@ class IssueUniqueBuilder extends BaseAssetTransactionBuilder {
216
216
  ipfs_hashes: ipfsHashes.length > 0 ? ipfsHashes : undefined
217
217
  });
218
218
 
219
- outputs[toAddress] = issueUniqueOutput;
219
+ outputs.push({ [toAddress]: issueUniqueOutput });
220
220
 
221
221
  // 15. Order outputs (protocol requirement)
222
222
  const orderedOutputs = this.outputOrderer.order(outputs);
@@ -128,7 +128,8 @@ class ReissueBuilder extends BaseAssetTransactionBuilder {
128
128
 
129
129
  // 8. Estimate fee
130
130
  // Inputs: XNA UTXOs + owner token UTXO
131
- // Outputs: burn + change + owner token return + reissue operation
131
+ // Outputs: burn + change + reissue operation
132
+ // (node auto-generates owner token return from the reissue entry, total = 4 physical outputs)
132
133
  const estimatedFee = await this.estimateFee(2, 4);
133
134
 
134
135
  // 9. Calculate total XNA needed
@@ -181,26 +182,19 @@ class ReissueBuilder extends BaseAssetTransactionBuilder {
181
182
  });
182
183
 
183
184
  // 15. Build outputs (ORDER CRITICAL!)
184
- const outputs = {};
185
+ const outputs = [];
185
186
 
186
187
  // First: Burn output
187
- outputs[burnInfo.address] = burnInfo.amount;
188
+ outputs.push({ [burnInfo.address]: burnInfo.amount });
188
189
 
189
190
  // Second: XNA change (if any)
190
191
  if (xnaChange > 0.00000001) {
191
- outputs[changeAddress] = parseFloat(xnaChange.toFixed(8));
192
+ outputs.push({ [changeAddress]: parseFloat(xnaChange.toFixed(8)) });
192
193
  }
193
194
 
194
- // Third: Owner token return (CRITICAL - must return or lost forever!)
195
- // Support optional ownerChangeAddress for transferring ownership
196
- const ownerReturnAddress = this.params.ownerChangeAddress || changeAddress;
197
- const ownerTokenReturn = this.ownerTokenManager.createOwnerTokenReturnOutput(
198
- ownerTokenName,
199
- ownerReturnAddress
200
- );
201
- Object.assign(outputs, ownerTokenReturn);
202
-
203
195
  // Last: Reissue operation
196
+ // Note: the node auto-generates the owner token return as part of processing
197
+ // the reissue entry — no explicit transfer output needed here.
204
198
  const units = assetData.units || 0;
205
199
  const reissueOutput = OutputFormatter.formatReissueOutput({
206
200
  asset_name: assetName,
@@ -209,18 +203,15 @@ class ReissueBuilder extends BaseAssetTransactionBuilder {
209
203
  new_ipfs: newIpfs || undefined
210
204
  });
211
205
 
212
- outputs[toAddress] = reissueOutput;
206
+ outputs.push({ [toAddress]: reissueOutput });
213
207
 
214
208
  // 16. Order outputs (protocol requirement)
215
209
  const orderedOutputs = this.outputOrderer.order(outputs);
216
210
 
217
- // 17. Validate owner token is returned (safety check)
218
- this.ownerTokenManager.validateOwnerTokenReturn(inputs, orderedOutputs);
219
-
220
- // 18. Create raw transaction
211
+ // 17. Create raw transaction
221
212
  const rawTx = await this.buildRawTransaction(inputs, orderedOutputs);
222
213
 
223
- // 19. Format and return result
214
+ // 18. Format and return result
224
215
  const allUTXOs = [...baseCurrencyUTXOs, ownerTokenUTXO];
225
216
 
226
217
  return this.formatResult(
@@ -190,14 +190,14 @@ class ReissueRestrictedBuilder extends BaseAssetTransactionBuilder {
190
190
  });
191
191
 
192
192
  // 15. Build outputs (ORDER CRITICAL!)
193
- const outputs = {};
193
+ const outputs = [];
194
194
 
195
195
  // First: Burn output
196
- outputs[burnInfo.address] = burnInfo.amount;
196
+ outputs.push({ [burnInfo.address]: burnInfo.amount });
197
197
 
198
198
  // Second: XNA change (if any)
199
199
  if (xnaChange > 0.00000001) {
200
- outputs[changeAddress] = parseFloat(xnaChange.toFixed(8));
200
+ outputs.push({ [changeAddress]: parseFloat(xnaChange.toFixed(8)) });
201
201
  }
202
202
 
203
203
  // Third: Owner token return (CRITICAL - must return or lost forever!)
@@ -206,7 +206,7 @@ class ReissueRestrictedBuilder extends BaseAssetTransactionBuilder {
206
206
  ownerTokenName,
207
207
  ownerReturnAddress
208
208
  );
209
- Object.assign(outputs, ownerTokenReturn);
209
+ outputs.push(ownerTokenReturn);
210
210
 
211
211
  // Last: Reissue restricted operation
212
212
  const units = assetData.units || 0;
@@ -219,7 +219,7 @@ class ReissueRestrictedBuilder extends BaseAssetTransactionBuilder {
219
219
  new_ipfs: newIpfs || undefined
220
220
  });
221
221
 
222
- outputs[toAddress] = reissueRestrictedOutput;
222
+ outputs.push({ [toAddress]: reissueRestrictedOutput });
223
223
 
224
224
  // 16. Order outputs (protocol requirement)
225
225
  const orderedOutputs = this.outputOrderer.order(outputs);
@@ -162,14 +162,14 @@ class TagAddressBuilder extends BaseAssetTransactionBuilder {
162
162
  });
163
163
 
164
164
  // 13. Build outputs (ORDER CRITICAL!)
165
- const outputs = {};
165
+ const outputs = [];
166
166
 
167
167
  // First: Burn output
168
- outputs[burnInfo.address] = burnInfo.amount;
168
+ outputs.push({ [burnInfo.address]: burnInfo.amount });
169
169
 
170
170
  // Second: XNA change (if any)
171
171
  if (xnaChange > 0.00000001) {
172
- outputs[changeAddress] = parseFloat(xnaChange.toFixed(8));
172
+ outputs.push({ [changeAddress]: parseFloat(xnaChange.toFixed(8)) });
173
173
  }
174
174
 
175
175
  // Third: Owner token return (CRITICAL - must return or lost forever!)
@@ -177,7 +177,7 @@ class TagAddressBuilder extends BaseAssetTransactionBuilder {
177
177
  ownerTokenName,
178
178
  changeAddress
179
179
  );
180
- Object.assign(outputs, ownerTokenReturn);
180
+ outputs.push(ownerTokenReturn);
181
181
 
182
182
  // Last: Tag/Untag operation
183
183
  // Note: Using first address as transaction output address (protocol requirement)
@@ -192,7 +192,7 @@ class TagAddressBuilder extends BaseAssetTransactionBuilder {
192
192
  asset_data: assetData
193
193
  });
194
194
 
195
- outputs[targetAddresses[0]] = operationOutput;
195
+ outputs.push({ [targetAddresses[0]]: operationOutput });
196
196
 
197
197
  // 14. Order outputs (protocol requirement)
198
198
  const orderedOutputs = this.outputOrderer.order(outputs);
@@ -7,26 +7,26 @@ const MAINNET_BURN_ADDRESSES = {
7
7
  ISSUE_ROOT: 'NbURNXXXXXXXXXXXXXXXXXXXXXXXT65Gdr',
8
8
  ISSUE_SUB: 'NXissueSubAssetXXXXXXXXXXXXXX6B2JF',
9
9
  ISSUE_UNIQUE: 'NXissueUniqueAssetXXXXXXXXXXUBzP4Z',
10
- ISSUE_MSGCHANNEL: 'NXissueMsgChanneLAssetXXXXXXVeF2mU',
11
- REISSUE: 'NXReissueAssetXXXXXXXXXXXXXXVEFAWu',
12
- ISSUE_RESTRICTED: 'NXissueRestrictedXXXXXXXXXXXZaQtSE',
13
- ISSUE_QUALIFIER: 'NXissueQualifierAssetXXXXXXXXWPHpDX',
14
- ISSUE_SUB_QUALIFIER: 'NXissueSubQualifierAssetXXXXXYWcFp8',
15
- TAG_ADDRESS: 'NXaddTagBurnXXXXXXXXXXXXXXXXUBL1a5',
16
- UNTAG_ADDRESS: 'NXaddTagBurnXXXXXXXXXXXXXXXXUBL1a5'
10
+ ISSUE_MSGCHANNEL: 'NXissueMsgChanneLAssetXXXXXXTUzrtJ',
11
+ REISSUE: 'NXReissueAssetXXXXXXXXXXXXXXWLe4Ao',
12
+ ISSUE_RESTRICTED: 'NXissueRestrictedXXXXXXXXXXXWpXx4H',
13
+ ISSUE_QUALIFIER: 'NXissueQuaLifierXXXXXXXXXXXXWurNcU',
14
+ ISSUE_SUB_QUALIFIER: 'NXissueSubQuaLifierXXXXXXXXXV71vM3',
15
+ TAG_ADDRESS: 'NXaddTagBurnXXXXXXXXXXXXXXXXWucUTr',
16
+ UNTAG_ADDRESS: 'NXaddTagBurnXXXXXXXXXXXXXXXXWucUTr'
17
17
  };
18
18
 
19
19
  const TESTNET_BURN_ADDRESSES = {
20
- ISSUE_ROOT: 'mbURNXXXXXXXXXXXXXXXXXXXXXXUV9F72',
21
- ISSUE_SUB: 'mXissueSubAssetXXXXXXXXXXXXXUDJP6N',
22
- ISSUE_UNIQUE: 'mXissueUniqueAssetXXXXXXXXXXQV8W2V',
23
- ISSUE_MSGCHANNEL: 'mXissueMsgChanneLAssetXXXXXXX9fhPh',
24
- REISSUE: 'mXReissueAssetXXXXXXXXXXXXXXTZzJ3c',
25
- ISSUE_RESTRICTED: 'mXissueRestrictedXXXXXXXXXXXXYHqJY',
26
- ISSUE_QUALIFIER: 'mXissueQualifierAssetXXXXXXXXZhqiGs',
27
- ISSUE_SUB_QUALIFIER: 'mXissueSubQualifierAssetXXXXXab7bRN',
28
- TAG_ADDRESS: 'mXaddTagBurnXXXXXXXXXXXXXXXXWEKFqa',
29
- UNTAG_ADDRESS: 'mXaddTagBurnXXXXXXXXXXXXXXXXWEKFqa'
20
+ ISSUE_ROOT: 'tBURNXXXXXXXXXXXXXXXXXXXXXXXVZLroy',
21
+ ISSUE_SUB: 'tSubAssetXXXXXXXXXXXXXXXXXXXXGTvF4',
22
+ ISSUE_UNIQUE: 'tUniqueAssetXXXXXXXXXXXXXXXXVCgpLs',
23
+ ISSUE_MSGCHANNEL: 'tMsgChanneLAssetXXXXXXXXXXXXVsJoya',
24
+ REISSUE: 'tAssetXXXXXXXXXXXXXXXXXXXXXXas6pz8',
25
+ ISSUE_RESTRICTED: 'tRestrictedXXXXXXXXXXXXXXXXXVyPBEK',
26
+ ISSUE_QUALIFIER: 'tQuaLifierXXXXXXXXXXXXXXXXXXT5czoV',
27
+ ISSUE_SUB_QUALIFIER: 'tSubQuaLifierXXXXXXXXXXXXXXXW5MmGk',
28
+ TAG_ADDRESS: 'tTagBurnXXXXXXXXXXXXXXXXXXXXYm6pxA',
29
+ UNTAG_ADDRESS: 'tTagBurnXXXXXXXXXXXXXXXXXXXXYm6pxA'
30
30
  };
31
31
 
32
32
  /**
@@ -7,16 +7,16 @@ const NETWORKS = {
7
7
  name: 'xna',
8
8
  displayName: 'Neurai Mainnet',
9
9
  addressPrefix: 'N',
10
- assetNameMaxLength: 31,
11
- defaultRPCPort: 8766,
10
+ assetNameMaxLength: 32,
11
+ defaultRPCPort: 19001,
12
12
  coin: 'XNA'
13
13
  },
14
14
  TESTNET: {
15
15
  name: 'xna-test',
16
16
  displayName: 'Neurai Testnet',
17
17
  addressPrefix: 'm',
18
- assetNameMaxLength: 31, // Same as mainnet
19
- defaultRPCPort: 18766,
18
+ assetNameMaxLength: 32, // Same as mainnet
19
+ defaultRPCPort: 19101,
20
20
  coin: 'TXNA'
21
21
  }
22
22
  };
@@ -17,59 +17,46 @@ const { AssetNameParser } = require('../utils');
17
17
  class OutputOrderer {
18
18
  /**
19
19
  * Order outputs according to protocol requirements
20
- * @param {object} outputs - Unordered outputs object
21
- * @returns {object} Ordered outputs object
20
+ * @param {object|Array} outputs - Unordered outputs object or array of single-key objects
21
+ * @returns {Array} Ordered array of single-key output objects
22
22
  */
23
23
  order(outputs) {
24
- if (!outputs || typeof outputs !== 'object') {
25
- throw new Error('Outputs must be an object');
24
+ // Normalize to array of {address, value} pairs
25
+ let pairs;
26
+ if (Array.isArray(outputs)) {
27
+ pairs = outputs.map(obj => {
28
+ const [address, value] = Object.entries(obj)[0];
29
+ return { address, value };
30
+ });
31
+ } else if (outputs && typeof outputs === 'object') {
32
+ pairs = Object.entries(outputs).map(([address, value]) => ({ address, value }));
33
+ } else {
34
+ throw new Error('Outputs must be an object or array');
26
35
  }
27
36
 
28
- // Categorize outputs
29
- const xnaOutputs = []; // XNA amounts (burn + change)
30
- const ownerOutputs = []; // Owner token transfers
31
- const assetOutputs = []; // Asset operations and transfers
37
+ // Categorize
38
+ const xnaOutputs = [];
39
+ const ownerOutputs = [];
40
+ const assetOutputs = [];
32
41
 
33
- for (const [address, value] of Object.entries(outputs)) {
42
+ for (const { address, value } of pairs) {
34
43
  if (typeof value === 'number') {
35
- // XNA output (numeric value)
36
- xnaOutputs.push({ address, value, order: 1 });
44
+ xnaOutputs.push({ address, value });
37
45
  } else if (typeof value === 'object') {
38
- // Complex output (transfer or operation)
39
- if (value.transfer) {
40
- // Check if it's an owner token transfer
41
- if (this.isOwnerTokenTransfer(value.transfer)) {
42
- ownerOutputs.push({ address, value, order: 2 });
43
- } else {
44
- // Regular asset transfer
45
- assetOutputs.push({ address, value, order: 3 });
46
- }
46
+ if (value.transfer && this.isOwnerTokenTransfer(value.transfer)) {
47
+ ownerOutputs.push({ address, value });
47
48
  } else {
48
- // Asset operation (issue, reissue, etc.)
49
- assetOutputs.push({ address, value, order: 3 });
49
+ assetOutputs.push({ address, value });
50
50
  }
51
51
  }
52
52
  }
53
53
 
54
- // Build ordered output object
55
- const orderedOutputs = {};
56
-
57
- // 1. Add XNA outputs first
58
- xnaOutputs.forEach(({ address, value }) => {
59
- orderedOutputs[address] = value;
60
- });
61
-
62
- // 2. Add owner token outputs second
63
- ownerOutputs.forEach(({ address, value }) => {
64
- orderedOutputs[address] = value;
65
- });
66
-
67
- // 3. Add asset operations/transfers last
68
- assetOutputs.forEach(({ address, value }) => {
69
- orderedOutputs[address] = value;
70
- });
71
-
72
- return orderedOutputs;
54
+ // Return ordered array of single-key objects
55
+ return [
56
+ ...xnaOutputs.map(({ address, value }) => ({ [address]: value })),
57
+ ...ownerOutputs.map(({ address, value }) => ({ [address]: value })),
58
+ ...assetOutputs.map(({ address, value }) => ({ [address]: value })),
59
+ ];
73
60
  }
74
61
 
75
62
  /**
@@ -97,7 +84,9 @@ class OutputOrderer {
97
84
  * @throws {Error} If ordering is invalid
98
85
  */
99
86
  validateOrdering(outputs) {
100
- const entries = Object.entries(outputs);
87
+ const entries = Array.isArray(outputs)
88
+ ? outputs.map(obj => Object.entries(obj)[0])
89
+ : Object.entries(outputs);
101
90
  let currentCategory = 0; // 0 = not started, 1 = XNA, 2 = owner, 3 = assets
102
91
 
103
92
  for (const [address, value] of entries) {
@@ -175,8 +164,11 @@ class OutputOrderer {
175
164
  */
176
165
  debugOrdering(outputs) {
177
166
  const debug = [];
167
+ const entries = Array.isArray(outputs)
168
+ ? outputs.map(obj => Object.entries(obj)[0])
169
+ : Object.entries(outputs);
178
170
 
179
- for (const [address, value] of Object.entries(outputs)) {
171
+ for (const [address, value] of entries) {
180
172
  const category = this.getOutputCategory(value);
181
173
  let order;
182
174
 
@@ -46,8 +46,8 @@ class OwnerTokenManager {
46
46
  }
47
47
 
48
48
  try {
49
- // Query UTXOs for the owner token
50
- const utxos = await this.rpc('getaddressutxos', [{ addresses }]);
49
+ // Request UTXOs for the specific owner token via assetName param
50
+ const utxos = await this.rpc('getaddressutxos', [{ addresses, assetName: ownerTokenName }]);
51
51
 
52
52
  // Filter for the specific owner token
53
53
  const ownerTokenUTXOs = utxos.filter(utxo => utxo.assetName === ownerTokenName);
@@ -133,12 +133,16 @@ class OwnerTokenManager {
133
133
  }
134
134
 
135
135
  // Check each owner token is in outputs
136
+ const outputEntries = Array.isArray(outputs)
137
+ ? outputs.map(obj => Object.entries(obj)[0])
138
+ : Object.entries(outputs);
139
+
136
140
  for (const ownerInput of ownerTokenInputs) {
137
141
  const ownerTokenName = ownerInput.assetName;
138
142
  let foundInOutputs = false;
139
143
 
140
144
  // Check all outputs for owner token
141
- for (const [address, output] of Object.entries(outputs)) {
145
+ for (const [address, output] of outputEntries) {
142
146
  // Check if output has transfer field
143
147
  if (output && typeof output === 'object' && output.transfer) {
144
148
  // Check if owner token is in the transfer
@@ -192,7 +196,8 @@ class OwnerTokenManager {
192
196
  }
193
197
 
194
198
  try {
195
- const utxos = await this.rpc('getaddressutxos', [{ addresses }]);
199
+ // Request all asset UTXOs with assetName='*'
200
+ const utxos = await this.rpc('getaddressutxos', [{ addresses, assetName: '*' }]);
196
201
 
197
202
  // Filter for owner tokens (asset names ending with !)
198
203
  const ownerTokenUTXOs = utxos.filter(utxo => {
@@ -33,11 +33,9 @@ class UTXOSelector {
33
33
  }
34
34
 
35
35
  try {
36
- const params = assetName
37
- ? [{ addresses, assetName }]
38
- : [{ addresses }];
39
-
40
- const utxos = await this.rpc('getaddressutxos', params);
36
+ // assetName goes inside the params object; omit it for XNA UTXOs
37
+ const queryParams = assetName ? { addresses, assetName } : { addresses };
38
+ const utxos = await this.rpc('getaddressutxos', [queryParams]);
41
39
 
42
40
  // Filter for specific asset or XNA
43
41
  if (assetName) {