@sip-protocol/sdk 0.2.6 → 0.2.8

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
@@ -644,6 +644,91 @@ function validateScalar(value, field) {
644
644
  );
645
645
  }
646
646
  }
647
+ function isValidEvmAddress(address) {
648
+ if (typeof address !== "string") return false;
649
+ return /^0x[0-9a-fA-F]{40}$/.test(address);
650
+ }
651
+ function isValidSolanaAddressFormat(address) {
652
+ if (typeof address !== "string") return false;
653
+ return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address);
654
+ }
655
+ function isValidNearAddressFormat(address) {
656
+ if (typeof address !== "string") return false;
657
+ if (/^[0-9a-f]{64}$/.test(address)) return true;
658
+ if (address.length < 2 || address.length > 64) return false;
659
+ if (!/^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$/.test(address)) return false;
660
+ if (address.includes("..")) return false;
661
+ return true;
662
+ }
663
+ function getChainAddressType(chain) {
664
+ switch (chain) {
665
+ case "ethereum":
666
+ case "polygon":
667
+ case "arbitrum":
668
+ case "optimism":
669
+ case "base":
670
+ return "evm";
671
+ case "solana":
672
+ return "solana";
673
+ case "near":
674
+ return "near";
675
+ case "zcash":
676
+ return "zcash";
677
+ default:
678
+ return "unknown";
679
+ }
680
+ }
681
+ function validateAddressForChain(address, chain, field = "address") {
682
+ const addressType = getChainAddressType(chain);
683
+ switch (addressType) {
684
+ case "evm":
685
+ if (!isValidEvmAddress(address)) {
686
+ throw new ValidationError(
687
+ `Invalid address format for ${chain}. Expected EVM address (0x + 40 hex chars), got: ${address.slice(0, 20)}...`,
688
+ field,
689
+ { chain, expectedFormat: "0x...", receivedFormat: address.startsWith("0x") ? "hex but wrong length" : "not hex" }
690
+ );
691
+ }
692
+ break;
693
+ case "solana":
694
+ if (!isValidSolanaAddressFormat(address)) {
695
+ throw new ValidationError(
696
+ `Invalid address format for ${chain}. Expected Solana address (base58, 32-44 chars), got: ${address.slice(0, 20)}...`,
697
+ field,
698
+ { chain, expectedFormat: "base58", receivedFormat: address.startsWith("0x") ? "looks like EVM" : "unknown" }
699
+ );
700
+ }
701
+ break;
702
+ case "near":
703
+ if (!isValidNearAddressFormat(address)) {
704
+ throw new ValidationError(
705
+ `Invalid address format for ${chain}. Expected NEAR account ID (named or implicit), got: ${address.slice(0, 20)}...`,
706
+ field,
707
+ { chain, expectedFormat: "alice.near or 64 hex chars" }
708
+ );
709
+ }
710
+ break;
711
+ case "zcash":
712
+ if (!address || address.length === 0) {
713
+ throw new ValidationError(
714
+ `Invalid address format for ${chain}. Expected Zcash address.`,
715
+ field,
716
+ { chain }
717
+ );
718
+ }
719
+ break;
720
+ default:
721
+ break;
722
+ }
723
+ }
724
+ function isAddressValidForChain(address, chain) {
725
+ try {
726
+ validateAddressForChain(address, chain);
727
+ return true;
728
+ } catch {
729
+ return false;
730
+ }
731
+ }
647
732
 
648
733
  // src/secure-memory.ts
649
734
  var import_utils = require("@noble/hashes/utils");
