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