@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 +1 -1
- package/src/builders/BaseAssetTransactionBuilder.js +24 -6
- package/src/builders/FreezeAddressBuilder.js +7 -7
- package/src/builders/IssueQualifierBuilder.js +5 -5
- package/src/builders/IssueRestrictedBuilder.js +4 -4
- package/src/builders/IssueRootBuilder.js +4 -4
- package/src/builders/IssueSubBuilder.js +5 -5
- package/src/builders/IssueUniqueBuilder.js +5 -5
- package/src/builders/ReissueBuilder.js +10 -19
- package/src/builders/ReissueRestrictedBuilder.js +5 -5
- package/src/builders/TagAddressBuilder.js +5 -5
- package/src/constants/burnAddresses.js +17 -17
- package/src/constants/networks.js +4 -4
- package/src/managers/OutputOrderer.js +35 -43
- package/src/managers/OwnerTokenManager.js +9 -4
- package/src/managers/UTXOSelector.js +3 -5
package/package.json
CHANGED
|
@@ -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,
|
|
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
|
|
38
|
-
this.getAddresses =
|
|
39
|
-
} else if (Array.isArray(
|
|
40
|
-
this.getAddresses = () =>
|
|
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 =
|
|
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]
|
|
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
|
-
|
|
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]]
|
|
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]]
|
|
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]
|
|
197
|
+
outputs.push({ [changeAddress]: operationOutput });
|
|
198
198
|
break;
|
|
199
199
|
|
|
200
200
|
case 'UNFREEZE_ASSET':
|
|
201
201
|
operationOutput = OutputFormatter.formatUnfreezeAssetOutput(assetName);
|
|
202
|
-
outputs[changeAddress]
|
|
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]
|
|
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]
|
|
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
|
-
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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
|
-
|
|
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]
|
|
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]
|
|
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]
|
|
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
|
-
|
|
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]
|
|
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 +
|
|
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]
|
|
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]
|
|
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]
|
|
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.
|
|
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
|
-
//
|
|
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]
|
|
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]
|
|
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
|
-
|
|
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]
|
|
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]
|
|
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]
|
|
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
|
-
|
|
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]]
|
|
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: '
|
|
11
|
-
REISSUE: '
|
|
12
|
-
ISSUE_RESTRICTED: '
|
|
13
|
-
ISSUE_QUALIFIER: '
|
|
14
|
-
ISSUE_SUB_QUALIFIER: '
|
|
15
|
-
TAG_ADDRESS: '
|
|
16
|
-
UNTAG_ADDRESS: '
|
|
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: '
|
|
21
|
-
ISSUE_SUB: '
|
|
22
|
-
ISSUE_UNIQUE: '
|
|
23
|
-
ISSUE_MSGCHANNEL: '
|
|
24
|
-
REISSUE: '
|
|
25
|
-
ISSUE_RESTRICTED: '
|
|
26
|
-
ISSUE_QUALIFIER: '
|
|
27
|
-
ISSUE_SUB_QUALIFIER: '
|
|
28
|
-
TAG_ADDRESS: '
|
|
29
|
-
UNTAG_ADDRESS: '
|
|
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:
|
|
11
|
-
defaultRPCPort:
|
|
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:
|
|
19
|
-
defaultRPCPort:
|
|
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 {
|
|
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
|
-
|
|
25
|
-
|
|
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
|
|
29
|
-
const xnaOutputs = [];
|
|
30
|
-
const ownerOutputs = [];
|
|
31
|
-
const assetOutputs = [];
|
|
37
|
+
// Categorize
|
|
38
|
+
const xnaOutputs = [];
|
|
39
|
+
const ownerOutputs = [];
|
|
40
|
+
const assetOutputs = [];
|
|
32
41
|
|
|
33
|
-
for (const
|
|
42
|
+
for (const { address, value } of pairs) {
|
|
34
43
|
if (typeof value === 'number') {
|
|
35
|
-
|
|
36
|
-
xnaOutputs.push({ address, value, order: 1 });
|
|
44
|
+
xnaOutputs.push({ address, value });
|
|
37
45
|
} else if (typeof value === 'object') {
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
49
|
-
assetOutputs.push({ address, value, order: 3 });
|
|
49
|
+
assetOutputs.push({ address, value });
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 =
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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) {
|