@@ -2371,20 +2456,43 @@ var DEFAULT_ASSET_MAPPINGS = {
2371
2456
  // NEAR assets
2372
2457
  "near:NEAR": "nep141:wrap.near",
2373
2458
  "near:wNEAR": "nep141:wrap.near",
2459
+ "near:USDC": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
2374
2460
  // Ethereum assets (via OMFT bridge)
2375
2461
  "ethereum:ETH": "nep141:eth.omft.near",
2376
- "ethereum:USDC": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
2377
- "ethereum:USDT": "nep141:usdt.tether-token.near",
2462
+ "ethereum:USDC": "nep141:eth-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.omft.near",
2463
+ "ethereum:USDT": "nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near",
2378
2464
  // Solana assets (via OMFT bridge)
2379
2465
  "solana:SOL": "nep141:sol.omft.near",
2466
+ "solana:USDC": "nep141:sol-5ce3bf3a31af18be40ba30f721101b4341690186.omft.near",
2467
+ "solana:USDT": "nep141:sol-c800a4bd850783ccb82c2b2c7e84175443606352.omft.near",
2380
2468
  // Zcash assets
2381
2469
  "zcash:ZEC": "nep141:zec.omft.near",
2382
2470
  // Arbitrum assets
2383
2471
  "arbitrum:ETH": "nep141:arb.omft.near",
2472
+ "arbitrum:ARB": "nep141:arb-0x912ce59144191c1204e64559fe8253a0e49e6548.omft.near",
2473
+ "arbitrum:USDC": "nep141:arb-0xaf88d065e77c8cc2239327c5edb3a432268e5831.omft.near",
2384
2474
  // Base assets
2385
2475
  "base:ETH": "nep141:base.omft.near",
2386
- // Polygon assets
2387
- "polygon:MATIC": "nep141:matic.omft.near"
2476
+ "base:USDC": "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
2477
+ // Optimism assets (via HOT bridge - uses nep245)
2478
+ "optimism:ETH": "nep245:v2_1.omni.hot.tg:10_11111111111111111111",
2479
+ "optimism:OP": "nep245:v2_1.omni.hot.tg:10_vLAiSt9KfUGKpw5cD3vsSyNYBo7",
2480
+ "optimism:USDC": "nep245:v2_1.omni.hot.tg:10_A2ewyUyDp6qsue1jqZsGypkCxRJ",
2481
+ // Polygon assets (via HOT bridge - uses nep245)
2482
+ "polygon:POL": "nep245:v2_1.omni.hot.tg:137_11111111111111111111",
2483
+ "polygon:MATIC": "nep245:v2_1.omni.hot.tg:137_11111111111111111111",
2484
+ // POL is the rebranded MATIC
2485
+ "polygon:USDC": "nep245:v2_1.omni.hot.tg:137_qiStmoQJDQPTebaPjgx5VBxZv6L",
2486
+ // BNB Chain assets (via HOT bridge - uses nep245)
2487
+ "bsc:BNB": "nep245:v2_1.omni.hot.tg:56_11111111111111111111",
2488
+ "bsc:USDC": "nep245:v2_1.omni.hot.tg:56_2w93GqMcEmQFDru84j3HZZWt557r",
2489
+ // Avalanche assets (via HOT bridge - uses nep245)
2490
+ "avalanche:AVAX": "nep245:v2_1.omni.hot.tg:43114_11111111111111111111",
2491
+ "avalanche:USDC": "nep245:v2_1.omni.hot.tg:43114_3atVJH3r5c4GqiSYmg9fECvjc47o",
2492
+ // Bitcoin
2493
+ "bitcoin:BTC": "nep141:btc.omft.near",
2494
+ // Aptos
2495
+ "aptos:APT": "nep141:aptos.omft.near"
2388
2496
  };
