@neuraiproject/neurai-assets 1.2.2 → 1.2.5

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/dist/index.js CHANGED
@@ -1037,11 +1037,15 @@ var hasRequiredNetworks;
1037
1037
  function requireNetworks () {
1038
1038
  if (hasRequiredNetworks) return networks;
1039
1039
  hasRequiredNetworks = 1;
1040
+ const MAINNET_NETWORKS = ['xna', 'mainnet', 'xna-pq', 'mainnet-pq'];
1041
+ const TESTNET_NETWORKS = ['xna-test', 'testnet', 'regtest', 'xna-pq-test', 'testnet-pq'];
1042
+
1040
1043
  const NETWORKS = {
1041
1044
  MAINNET: {
1042
1045
  name: 'xna',
1043
1046
  displayName: 'Neurai Mainnet',
1044
1047
  addressPrefix: 'N',
1048
+ authScriptAddressPrefix: 'nq1',
1045
1049
  pqAddressPrefix: 'nq1',
1046
1050
  assetNameMaxLength: 32,
1047
1051
  defaultRPCPort: 19001,
@@ -1052,6 +1056,7 @@ function requireNetworks () {
1052
1056
  name: 'xna-test',
1053
1057
  displayName: 'Neurai Testnet',
1054
1058
  addressPrefix: 't',
1059
+ authScriptAddressPrefix: 'tnq1',
1055
1060
  pqAddressPrefix: 'tnq1',
1056
1061
  assetNameMaxLength: 32, // Same as mainnet
1057
1062
  defaultRPCPort: 19101,
@@ -1060,8 +1065,9 @@ function requireNetworks () {
1060
1065
  },
1061
1066
  MAINNET_PQ: {
1062
1067
  name: 'xna-pq',
1063
- displayName: 'Neurai Mainnet PQ',
1068
+ displayName: 'Neurai Mainnet AuthScript',
1064
1069
  addressPrefix: 'N',
1070
+ authScriptAddressPrefix: 'nq1',
1065
1071
  pqAddressPrefix: 'nq1',
1066
1072
  assetNameMaxLength: 32,
1067
1073
  defaultRPCPort: 19001,
@@ -1070,8 +1076,9 @@ function requireNetworks () {
1070
1076
  },
1071
1077
  TESTNET_PQ: {
1072
1078
  name: 'xna-pq-test',
1073
- displayName: 'Neurai Testnet PQ',
1079
+ displayName: 'Neurai Testnet AuthScript',
1074
1080
  addressPrefix: 't',
1081
+ authScriptAddressPrefix: 'tnq1',
1075
1082
  pqAddressPrefix: 'tnq1',
1076
1083
  assetNameMaxLength: 32,
1077
1084
  defaultRPCPort: 19101,
@@ -1081,45 +1088,45 @@ function requireNetworks () {
1081
1088
  };
1082
1089
 
1083
1090
  /**
1084
- * Asset naming rules (same for all networks)
1091
+ * Asset naming helpers.
1092
+ * Network-specific maximum lengths are enforced in AssetNameValidator.
1085
1093
  */
1086
1094
  const ASSET_NAME_RULES = {
1087
1095
  ROOT: {
1088
1096
  minLength: 3,
1089
- maxLength: 30,
1097
+ maxLength: 31,
1090
1098
  pattern: /^[A-Z0-9_.]+$/,
1091
- cannotStartWith: ['.', 'A', 'Z'],
1092
- reserved: ['XNA', 'NEURAI']
1099
+ reserved: ['XNA', 'NEURAI', 'NEURAICOIN']
1093
1100
  },
1094
1101
  SUB: {
1095
1102
  minLength: 1,
1096
- maxLength: 30,
1103
+ maxLength: 31,
1097
1104
  pattern: /^[A-Z0-9_.]+$/,
1098
1105
  separator: '/',
1099
- maxDepth: 1 // Only one level of sub-assets
1106
+ maxDepth: null
1100
1107
  },
1101
1108
  UNIQUE: {
1102
1109
  minLength: 1,
1103
- maxLength: 30,
1104
- pattern: /^[A-Z0-9_.]+$/,
1110
+ maxLength: 32,
1111
+ pattern: /^[-A-Za-z0-9@$%&*()[\]{}_.?:]+$/,
1105
1112
  separator: '#'
1106
1113
  },
1107
1114
  QUALIFIER: {
1108
1115
  minLength: 3,
1109
- maxLength: 30,
1110
- pattern: /^[A-Z0-9_]+$/,
1116
+ maxLength: 32,
1117
+ pattern: /^[A-Z0-9_.]+$/,
1111
1118
  prefix: '#',
1112
1119
  separator: '/'
1113
1120
  },
1114
1121
  RESTRICTED: {
1115
1122
  minLength: 3,
1116
- maxLength: 30,
1123
+ maxLength: 32,
1117
1124
  pattern: /^[A-Z0-9_.]+$/,
1118
1125
  prefix: '$'
1119
1126
  },
1120
1127
  DEPIN: {
1121
1128
  minLength: 3,
1122
- maxLength: 120,
1129
+ maxLength: 121,
1123
1130
  pattern: /^[A-Z0-9_.]+$/,
1124
1131
  prefix: '&',
1125
1132
  separator: '/'
@@ -1141,32 +1148,68 @@ function requireNetworks () {
1141
1148
 
1142
1149
  /**
1143
1150
  * Get network configuration
1151
+ * `xna-pq` / `xna-pq-test` are preserved as compatibility aliases for
1152
+ * AuthScript address flows on the same mainnet/testnet families.
1153
+ *
1144
1154
  * @param {string} networkName - Network name ('xna', 'xna-test', 'xna-pq', or 'xna-pq-test')
1145
1155
  * @returns {object} Network configuration
1146
1156
  */
1147
1157
  function getNetworkConfig(networkName) {
1148
- if (networkName === 'xna' || networkName === 'mainnet') {
1149
- return NETWORKS.MAINNET;
1150
- } else if (networkName === 'xna-test' || networkName === 'testnet') {
1151
- return NETWORKS.TESTNET;
1152
- } else if (networkName === 'xna-pq' || networkName === 'mainnet-pq') {
1153
- return NETWORKS.MAINNET_PQ;
1154
- } else if (networkName === 'xna-pq-test' || networkName === 'testnet-pq') {
1155
- return NETWORKS.TESTNET_PQ;
1158
+ if (MAINNET_NETWORKS.includes(networkName)) {
1159
+ return networkName === 'xna-pq' || networkName === 'mainnet-pq'
1160
+ ? NETWORKS.MAINNET_PQ
1161
+ : NETWORKS.MAINNET;
1162
+ } else if (TESTNET_NETWORKS.includes(networkName)) {
1163
+ return networkName === 'xna-pq-test' || networkName === 'testnet-pq'
1164
+ ? NETWORKS.TESTNET_PQ
1165
+ : NETWORKS.TESTNET;
1156
1166
  } else {
1157
1167
  throw new Error(`Unknown network: ${networkName}`);
1158
1168
  }
1159
1169
  }
1160
1170
 
1161
1171
  /**
1162
- * Detect network from address prefix
1172
+ * Resolve a network name to its chain family.
1173
+ * AuthScript aliases share the same family as legacy addresses.
1174
+ *
1175
+ * @param {string} networkName - Network name
1176
+ * @returns {'mainnet'|'testnet'} Network family
1177
+ */
1178
+ function resolveAddressNetworkFamily(networkName) {
1179
+ if (MAINNET_NETWORKS.includes(networkName)) {
1180
+ return 'mainnet';
1181
+ }
1182
+
1183
+ if (TESTNET_NETWORKS.includes(networkName)) {
1184
+ return 'testnet';
1185
+ }
1186
+
1187
+ throw new Error(`Unknown network: ${networkName}`);
1188
+ }
1189
+
1190
+ /**
1191
+ * Determine whether two network labels are compatible for address use.
1192
+ * This treats legacy and AuthScript labels on the same chain as compatible.
1193
+ *
1194
+ * @param {string} left - First network name
1195
+ * @param {string} right - Second network name
1196
+ * @returns {boolean} True if both belong to the same chain family
1197
+ */
1198
+ function areAddressNetworksCompatible(left, right) {
1199
+ return resolveAddressNetworkFamily(left) === resolveAddressNetworkFamily(right);
1200
+ }
1201
+
1202
+ /**
1203
+ * Detect network from address prefix.
1204
+ * `nq1...` / `tnq1...` are AuthScript witness-v1 destinations.
1205
+ *
1163
1206
  * @param {string} address - Neurai address
1164
1207
  * @returns {string} Network name ('xna', 'xna-test', 'xna-pq', or 'xna-pq-test')
1165
1208
  */
1166
1209
  function detectNetworkFromAddress(address) {
1167
- if (address.startsWith(NETWORKS.MAINNET_PQ.pqAddressPrefix)) {
1210
+ if (address.startsWith(NETWORKS.MAINNET_PQ.authScriptAddressPrefix)) {
1168
1211
  return 'xna-pq';
1169
- } else if (address.startsWith(NETWORKS.TESTNET_PQ.pqAddressPrefix)) {
1212
+ } else if (address.startsWith(NETWORKS.TESTNET_PQ.authScriptAddressPrefix)) {
1170
1213
  return 'xna-pq-test';
1171
1214
  } else if (address.startsWith('N')) {
1172
1215
  return 'xna';
@@ -1179,9 +1222,13 @@ function requireNetworks () {
1179
1222
 
1180
1223
  networks = {
1181
1224
  NETWORKS,
1225
+ MAINNET_NETWORKS,
1226
+ TESTNET_NETWORKS,
1182
1227
  ASSET_NAME_RULES,
1183
1228
  ASSET_LIMITS,
1184
1229
  getNetworkConfig,
1230
+ resolveAddressNetworkFamily,
1231
+ areAddressNetworksCompatible,
1185
1232
  detectNetworkFromAddress
1186
1233
  };
1187
1234
  return networks;
@@ -1209,9 +1256,13 @@ function requireConstants () {
1209
1256
  } = requireBurnAddresses();
1210
1257
  const {
1211
1258
  NETWORKS,
1259
+ MAINNET_NETWORKS,
1260
+ TESTNET_NETWORKS,
1212
1261
  ASSET_NAME_RULES,
1213
1262
  ASSET_LIMITS,
1214
1263
  getNetworkConfig,
1264
+ resolveAddressNetworkFamily,
1265
+ areAddressNetworksCompatible,
1215
1266
  detectNetworkFromAddress
1216
1267
  } = requireNetworks();
1217
1268
 
@@ -1234,9 +1285,13 @@ function requireConstants () {
1234
1285
 
1235
1286
  // Networks
1236
1287
  NETWORKS,
1288
+ MAINNET_NETWORKS,
1289
+ TESTNET_NETWORKS,
1237
1290
  ASSET_NAME_RULES,
1238
1291
  ASSET_LIMITS,
1239
1292
  getNetworkConfig,
1293
+ resolveAddressNetworkFamily,
1294
+ areAddressNetworksCompatible,
1240
1295
  detectNetworkFromAddress
1241
1296
  };
1242
1297
  return constants$1;
@@ -1853,7 +1908,11 @@ var hasRequiredNetworkDetector;
1853
1908
  function requireNetworkDetector () {
1854
1909
  if (hasRequiredNetworkDetector) return networkDetector;
1855
1910
  hasRequiredNetworkDetector = 1;
1856
- const { NETWORKS } = requireConstants();
1911
+ const {
1912
+ NETWORKS,
1913
+ areAddressNetworksCompatible,
1914
+ resolveAddressNetworkFamily
1915
+ } = requireConstants();
1857
1916
 
1858
1917
  class NetworkDetector {
1859
1918
  /**
@@ -1890,18 +1949,18 @@ function requireNetworkDetector () {
1890
1949
  /**
1891
1950
  * Detect network from address
1892
1951
  * @param {string} address - Neurai address
1893
- * @returns {string} Network name ('xna', 'xna-test', 'xna-pq', or 'xna-pq-test')
1952
+ * @returns {string} Network label ('xna', 'xna-test', 'xna-pq', or 'xna-pq-test')
1894
1953
  */
1895
1954
  static detectFromAddress(address) {
1896
1955
  if (!address || typeof address !== 'string') {
1897
1956
  throw new Error('Address must be a non-empty string');
1898
1957
  }
1899
1958
 
1900
- if (address.startsWith(NETWORKS.MAINNET_PQ.pqAddressPrefix)) {
1959
+ if (address.startsWith(NETWORKS.MAINNET_PQ.authScriptAddressPrefix)) {
1901
1960
  return 'xna-pq';
1902
1961
  }
1903
1962
 
1904
- if (address.startsWith(NETWORKS.TESTNET_PQ.pqAddressPrefix)) {
1963
+ if (address.startsWith(NETWORKS.TESTNET_PQ.authScriptAddressPrefix)) {
1905
1964
  return 'xna-pq-test';
1906
1965
  }
1907
1966
 
@@ -1921,31 +1980,32 @@ function requireNetworkDetector () {
1921
1980
  /**
1922
1981
  * Detect network from multiple addresses
1923
1982
  * @param {string[]} addresses - Array of addresses
1924
- * @returns {string} Network name ('xna', 'xna-test', 'xna-pq', or 'xna-pq-test')
1983
+ * @returns {string} Network label. Mixed legacy/AuthScript addresses on the same
1984
+ * chain are normalized to the chain family label (`xna` or `xna-test`).
1925
1985
  */
1926
1986
  static detectFromAddresses(addresses) {
1927
1987
  if (!Array.isArray(addresses) || addresses.length === 0) {
1928
1988
  throw new Error('Addresses must be a non-empty array');
1929
1989
  }
1930
1990
 
1931
- // Detect from first address
1932
- const network = this.detectFromAddress(addresses[0]);
1991
+ const firstNetwork = this.detectFromAddress(addresses[0]);
1992
+ const family = resolveAddressNetworkFamily(firstNetwork);
1933
1993
 
1934
1994
  // Verify all addresses are from the same network
1935
1995
  for (let i = 1; i < addresses.length; i++) {
1936
1996
  const otherNetwork = this.detectFromAddress(addresses[i]);
1937
- if (otherNetwork !== network) {
1938
- throw new Error(`Mixed network addresses detected: ${network} and ${otherNetwork}`);
1997
+ if (!areAddressNetworksCompatible(firstNetwork, otherNetwork)) {
1998
+ throw new Error(`Mixed network addresses detected: ${firstNetwork} and ${otherNetwork}`);
1939
1999
  }
1940
2000
  }
1941
2001
 
1942
- return network;
2002
+ return family === 'mainnet' ? 'xna' : 'xna-test';
1943
2003
  }
1944
2004
 
1945
2005
  /**
1946
2006
  * Validate that addresses match expected network
1947
2007
  * @param {string[]} addresses - Array of addresses
1948
- * @param {string} expectedNetwork - Expected network ('xna', 'xna-test', 'xna-pq', or 'xna-pq-test')
2008
+ * @param {string} expectedNetwork - Expected network label
1949
2009
  * @returns {boolean} True if all addresses match network
1950
2010
  */
1951
2011
  static validateAddressesNetwork(addresses, expectedNetwork) {
@@ -1955,7 +2015,7 @@ function requireNetworkDetector () {
1955
2015
 
1956
2016
  for (const address of addresses) {
1957
2017
  const network = this.detectFromAddress(address);
1958
- if (network !== expectedNetwork) {
2018
+ if (!areAddressNetworksCompatible(network, expectedNetwork)) {
1959
2019
  throw new Error(
1960
2020
  `Address ${address} is from ${network} but expected ${expectedNetwork}`
1961
2021
  );
@@ -3307,52 +3367,164 @@ function requireAssetNameValidator () {
3307
3367
  const { ASSET_NAME_RULES } = requireConstants();
3308
3368
  const { InvalidAssetNameError } = requireErrors();
3309
3369
 
3370
+ const MIN_ASSET_LENGTH = 3;
3371
+ const MAINNET_MAX_NAME_LENGTH = 32;
3372
+ const TESTNET_MAX_NAME_LENGTH = 121;
3373
+
3374
+ const ROOT_NAME_CHARACTERS = /^[A-Z0-9._]{3,}$/;
3375
+ const SUB_NAME_CHARACTERS = /^[A-Z0-9._]+$/;
3376
+ const UNIQUE_TAG_CHARACTERS = /^[-A-Za-z0-9@$%&*()[\]{}_.?:]+$/;
3377
+ const QUALIFIER_NAME_CHARACTERS = /^#[A-Z0-9._]{3,}$/;
3378
+ const SUB_QUALIFIER_NAME_CHARACTERS = /^#[A-Z0-9._]+$/;
3379
+ const RESTRICTED_NAME_CHARACTERS = /^\$[A-Z0-9._]{3,}$/;
3380
+ const DEPIN_NAME_CHARACTERS = /^&[A-Z0-9._]{3,}$/;
3381
+ const SUB_DEPIN_NAME_CHARACTERS = /^&[A-Z0-9._]+\/[A-Z0-9._/]+$/;
3382
+ const DOUBLE_PUNCTUATION = /^.*[._]{2,}.*$/;
3383
+ const LEADING_PUNCTUATION = /^[._].*$/;
3384
+ const TRAILING_PUNCTUATION = /^.*[._]$/;
3385
+ const QUALIFIER_LEADING_PUNCTUATION = /^[#$][._].*$/;
3386
+ const NEURAI_NAMES = /^XNA$|^NEURAI$|^NEURAICOIN$|^#XNA$|^#NEURAI$|^#NEURAICOIN$/;
3387
+
3388
+ function isTestnet(network) {
3389
+ return typeof network === 'string' && network.toLowerCase().includes('test');
3390
+ }
3391
+
3392
+ function getMaxAssetNameLength(network) {
3393
+ return isTestnet(network) ? TESTNET_MAX_NAME_LENGTH : MAINNET_MAX_NAME_LENGTH;
3394
+ }
3395
+
3396
+ function getRootOrSubMaxLength(network) {
3397
+ return getMaxAssetNameLength(network) - 1;
3398
+ }
3399
+
3400
+ function ensureName(name, label) {
3401
+ if (!name || typeof name !== 'string') {
3402
+ throw new InvalidAssetNameError(`${label} must be a non-empty string`, name);
3403
+ }
3404
+ }
3405
+
3406
+ function ensureMaxLength(name, maxLength, label) {
3407
+ if (name.length > maxLength) {
3408
+ throw new InvalidAssetNameError(`${label} cannot exceed ${maxLength} characters`, name);
3409
+ }
3410
+ }
3411
+
3412
+ function ensureUppercase(name, message) {
3413
+ if (name !== name.toUpperCase()) {
3414
+ throw new InvalidAssetNameError(message, name);
3415
+ }
3416
+ }
3417
+
3418
+ function isRootNameValid(name) {
3419
+ return ROOT_NAME_CHARACTERS.test(name)
3420
+ && !DOUBLE_PUNCTUATION.test(name)
3421
+ && !LEADING_PUNCTUATION.test(name)
3422
+ && !TRAILING_PUNCTUATION.test(name)
3423
+ && !NEURAI_NAMES.test(name);
3424
+ }
3425
+
3426
+ function isSubNameValid(name) {
3427
+ return SUB_NAME_CHARACTERS.test(name)
3428
+ && !DOUBLE_PUNCTUATION.test(name)
3429
+ && !LEADING_PUNCTUATION.test(name)
3430
+ && !TRAILING_PUNCTUATION.test(name);
3431
+ }
3432
+
3433
+ function isQualifierNameValid(name) {
3434
+ return QUALIFIER_NAME_CHARACTERS.test(name)
3435
+ && !DOUBLE_PUNCTUATION.test(name)
3436
+ && !QUALIFIER_LEADING_PUNCTUATION.test(name)
3437
+ && !TRAILING_PUNCTUATION.test(name)
3438
+ && !NEURAI_NAMES.test(name);
3439
+ }
3440
+
3441
+ function isSubQualifierNameValid(name) {
3442
+ return SUB_QUALIFIER_NAME_CHARACTERS.test(name)
3443
+ && !DOUBLE_PUNCTUATION.test(name)
3444
+ && !LEADING_PUNCTUATION.test(name)
3445
+ && !TRAILING_PUNCTUATION.test(name);
3446
+ }
3447
+
3448
+ function isRestrictedNameValid(name) {
3449
+ return RESTRICTED_NAME_CHARACTERS.test(name)
3450
+ && !DOUBLE_PUNCTUATION.test(name)
3451
+ && !LEADING_PUNCTUATION.test(name)
3452
+ && !TRAILING_PUNCTUATION.test(name)
3453
+ && !NEURAI_NAMES.test(name);
3454
+ }
3455
+
3456
+ function isNameValidBeforeTag(name) {
3457
+ const parts = name.split('/');
3458
+
3459
+ if (!isRootNameValid(parts[0])) {
3460
+ return false;
3461
+ }
3462
+
3463
+ for (let index = 1; index < parts.length; index += 1) {
3464
+ if (!isSubNameValid(parts[index])) {
3465
+ return false;
3466
+ }
3467
+ }
3468
+
3469
+ return true;
3470
+ }
3471
+
3472
+ function isQualifierNameValidBeforeTag(name) {
3473
+ const parts = name.split('/');
3474
+
3475
+ if (!isQualifierNameValid(parts[0])) {
3476
+ return false;
3477
+ }
3478
+
3479
+ if (parts.length > 2) {
3480
+ return false;
3481
+ }
3482
+
3483
+ for (let index = 1; index < parts.length; index += 1) {
3484
+ if (!isSubQualifierNameValid(parts[index])) {
3485
+ return false;
3486
+ }
3487
+ }
3488
+
3489
+ return true;
3490
+ }
3491
+
3492
+ function isDepinIndicator(name) {
3493
+ return DEPIN_NAME_CHARACTERS.test(name) || SUB_DEPIN_NAME_CHARACTERS.test(name);
3494
+ }
3495
+
3310
3496
  class AssetNameValidator {
3311
3497
  /**
3312
3498
  * Validate ROOT asset name
3313
- * Rules: 3-30 uppercase characters, A-Z, 0-9, underscore, period
3314
- * Cannot start with period, A, or Z
3499
+ * Rules: 3-31 visible characters on mainnet, A-Z, 0-9, underscore, period
3500
+ * Cannot start or end with period/underscore, cannot use repeated punctuation
3315
3501
  * Cannot be reserved names
3316
3502
  */
3317
- static validateRoot(name) {
3318
- if (!name || typeof name !== 'string') {
3319
- throw new InvalidAssetNameError('Asset name must be a non-empty string', name);
3320
- }
3503
+ static validateRoot(name, network = 'xna') {
3504
+ ensureName(name, 'Asset name');
3321
3505
 
3322
- // Length check
3323
- if (name.length < ASSET_NAME_RULES.ROOT.minLength || name.length > ASSET_NAME_RULES.ROOT.maxLength) {
3506
+ const maxLength = getRootOrSubMaxLength(network);
3507
+
3508
+ if (name.length < MIN_ASSET_LENGTH || name.length > maxLength) {
3324
3509
  throw new InvalidAssetNameError(
3325
- `ROOT asset name must be ${ASSET_NAME_RULES.ROOT.minLength}-${ASSET_NAME_RULES.ROOT.maxLength} characters`,
3510
+ `ROOT asset name must be ${MIN_ASSET_LENGTH}-${maxLength} characters`,
3326
3511
  name
3327
3512
  );
3328
3513
  }
3329
3514
 
3330
- // Uppercase check
3331
- if (name !== name.toUpperCase()) {
3332
- throw new InvalidAssetNameError('Asset name must be uppercase', name);
3333
- }
3515
+ ensureUppercase(name, 'Asset name must be uppercase');
3334
3516
 
3335
- // Starting character check
3336
- if (ASSET_NAME_RULES.ROOT.cannotStartWith.some(char => name.startsWith(char))) {
3337
- throw new InvalidAssetNameError(
3338
- `Asset name cannot start with: ${ASSET_NAME_RULES.ROOT.cannotStartWith.join(', ')}`,
3339
- name
3340
- );
3517
+ if (NEURAI_NAMES.test(name)) {
3518
+ throw new InvalidAssetNameError(`${name} is a reserved asset name`, name);
3341
3519
  }
3342
3520
 
3343
- // Pattern check
3344
- if (!ASSET_NAME_RULES.ROOT.pattern.test(name)) {
3521
+ if (!isRootNameValid(name)) {
3345
3522
  throw new InvalidAssetNameError(
3346
- 'Asset name can only contain A-Z, 0-9, underscore, and period',
3523
+ 'Name contains invalid characters (Valid characters are: A-Z 0-9 _ .) (special characters can\'t be the first or last characters)',
3347
3524
  name
3348
3525
  );
3349
3526
  }
3350
3527
 
3351
- // Reserved names check
3352
- if (ASSET_NAME_RULES.ROOT.reserved.includes(name)) {
3353
- throw new InvalidAssetNameError(`${name} is a reserved asset name`, name);
3354
- }
3355
-
3356
3528
  return true;
3357
3529
  }
3358
3530
 
@@ -3360,39 +3532,25 @@ function requireAssetNameValidator () {
3360
3532
  * Validate SUB asset name
3361
3533
  * Format: ROOT/SUBNAME
3362
3534
  */
3363
- static validateSub(name) {
3364
- if (!name || typeof name !== 'string') {
3365
- throw new InvalidAssetNameError('SUB asset name must be a non-empty string', name);
3366
- }
3535
+ static validateSub(name, network = 'xna') {
3536
+ ensureName(name, 'SUB asset name');
3367
3537
 
3368
- const parts = name.split(ASSET_NAME_RULES.SUB.separator);
3369
- if (parts.length !== 2) {
3538
+ if (!name.includes(ASSET_NAME_RULES.SUB.separator)) {
3370
3539
  throw new InvalidAssetNameError(
3371
3540
  `SUB asset must be in ${ASSET_NAME_RULES.SUB.separator} format (ROOT/SUBNAME)`,
3372
3541
  name
3373
3542
  );
3374
3543
  }
3375
3544
 
3376
- const [rootName, subName] = parts;
3377
-
3378
- // Validate root part
3379
- this.validateRoot(rootName);
3545
+ ensureMaxLength(name, getRootOrSubMaxLength(network), 'SUB asset name');
3380
3546
 
3381
- // Validate sub part
3382
- if (subName.length < ASSET_NAME_RULES.SUB.minLength || subName.length > ASSET_NAME_RULES.SUB.maxLength) {
3383
- throw new InvalidAssetNameError(
3384
- `SUB asset name must be ${ASSET_NAME_RULES.SUB.minLength}-${ASSET_NAME_RULES.SUB.maxLength} characters`,
3385
- name
3386
- );
3387
- }
3388
-
3389
- if (subName !== subName.toUpperCase()) {
3547
+ if (name !== name.toUpperCase()) {
3390
3548
  throw new InvalidAssetNameError('SUB asset name must be uppercase', name);
3391
3549
  }
3392
3550
 
3393
- if (!ASSET_NAME_RULES.SUB.pattern.test(subName)) {
3551
+ if (!isNameValidBeforeTag(name)) {
3394
3552
  throw new InvalidAssetNameError(
3395
- 'SUB asset name can only contain A-Z, 0-9, underscore, and period',
3553
+ 'Name contains invalid characters (Valid characters are: A-Z 0-9 _ .) (special characters can\'t be the first or last characters)',
3396
3554
  name
3397
3555
  );
3398
3556
  }
@@ -3404,10 +3562,10 @@ function requireAssetNameValidator () {
3404
3562
  * Validate UNIQUE asset name
3405
3563
  * Format: ROOT#TAG
3406
3564
  */
3407
- static validateUnique(name) {
3408
- if (!name || typeof name !== 'string') {
3409
- throw new InvalidAssetNameError('UNIQUE asset name must be a non-empty string', name);
3410
- }
3565
+ static validateUnique(name, network = 'xna') {
3566
+ ensureName(name, 'UNIQUE asset name');
3567
+
3568
+ ensureMaxLength(name, getMaxAssetNameLength(network), 'UNIQUE asset name');
3411
3569
 
3412
3570
  const parts = name.split(ASSET_NAME_RULES.UNIQUE.separator);
3413
3571
  if (parts.length !== 2) {
@@ -3419,24 +3577,9 @@ function requireAssetNameValidator () {
3419
3577
 
3420
3578
  const [rootName, tag] = parts;
3421
3579
 
3422
- // Validate root part
3423
- this.validateRoot(rootName);
3424
-
3425
- // Validate tag
3426
- if (tag.length < ASSET_NAME_RULES.UNIQUE.minLength || tag.length > ASSET_NAME_RULES.UNIQUE.maxLength) {
3427
- throw new InvalidAssetNameError(
3428
- `UNIQUE tag must be ${ASSET_NAME_RULES.UNIQUE.minLength}-${ASSET_NAME_RULES.UNIQUE.maxLength} characters`,
3429
- name
3430
- );
3431
- }
3432
-
3433
- if (tag !== tag.toUpperCase()) {
3434
- throw new InvalidAssetNameError('UNIQUE tag must be uppercase', name);
3435
- }
3436
-
3437
- if (!ASSET_NAME_RULES.UNIQUE.pattern.test(tag)) {
3580
+ if (!isNameValidBeforeTag(rootName) || !UNIQUE_TAG_CHARACTERS.test(tag)) {
3438
3581
  throw new InvalidAssetNameError(
3439
- 'UNIQUE tag can only contain A-Z, 0-9, underscore, and period',
3582
+ 'Unique name contains invalid characters (Valid characters are: A-Z a-z 0-9 @ $ % & * ( ) [ ] { } _ . ? : -)',
3440
3583
  name
3441
3584
  );
3442
3585
  }
@@ -3448,10 +3591,8 @@ function requireAssetNameValidator () {
3448
3591
  * Validate QUALIFIER asset name
3449
3592
  * Format: #NAME or #ROOT/SUB
3450
3593
  */
3451
- static validateQualifier(name) {
3452
- if (!name || typeof name !== 'string') {
3453
- throw new InvalidAssetNameError('QUALIFIER asset name must be a non-empty string', name);
3454
- }
3594
+ static validateQualifier(name, network = 'xna') {
3595
+ ensureName(name, 'QUALIFIER asset name');
3455
3596
 
3456
3597
  if (!name.startsWith(ASSET_NAME_RULES.QUALIFIER.prefix)) {
3457
3598
  throw new InvalidAssetNameError(
@@ -3460,58 +3601,17 @@ function requireAssetNameValidator () {
3460
3601
  );
3461
3602
  }
3462
3603
 
3463
- const withoutPrefix = name.substring(1);
3464
-
3465
- if (withoutPrefix.includes(ASSET_NAME_RULES.QUALIFIER.separator)) {
3466
- // Sub-qualifier: #ROOT/SUB
3467
- const parts = withoutPrefix.split(ASSET_NAME_RULES.QUALIFIER.separator);
3468
- if (parts.length !== 2) {
3469
- throw new InvalidAssetNameError(
3470
- 'SUB_QUALIFIER must be in #ROOT/SUB format',
3471
- name
3472
- );
3473
- }
3474
-
3475
- // Validate each part as a qualifier name (without the #)
3476
- parts.forEach(part => {
3477
- if (part.length < ASSET_NAME_RULES.QUALIFIER.minLength || part.length > ASSET_NAME_RULES.QUALIFIER.maxLength) {
3478
- throw new InvalidAssetNameError(
3479
- `QUALIFIER name must be ${ASSET_NAME_RULES.QUALIFIER.minLength}-${ASSET_NAME_RULES.QUALIFIER.maxLength} characters`,
3480
- name
3481
- );
3482
- }
3483
-
3484
- if (part !== part.toUpperCase()) {
3485
- throw new InvalidAssetNameError('QUALIFIER name must be uppercase', name);
3486
- }
3487
-
3488
- if (!ASSET_NAME_RULES.QUALIFIER.pattern.test(part)) {
3489
- throw new InvalidAssetNameError(
3490
- 'QUALIFIER name can only contain A-Z, 0-9, and underscore',
3491
- name
3492
- );
3493
- }
3494
- });
3495
- } else {
3496
- // Root qualifier: #NAME
3497
- if (withoutPrefix.length < ASSET_NAME_RULES.QUALIFIER.minLength ||
3498
- withoutPrefix.length > ASSET_NAME_RULES.QUALIFIER.maxLength) {
3499
- throw new InvalidAssetNameError(
3500
- `QUALIFIER name must be ${ASSET_NAME_RULES.QUALIFIER.minLength}-${ASSET_NAME_RULES.QUALIFIER.maxLength} characters`,
3501
- name
3502
- );
3503
- }
3604
+ ensureMaxLength(name, getMaxAssetNameLength(network), 'QUALIFIER asset name');
3504
3605
 
3505
- if (withoutPrefix !== withoutPrefix.toUpperCase()) {
3506
- throw new InvalidAssetNameError('QUALIFIER name must be uppercase', name);
3507
- }
3606
+ if (name !== name.toUpperCase()) {
3607
+ throw new InvalidAssetNameError('QUALIFIER name must be uppercase', name);
3608
+ }
3508
3609
 
3509
- if (!ASSET_NAME_RULES.QUALIFIER.pattern.test(withoutPrefix)) {
3510
- throw new InvalidAssetNameError(
3511
- 'QUALIFIER name can only contain A-Z, 0-9, and underscore',
3512
- name
3513
- );
3514
- }
3610
+ if (!isQualifierNameValidBeforeTag(name)) {
3611
+ throw new InvalidAssetNameError(
3612
+ 'Qualifier name contains invalid characters (Valid characters are: A-Z 0-9 _ .) (# must be the first character, _ . special characters can\'t be the first or last characters)',
3613
+ name
3614
+ );
3515
3615
  }
3516
3616
 
3517
3617
  return true;
@@ -3521,10 +3621,8 @@ function requireAssetNameValidator () {
3521
3621
  * Validate RESTRICTED asset name
3522
3622
  * Format: $NAME
3523
3623
  */
3524
- static validateRestricted(name) {
3525
- if (!name || typeof name !== 'string') {
3526
- throw new InvalidAssetNameError('RESTRICTED asset name must be a non-empty string', name);
3527
- }
3624
+ static validateRestricted(name, network = 'xna') {
3625
+ ensureName(name, 'RESTRICTED asset name');
3528
3626
 
3529
3627
  if (!name.startsWith(ASSET_NAME_RULES.RESTRICTED.prefix)) {
3530
3628
  throw new InvalidAssetNameError(
@@ -3533,10 +3631,18 @@ function requireAssetNameValidator () {
3533
3631
  );
3534
3632
  }
3535
3633
 
3536
- const withoutPrefix = name.substring(1);
3634
+ ensureMaxLength(name, getMaxAssetNameLength(network), 'RESTRICTED asset name');
3537
3635
 
3538
- // Validate the part after $ as a ROOT asset name
3539
- this.validateRoot(withoutPrefix);
3636
+ if (name !== name.toUpperCase()) {
3637
+ throw new InvalidAssetNameError('RESTRICTED asset name must be uppercase', name);
3638
+ }
3639
+
3640
+ if (!isRestrictedNameValid(name)) {
3641
+ throw new InvalidAssetNameError(
3642
+ 'Restricted name contains invalid characters (Valid characters are: A-Z 0-9 _ .) ($ must be the first character, _ . special characters can\'t be the first or last characters)',
3643
+ name
3644
+ );
3645
+ }
3540
3646
 
3541
3647
  return true;
3542
3648
  }
@@ -3545,10 +3651,8 @@ function requireAssetNameValidator () {
3545
3651
  * Validate DEPIN asset name
3546
3652
  * Format: &NAME or &NAME/SUB[/...]
3547
3653
  */
3548
- static validateDepin(name) {
3549
- if (!name || typeof name !== 'string') {
3550
- throw new InvalidAssetNameError('DEPIN asset name must be a non-empty string', name);
3551
- }
3654
+ static validateDepin(name, network) {
3655
+ ensureName(name, 'DEPIN asset name');
3552
3656
 
3553
3657
  if (!name.startsWith(ASSET_NAME_RULES.DEPIN.prefix)) {
3554
3658
  throw new InvalidAssetNameError(
@@ -3557,58 +3661,41 @@ function requireAssetNameValidator () {
3557
3661
  );
3558
3662
  }
3559
3663
 
3560
- if (name.length > ASSET_NAME_RULES.DEPIN.maxLength) {
3561
- throw new InvalidAssetNameError(
3562
- `DEPIN asset name cannot exceed ${ASSET_NAME_RULES.DEPIN.maxLength} characters`,
3563
- name
3564
- );
3664
+ if (network && !isTestnet(network)) {
3665
+ throw new InvalidAssetNameError('DEPIN assets are only available in testnet', name);
3565
3666
  }
3566
3667
 
3567
- const parts = name.split(ASSET_NAME_RULES.DEPIN.separator);
3568
- if (parts.length === 0) {
3569
- throw new InvalidAssetNameError('DEPIN asset name is invalid', name);
3668
+ ensureMaxLength(name, getMaxAssetNameLength(network || 'xna-test'), 'DEPIN asset name');
3669
+
3670
+ if (name !== name.toUpperCase()) {
3671
+ throw new InvalidAssetNameError('DEPIN asset name must be uppercase', name);
3570
3672
  }
3571
3673
 
3572
- const rootPart = parts[0].substring(1);
3573
- if (rootPart.length < ASSET_NAME_RULES.DEPIN.minLength) {
3674
+ if (!isDepinIndicator(name)) {
3574
3675
  throw new InvalidAssetNameError(
3575
- `DEPIN root name must be at least ${ASSET_NAME_RULES.DEPIN.minLength} characters`,
3676
+ 'DEPIN asset name can only contain A-Z, 0-9, underscore, period, and separator /',
3576
3677
  name
3577
3678
  );
3578
3679
  }
3579
3680
 
3580
- if (rootPart !== rootPart.toUpperCase()) {
3581
- throw new InvalidAssetNameError('DEPIN asset name must be uppercase', name);
3582
- }
3681
+ const parts = name.split(ASSET_NAME_RULES.DEPIN.separator);
3583
3682
 
3584
- if (!ASSET_NAME_RULES.DEPIN.pattern.test(rootPart)) {
3683
+ if (parts.length > 1) {
3684
+ parts.forEach(part => {
3685
+ if (part.length < MIN_ASSET_LENGTH) {
3686
+ throw new InvalidAssetNameError(
3687
+ `Each DEPIN sub-part must be at least ${MIN_ASSET_LENGTH} characters`,
3688
+ name
3689
+ );
3690
+ }
3691
+ });
3692
+ } else if (name.length < MIN_ASSET_LENGTH + 1) {
3585
3693
  throw new InvalidAssetNameError(
3586
- 'DEPIN asset name can only contain A-Z, 0-9, underscore, and period',
3694
+ `DEPIN name must be at least ${MIN_ASSET_LENGTH} characters (excluding &)`,
3587
3695
  name
3588
3696
  );
3589
3697
  }
3590
3698
 
3591
- const subParts = parts.slice(1);
3592
- subParts.forEach(part => {
3593
- if (part.length < ASSET_NAME_RULES.DEPIN.minLength) {
3594
- throw new InvalidAssetNameError(
3595
- `Each DEPIN sub-part must be at least ${ASSET_NAME_RULES.DEPIN.minLength} characters`,
3596
- name
3597
- );
3598
- }
3599
-
3600
- if (part !== part.toUpperCase()) {
3601
- throw new InvalidAssetNameError('DEPIN asset name must be uppercase', name);
3602
- }
3603
-
3604
- if (!ASSET_NAME_RULES.DEPIN.pattern.test(part)) {
3605
- throw new InvalidAssetNameError(
3606
- 'DEPIN asset name can only contain A-Z, 0-9, underscore, and period',
3607
- name
3608
- );
3609
- }
3610
- });
3611
-
3612
3699
  return true;
3613
3700
  }
3614
3701
 
@@ -3616,24 +3703,31 @@ function requireAssetNameValidator () {
3616
3703
  * Validate owner token name
3617
3704
  * Format: ASSETNAME!
3618
3705
  */
3619
- static validateOwnerToken(name) {
3620
- if (!name || typeof name !== 'string') {
3621
- throw new InvalidAssetNameError('Owner token name must be a non-empty string', name);
3622
- }
3706
+ static validateOwnerToken(name, network) {
3707
+ ensureName(name, 'Owner token name');
3708
+
3709
+ ensureMaxLength(name, getMaxAssetNameLength(network), 'Owner token name');
3623
3710
 
3624
3711
  if (!name.endsWith('!')) {
3625
3712
  throw new InvalidAssetNameError('Owner token must end with !', name);
3626
3713
  }
3627
3714
 
3628
3715
  const assetName = name.substring(0, name.length - 1);
3716
+ const validBaseName = isNameValidBeforeTag(assetName)
3717
+ || (assetName.startsWith('&') && (() => {
3718
+ try {
3719
+ this.validateDepin(assetName, network);
3720
+ return true;
3721
+ } catch (error) {
3722
+ return false;
3723
+ }
3724
+ })());
3629
3725
 
3630
- // Validate the asset name part (could be ROOT, RESTRICTED, or DEPIN)
3631
- if (assetName.startsWith('$')) {
3632
- this.validateRestricted(assetName);
3633
- } else if (assetName.startsWith('&')) {
3634
- this.validateDepin(assetName);
3635
- } else {
3636
- this.validateRoot(assetName);
3726
+ if (!validBaseName) {
3727
+ throw new InvalidAssetNameError(
3728
+ 'Owner name contains invalid characters (Valid characters are: A-Z 0-9 _ .) (special characters can\'t be the first or last characters)',
3729
+ name
3730
+ );
3637
3731
  }
3638
3732
 
3639
3733
  return true;
@@ -3644,27 +3738,27 @@ function requireAssetNameValidator () {
3644
3738
  * @param {string} name - Asset name
3645
3739
  * @returns {string} Asset type ('ROOT', 'SUB', 'UNIQUE', 'QUALIFIER', 'RESTRICTED', 'DEPIN', 'OWNER')
3646
3740
  */
3647
- static validateAndDetectType(name) {
3741
+ static validateAndDetectType(name, network) {
3648
3742
  if (name.endsWith('!')) {
3649
- this.validateOwnerToken(name);
3743
+ this.validateOwnerToken(name, network);
3650
3744
  return 'OWNER';
3651
3745
  } else if (name.startsWith('#')) {
3652
- this.validateQualifier(name);
3746
+ this.validateQualifier(name, network);
3653
3747
  return name.includes('/') ? 'SUB_QUALIFIER' : 'QUALIFIER';
3654
3748
  } else if (name.startsWith('$')) {
3655
- this.validateRestricted(name);
3749
+ this.validateRestricted(name, network);
3656
3750
  return 'RESTRICTED';
3657
3751
  } else if (name.startsWith('&')) {
3658
- this.validateDepin(name);
3752
+ this.validateDepin(name, network);
3659
3753
  return 'DEPIN';
3660
3754
  } else if (name.includes('#')) {
3661
- this.validateUnique(name);
3755
+ this.validateUnique(name, network);
3662
3756
  return 'UNIQUE';
3663
3757
  } else if (name.includes('/')) {
3664
- this.validateSub(name);
3758
+ this.validateSub(name, network);
3665
3759
  return 'SUB';
3666
3760
  } else {
3667
- this.validateRoot(name);
3761
+ this.validateRoot(name, network);
3668
3762
  return 'ROOT';
3669
3763
  }
3670
3764
  }
@@ -4341,22 +4435,22 @@ function requireBaseAssetTransactionBuilder () {
4341
4435
 
4342
4436
  switch (type) {
4343
4437
  case 'ROOT':
4344
- AssetNameValidator.validateRoot(assetName);
4438
+ AssetNameValidator.validateRoot(assetName, this.network);
4345
4439
  break;
4346
4440
  case 'SUB':
4347
- AssetNameValidator.validateSub(assetName);
4441
+ AssetNameValidator.validateSub(assetName, this.network);
4348
4442
  break;
4349
4443
  case 'UNIQUE':
4350
- AssetNameValidator.validateUnique(assetName);
4444
+ AssetNameValidator.validateUnique(assetName, this.network);
4351
4445
  break;
4352
4446
  case 'QUALIFIER':
4353
- AssetNameValidator.validateQualifier(assetName);
4447
+ AssetNameValidator.validateQualifier(assetName, this.network);
4354
4448
  break;
4355
4449
  case 'RESTRICTED':
4356
- AssetNameValidator.validateRestricted(assetName);
4450
+ AssetNameValidator.validateRestricted(assetName, this.network);
4357
4451
  break;
4358
4452
  case 'DEPIN':
4359
- AssetNameValidator.validateDepin(assetName);
4453
+ AssetNameValidator.validateDepin(assetName, this.network);
4360
4454
  break;
4361
4455
  default:
4362
4456
  throw new Error(`Unknown asset type: ${type}`);
@@ -5777,7 +5871,7 @@ function requireIssueUniqueBuilder () {
5777
5871
  *
5778
5872
  * QUALIFIER assets:
5779
5873
  * - KYC/compliance tags (e.g., #KYC_VERIFIED, #ACCREDITED)
5780
- * - Format: #NAME or #ROOT/SUB
5874
+ * - Format: #NAME or #ROOT/#SUB
5781
5875
  * - Cost: 2000 XNA (root) or 200 XNA (sub-qualifier)
5782
5876
  * - Quantity: 1-10 units only
5783
5877
  * - Units: Always 0 (non-divisible)
@@ -7242,7 +7336,7 @@ function requireNeuraiAssets () {
7242
7336
  /**
7243
7337
  * Create a ROOT asset
7244
7338
  * @param {object} params - Asset creation parameters
7245
- * @param {string} params.assetName - Asset name (3-30 chars, A-Z 0-9 _ .)
7339
+ * @param {string} params.assetName - Asset name (3-31 visible chars on mainnet, A-Z 0-9 _ .)
7246
7340
  * @param {number} params.quantity - Total supply
7247
7341
  * @param {number} [params.units=0] - Decimal places (0-8)
7248
7342
  * @param {boolean} [params.reissuable=true] - Can mint more later