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