@sip-protocol/sdk 0.2.6 → 0.2.7

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");
@@ -2383,8 +2468,10 @@ var DEFAULT_ASSET_MAPPINGS = {
2383
2468
  "arbitrum:ETH": "nep141:arb.omft.near",
2384
2469
  // Base assets
2385
2470
  "base:ETH": "nep141:base.omft.near",
2386
- // Polygon assets
2387
- "polygon:MATIC": "nep141:matic.omft.near"
2471
+ // Polygon assets (support both MATIC and POL symbols)
2472
+ "polygon:MATIC": "nep141:matic.omft.near",
2473
+ "polygon:POL": "nep141:matic.omft.near"
2474
+ // POL is the rebranded MATIC
2388
2475
  };
2389
2476
  var CHAIN_BLOCKCHAIN_MAP = {
2390
2477
  near: "near",
@@ -2432,6 +2519,23 @@ var NEARIntentsAdapter = class {
2432
2519
  */
2433
2520
  async prepareSwap(request, recipientMetaAddress, senderAddress) {
2434
2521
  this.validateRequest(request);
2522
+ const inputChain = request.inputAsset.chain;
2523
+ if (senderAddress) {
2524
+ if (!isAddressValidForChain(senderAddress, inputChain)) {
2525
+ const inputChainType = getChainAddressType(inputChain);
2526
+ 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";
2527
+ throw new ValidationError(
2528
+ `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}).`,
2529
+ "senderAddress",
2530
+ {
2531
+ inputChain,
2532
+ expectedFormat: inputChainType,
2533
+ receivedFormat: senderFormat,
2534
+ hint: `For ${inputChain} swaps, connect a ${inputChainType === "evm" ? "MetaMask or EVM" : inputChainType} wallet.`
2535
+ }
2536
+ );
2537
+ }
2538
+ }
2435
2539
  let recipientAddress;
2436
2540
  let refundAddress = senderAddress;
2437
2541
  let stealthData;
@@ -2496,22 +2600,22 @@ var NEARIntentsAdapter = class {
2496
2600
  );
2497
2601
  }
2498
2602
  if (!senderAddress) {
2499
- const inputChain = request.inputAsset.chain;
2500
- const inputChainType = CHAIN_BLOCKCHAIN_MAP[inputChain];
2501
- if (isEd25519Chain(inputChain)) {
2603
+ const inputChain2 = request.inputAsset.chain;
2604
+ const inputChainType = CHAIN_BLOCKCHAIN_MAP[inputChain2];
2605
+ if (isEd25519Chain(inputChain2)) {
2502
2606
  const inputKeyBytes = (metaAddr.spendingKey.length - 2) / 2;
2503
2607
  if (inputKeyBytes === 32) {
2504
2608
  const refundStealth = generateEd25519StealthAddress(metaAddr);
2505
- if (inputChain === "solana") {
2609
+ if (inputChain2 === "solana") {
2506
2610
  refundAddress = ed25519PublicKeyToSolanaAddress(refundStealth.stealthAddress.address);
2507
- } else if (inputChain === "near") {
2611
+ } else if (inputChain2 === "near") {
2508
2612
  refundAddress = ed25519PublicKeyToNearAddress(refundStealth.stealthAddress.address);
2509
2613
  }
2510
2614
  } else {
2511
2615
  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.`,
2616
+ `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
2617
  "senderAddress",
2514
- { inputChain, inputChainType, metaAddressCurve: "secp256k1" }
2618
+ { inputChain: inputChain2, inputChainType, metaAddressCurve: "secp256k1" }
2515
2619
  );
2516
2620
  }
2517
2621
  } else if (inputChainType === "evm") {
@@ -2521,16 +2625,16 @@ var NEARIntentsAdapter = class {
2521
2625
  refundAddress = publicKeyToEthAddress(refundStealth.stealthAddress.address);
2522
2626
  } else {
2523
2627
  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.`,
2628
+ `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
2629
  "senderAddress",
2526
- { inputChain, inputChainType, metaAddressCurve: "ed25519" }
2630
+ { inputChain: inputChain2, inputChainType, metaAddressCurve: "ed25519" }
2527
2631
  );
2528
2632
  }
2529
2633
  } else {
2530
2634
  throw new ValidationError(
2531
- `senderAddress is required for refunds on ${inputChain}. Automatic refund address generation is only supported for EVM, Solana, and NEAR chains.`,
2635
+ `senderAddress is required for refunds on ${inputChain2}. Automatic refund address generation is only supported for EVM, Solana, and NEAR chains.`,
2532
2636
  "senderAddress",
2533
- { inputChain, inputChainType }
2637
+ { inputChain: inputChain2, inputChainType }
2534
2638
  );
2535
2639
  }
2536
2640
  }
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-5BAS4D44.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.7",
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)
@@ -155,8 +156,9 @@ const DEFAULT_ASSET_MAPPINGS: Record<string, DefuseAssetId> = {
155
156
  // Base assets
156
157
  'base:ETH': 'nep141:base.omft.near',
157
158
 
158
- // Polygon assets
159
+ // Polygon assets (support both MATIC and POL symbols)
159
160
  'polygon:MATIC': 'nep141:matic.omft.near',
161
+ 'polygon:POL': 'nep141:matic.omft.near', // POL is the rebranded MATIC
160
162
  }
161
163
 
162
164
  /**
@@ -247,6 +249,30 @@ export class NEARIntentsAdapter {
247
249
  // Validate request
248
250
  this.validateRequest(request)
249
251
 
252
+ // Validate senderAddress matches input chain if provided
253
+ const inputChain = request.inputAsset.chain
254
+ if (senderAddress) {
255
+ if (!isAddressValidForChain(senderAddress, inputChain)) {
256
+ const inputChainType = getChainAddressType(inputChain)
257
+ const senderFormat = senderAddress.startsWith('0x') ? 'EVM' :
258
+ /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(senderAddress) ? 'Solana' :
259
+ /^[0-9a-f]{64}$/.test(senderAddress) || /^[a-z0-9._-]+$/.test(senderAddress) ? 'NEAR' : 'unknown'
260
+
261
+ throw new ValidationError(
262
+ `Wallet address format doesn't match input chain. ` +
263
+ `You're swapping FROM ${inputChain} (${inputChainType} format) but your connected wallet uses ${senderFormat} format. ` +
264
+ `Please connect a wallet that matches the source chain (${inputChain}).`,
265
+ 'senderAddress',
266
+ {
267
+ inputChain,
268
+ expectedFormat: inputChainType,
269
+ receivedFormat: senderFormat,
270
+ hint: `For ${inputChain} swaps, connect a ${inputChainType === 'evm' ? 'MetaMask or EVM' : inputChainType} wallet.`
271
+ }
272
+ )
273
+ }
274
+ }
275
+
250
276
  // Determine recipient address
251
277
  let recipientAddress: string
252
278
  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
  /**