2389
2497
  var CHAIN_BLOCKCHAIN_MAP = {
2390
2498
  near: "near",
@@ -2432,6 +2540,23 @@ var NEARIntentsAdapter = class {
2432
2540
  */
2433
2541
  async prepareSwap(request, recipientMetaAddress, senderAddress) {
2434
2542
  this.validateRequest(request);
2543
+ const inputChain = request.inputAsset.chain;
2544
+ if (senderAddress) {
2545
+ if (!isAddressValidForChain(senderAddress, inputChain)) {
2546
+ const inputChainType = getChainAddressType(inputChain);
2547
+ const senderFormat = senderAddress.startsWith("0x") ? "EVM" : /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(senderAddress) ? "Solana" : /^[0-9a-f]{64}$/.test(senderAddress) || /^[a-z0-9._-]+$/.test(senderAddress) ? "NEAR" : "unknown";
2548
+ throw new ValidationError(
2549
+ `Wallet address format doesn't match input chain. You're swapping FROM ${inputChain} (${inputChainType} format) but your connected wallet uses ${senderFormat} format. Please connect a wallet that matches the source chain (${inputChain}).`,
2550
+ "senderAddress",
2551
+ {
2552
+ inputChain,
2553
+ expectedFormat: inputChainType,
2554
+ receivedFormat: senderFormat,
2555
+ hint: `For ${inputChain} swaps, connect a ${inputChainType === "evm" ? "MetaMask or EVM" : inputChainType} wallet.`
2556
+ }
2557
+ );
2558
+ }
2559
+ }
2435
2560
  let recipientAddress;
2436
2561
  let refundAddress = senderAddress;
2437
2562
  let stealthData;
@@ -2496,22 +2621,22 @@ var NEARIntentsAdapter = class {
2496
2621
  );
2497
2622
  }
2498
2623
  if (!senderAddress) {
2499
- const inputChain = request.inputAsset.chain;
2500
- const inputChainType = CHAIN_BLOCKCHAIN_MAP[inputChain];
2501
- if (isEd25519Chain(inputChain)) {
2624
+ const inputChain2 = request.inputAsset.chain;
2625
+ const inputChainType = CHAIN_BLOCKCHAIN_MAP[inputChain2];
2626
+ if (isEd25519Chain(inputChain2)) {
2502
2627
  const inputKeyBytes = (metaAddr.spendingKey.length - 2) / 2;
2503
2628
  if (inputKeyBytes === 32) {
2504
2629
  const refundStealth = generateEd25519StealthAddress(metaAddr);
2505
- if (inputChain === "solana") {
2630
+ if (inputChain2 === "solana") {
2506
2631
  refundAddress = ed25519PublicKeyToSolanaAddress(refundStealth.stealthAddress.address);
2507
- } else if (inputChain === "near") {
2632
+ } else if (inputChain2 === "near") {
2508
2633
  refundAddress = ed25519PublicKeyToNearAddress(refundStealth.stealthAddress.address);
2509
2634
  }
2510
2635
  } else {
2511
2636
  throw new ValidationError(
2512
- `Cross-curve refunds not supported: input chain ${inputChain} requires ed25519 but meta-address uses secp256k1. Please provide a senderAddress for refunds, or use matching curves for input/output chains.`,
2637
+ `Cross-curve refunds not supported: input chain ${inputChain2} requires ed25519 but meta-address uses secp256k1. Please provide a senderAddress for refunds, or use matching curves for input/output chains.`,
2513
2638
  "senderAddress",
2514
- { inputChain, inputChainType, metaAddressCurve: "secp256k1" }
2639
+ { inputChain: inputChain2, inputChainType, metaAddressCurve: "secp256k1" }
2515
2640
  );
2516
2641
  }
2517
2642
  } else if (inputChainType === "evm") {
@@ -2521,16 +2646,16 @@ var NEARIntentsAdapter = class {
2521
2646
  refundAddress = publicKeyToEthAddress(refundStealth.stealthAddress.address);
2522
2647
  } else {
2523
2648
  throw new ValidationError(
2524
- `Cross-curve refunds not supported: input chain ${inputChain} requires secp256k1 but meta-address uses ed25519. Please provide a senderAddress for refunds, or use matching curves for input/output chains.`,
2649
+ `Cross-curve refunds not supported: input chain ${inputChain2} requires secp256k1 but meta-address uses ed25519. Please provide a senderAddress for refunds, or use matching curves for input/output chains.`,
2525
2650
  "senderAddress",
2526
- { inputChain, inputChainType, metaAddressCurve: "ed25519" }
2651
+ { inputChain: inputChain2, inputChainType, metaAddressCurve: "ed25519" }
2527
2652
  );
2528
2653
  }
2529
2654
  } else {
2530
2655
  throw new ValidationError(
2531
- `senderAddress is required for refunds on ${inputChain}. Automatic refund address generation is only supported for EVM, Solana, and NEAR chains.`,
2656
+ `senderAddress is required for refunds on ${inputChain2}. Automatic refund address generation is only supported for EVM, Solana, and NEAR chains.`,
2532
2657
  "senderAddress",
2533
- { inputChain, inputChainType }
2658
+ { inputChain: inputChain2, inputChainType }
2534
2659
  );
2535
2660
  }
2536
2661
  }
package/dist/index.mjs CHANGED
@@ -197,7 +197,7 @@ import {
197
197
  walletRegistry,
198
198
  withSecureBuffer,
199
199
  withSecureBufferSync
200
- } from "./chunk-6WOV2YNG.mjs";
200
+ } from "./chunk-UPTISVCY.mjs";
201
201
  import {
202
202
  CryptoError,
203
203
  EncryptionNotImplementedError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sip-protocol/sdk",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Core SDK for Shielded Intents Protocol - Privacy layer for cross-chain transactions",
5
5
  "author": "SIP Protocol <hello@sip-protocol.org>",
6
6
  "homepage": "https://sip-protocol.org",
@@ -40,6 +40,7 @@ import {
40
40
  type StealthCurve,
41
41
  } from '../stealth'
42
42
  import { ValidationError } from '../errors'
43
+ import { isAddressValidForChain, getChainAddressType } from '../validation'
43
44
 
44
45
  /**
45
46
  * Swap request parameters (simplified interface for adapter)
@@ -137,26 +138,53 @@ const DEFAULT_ASSET_MAPPINGS: Record<string, DefuseAssetId> = {
137
138
  // NEAR assets
138
139
  'near:NEAR': 'nep141:wrap.near',
139
140
  'near:wNEAR': 'nep141:wrap.near',
141
+ 'near:USDC': 'nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
140
142
 
141
143
  // Ethereum assets (via OMFT bridge)
142
144
  'ethereum:ETH': 'nep141:eth.omft.near',
143
- 'ethereum:USDC': 'nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
144
- 'ethereum:USDT': 'nep141:usdt.tether-token.near',
145
+ 'ethereum:USDC': 'nep141:eth-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.omft.near',
146
+ 'ethereum:USDT': 'nep141:eth-0xdac17f958d2ee523a2206206994597c13d831ec7.omft.near',
145
147
 
146
148
  // Solana assets (via OMFT bridge)
147
149
  'solana:SOL': 'nep141:sol.omft.near',
150
+ 'solana:USDC': 'nep141:sol-5ce3bf3a31af18be40ba30f721101b4341690186.omft.near',
151
+ 'solana:USDT': 'nep141:sol-c800a4bd850783ccb82c2b2c7e84175443606352.omft.near',
148
152
 
149
153
  // Zcash assets
150
154
  'zcash:ZEC': 'nep141:zec.omft.near',
151
155
 
152
156
  // Arbitrum assets
153
157
  'arbitrum:ETH': 'nep141:arb.omft.near',
158
+ 'arbitrum:ARB': 'nep141:arb-0x912ce59144191c1204e64559fe8253a0e49e6548.omft.near',
159
+ 'arbitrum:USDC': 'nep141:arb-0xaf88d065e77c8cc2239327c5edb3a432268e5831.omft.near',
154
160
 
155
161
  // Base assets
156
162
  'base:ETH': 'nep141:base.omft.near',
163
+ 'base:USDC': 'nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near',
157
164
 
158
- // Polygon assets
159
- 'polygon:MATIC': 'nep141:matic.omft.near',
165
+ // Optimism assets (via HOT bridge - uses nep245)
166
+ 'optimism:ETH': 'nep245:v2_1.omni.hot.tg:10_11111111111111111111',
167
+ 'optimism:OP': 'nep245:v2_1.omni.hot.tg:10_vLAiSt9KfUGKpw5cD3vsSyNYBo7',
168
+ 'optimism:USDC': 'nep245:v2_1.omni.hot.tg:10_A2ewyUyDp6qsue1jqZsGypkCxRJ',
169
+
170
+ // Polygon assets (via HOT bridge - uses nep245)
171
+ 'polygon:POL': 'nep245:v2_1.omni.hot.tg:137_11111111111111111111',
172
+ 'polygon:MATIC': 'nep245:v2_1.omni.hot.tg:137_11111111111111111111', // POL is the rebranded MATIC
173
+ 'polygon:USDC': 'nep245:v2_1.omni.hot.tg:137_qiStmoQJDQPTebaPjgx5VBxZv6L',
174
+
175
+ // BNB Chain assets (via HOT bridge - uses nep245)
176
+ 'bsc:BNB': 'nep245:v2_1.omni.hot.tg:56_11111111111111111111',
177
+ 'bsc:USDC': 'nep245:v2_1.omni.hot.tg:56_2w93GqMcEmQFDru84j3HZZWt557r',
178
+
179
+ // Avalanche assets (via HOT bridge - uses nep245)
180
+ 'avalanche:AVAX': 'nep245:v2_1.omni.hot.tg:43114_11111111111111111111',
181
+ 'avalanche:USDC': 'nep245:v2_1.omni.hot.tg:43114_3atVJH3r5c4GqiSYmg9fECvjc47o',
182
+
183
+ // Bitcoin
184
+ 'bitcoin:BTC': 'nep141:btc.omft.near',
185
+
186
+ // Aptos
187
+ 'aptos:APT': 'nep141:aptos.omft.near',
160
188
  }
161
189
 
162
190
  /**
@@ -247,6 +275,30 @@ export class NEARIntentsAdapter {
247
275
  // Validate request
248
276
  this.validateRequest(request)
249
277
 
278
+ // Validate senderAddress matches input chain if provided
279
+ const inputChain = request.inputAsset.chain
280
+ if (senderAddress) {
281
+ if (!isAddressValidForChain(senderAddress, inputChain)) {
282
+ const inputChainType = getChainAddressType(inputChain)
283
+ const senderFormat = senderAddress.startsWith('0x') ? 'EVM' :
284
+ /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(senderAddress) ? 'Solana' :
285
+ /^[0-9a-f]{64}$/.test(senderAddress) || /^[a-z0-9._-]+$/.test(senderAddress) ? 'NEAR' : 'unknown'
286
+
287
+ throw new ValidationError(
288
+ `Wallet address format doesn't match input chain. ` +
289
+ `You're swapping FROM ${inputChain} (${inputChainType} format) but your connected wallet uses ${senderFormat} format. ` +
290
+ `Please connect a wallet that matches the source chain (${inputChain}).`,
291
+ 'senderAddress',
292
+ {
293
+ inputChain,
294
+ expectedFormat: inputChainType,
295
+ receivedFormat: senderFormat,
296
+ hint: `For ${inputChain} swaps, connect a ${inputChainType === 'evm' ? 'MetaMask or EVM' : inputChainType} wallet.`
297
+ }
298
+ )
299
+ }
300
+ }
301
+
250
302
  // Determine recipient address
251
303
  let recipientAddress: string
252
304
  let refundAddress: string | undefined = senderAddress
package/src/validation.ts CHANGED
@@ -377,6 +377,132 @@ export function validateTimestamp(
377
377
  }
378
378
  }
379
379
 
380
+ // ─── Address Format Validation ──────────────────────────────────────────────────
381
+
382
+ /**
383
+ * Check if an address is a valid EVM address (0x + 40 hex chars)
384
+ */
385
+ export function isValidEvmAddress(address: string): boolean {
386
+ if (typeof address !== 'string') return false
387
+ return /^0x[0-9a-fA-F]{40}$/.test(address)
388
+ }
389
+
390
+ /**
391
+ * Check if an address is a valid Solana address (base58, 32-44 chars)
392
+ */
393
+ export function isValidSolanaAddressFormat(address: string): boolean {
394
+ if (typeof address !== 'string') return false
395
+ // Solana addresses are base58-encoded 32-byte public keys
396
+ // Typically 32-44 characters, using base58 alphabet (no 0, O, I, l)
397
+ return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address)
398
+ }
399
+
400
+ /**
401
+ * Check if an address is a valid NEAR account ID (named or implicit)
402
+ */
403
+ export function isValidNearAddressFormat(address: string): boolean {
404
+ if (typeof address !== 'string') return false
405
+
406
+ // Implicit account: 64 hex characters
407
+ if (/^[0-9a-f]{64}$/.test(address)) return true
408
+
409
+ // Named account: 2-64 chars, lowercase alphanumeric with . _ -
410
+ if (address.length < 2 || address.length > 64) return false
411
+ if (!/^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$/.test(address)) return false
412
+ if (address.includes('..')) return false
413
+
414
+ return true
415
+ }
416
+
417
+ /**
418
+ * Get the expected address format for a chain
419
+ */
420
+ export function getChainAddressType(chain: ChainId): 'evm' | 'solana' | 'near' | 'zcash' | 'unknown' {
421
+ switch (chain) {
422
+ case 'ethereum':
423
+ case 'polygon':
424
+ case 'arbitrum':
425
+ case 'optimism':
426
+ case 'base':
427
+ return 'evm'
428
+ case 'solana':
429
+ return 'solana'
430
+ case 'near':
431
+ return 'near'
432
+ case 'zcash':
433
+ return 'zcash'
434
+ default:
435
+ return 'unknown'
436
+ }
437
+ }
438
+
439
+ /**
440
+ * Validate that an address matches the expected chain format
441
+ *
442
+ * @param address - The address to validate
443
+ * @param chain - The chain the address should be valid for
444
+ * @param field - Field name for error messages
445
+ * @throws {ValidationError} If address format doesn't match chain
446
+ */
447
+ export function validateAddressForChain(address: string, chain: ChainId, field: string = 'address'): void {
448
+ const addressType = getChainAddressType(chain)
449
+
450
+ switch (addressType) {
451
+ case 'evm':
452
+ if (!isValidEvmAddress(address)) {
453
+ throw new ValidationError(
454
+ `Invalid address format for ${chain}. Expected EVM address (0x + 40 hex chars), got: ${address.slice(0, 20)}...`,
455
+ field,
456
+ { chain, expectedFormat: '0x...', receivedFormat: address.startsWith('0x') ? 'hex but wrong length' : 'not hex' }
457
+ )
458
+ }
459
+ break
460
+ case 'solana':
461
+ if (!isValidSolanaAddressFormat(address)) {
462
+ throw new ValidationError(
463
+ `Invalid address format for ${chain}. Expected Solana address (base58, 32-44 chars), got: ${address.slice(0, 20)}...`,
464
+ field,
465
+ { chain, expectedFormat: 'base58', receivedFormat: address.startsWith('0x') ? 'looks like EVM' : 'unknown' }
466
+ )
467
+ }
468
+ break
469
+ case 'near':
470
+ if (!isValidNearAddressFormat(address)) {
471
+ throw new ValidationError(
472
+ `Invalid address format for ${chain}. Expected NEAR account ID (named or implicit), got: ${address.slice(0, 20)}...`,
473
+ field,
474
+ { chain, expectedFormat: 'alice.near or 64 hex chars' }
475
+ )
476
+ }
477
+ break
478
+ case 'zcash':
479
+ // Zcash has multiple formats (t-addr, z-addr, u-addr) - accept any non-empty string for now
480
+ if (!address || address.length === 0) {
481
+ throw new ValidationError(
482
+ `Invalid address format for ${chain}. Expected Zcash address.`,
483
+ field,
484
+ { chain }
485
+ )
486
+ }
487
+ break
488
+ default:
489
+ // Unknown chain - skip validation
490
+ break
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Check if an address format matches a chain (non-throwing version)
496
+ */
497
+ export function isAddressValidForChain(address: string, chain: ChainId): boolean {
498
+ try {
499
+ validateAddressForChain(address, chain)
500
+ return true
501
+ } catch {
502
+ return false
503
+ }
504
+ }
505
+
380
506
  // ─── Composite Validators ──────────────────────────────────────────────────────
381
507
 
382
508
  /**