@sip-protocol/sdk 0.2.5 → 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/browser.js +132 -14
- package/dist/browser.mjs +1 -1
- package/dist/chunk-5BAS4D44.mjs +10283 -0
- package/dist/chunk-6WOV2YNG.mjs +10179 -0
- package/dist/index.js +132 -14
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/src/adapters/near-intents.ts +27 -1
- package/src/adapters/oneclick-client.ts +33 -1
- package/src/validation.ts +126 -0
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");
|
|
@@ -2120,7 +2205,21 @@ var OneClickClient = class {
|
|
|
2120
2205
|
*/
|
|
2121
2206
|
async quote(request) {
|
|
2122
2207
|
this.validateQuoteRequest(request);
|
|
2123
|
-
|
|
2208
|
+
const rawResponse = await this.post("/v0/quote", request);
|
|
2209
|
+
return {
|
|
2210
|
+
quoteId: rawResponse.timestamp,
|
|
2211
|
+
// Use timestamp as quoteId since API doesn't provide one
|
|
2212
|
+
depositAddress: rawResponse.quote.depositAddress,
|
|
2213
|
+
amountIn: rawResponse.quote.amountIn,
|
|
2214
|
+
amountInFormatted: rawResponse.quote.amountInFormatted,
|
|
2215
|
+
amountOut: rawResponse.quote.amountOut,
|
|
2216
|
+
amountOutFormatted: rawResponse.quote.amountOutFormatted,
|
|
2217
|
+
amountOutUsd: rawResponse.quote.amountOutUsd,
|
|
2218
|
+
deadline: rawResponse.quote.deadline,
|
|
2219
|
+
timeEstimate: rawResponse.quote.timeEstimate,
|
|
2220
|
+
signature: rawResponse.signature,
|
|
2221
|
+
request: rawResponse.quoteRequest
|
|
2222
|
+
};
|
|
2124
2223
|
}
|
|
2125
2224
|
/**
|
|
2126
2225
|
* Request a dry quote (preview without deposit address)
|
|
@@ -2369,8 +2468,10 @@ var DEFAULT_ASSET_MAPPINGS = {
|
|
|
2369
2468
|
"arbitrum:ETH": "nep141:arb.omft.near",
|
|
2370
2469
|
// Base assets
|
|
2371
2470
|
"base:ETH": "nep141:base.omft.near",
|
|
2372
|
-
// Polygon assets
|
|
2373
|
-
"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
|
|
2374
2475
|
};
|
|
2375
2476
|
var CHAIN_BLOCKCHAIN_MAP = {
|
|
2376
2477
|
near: "near",
|
|
@@ -2418,6 +2519,23 @@ var NEARIntentsAdapter = class {
|
|
|
2418
2519
|
*/
|
|
2419
2520
|
async prepareSwap(request, recipientMetaAddress, senderAddress) {
|
|
2420
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
|
+
}
|
|
2421
2539
|
let recipientAddress;
|
|
2422
2540
|
let refundAddress = senderAddress;
|
|
2423
2541
|
let stealthData;
|
|
@@ -2482,22 +2600,22 @@ var NEARIntentsAdapter = class {
|
|
|
2482
2600
|
);
|
|
2483
2601
|
}
|
|
2484
2602
|
if (!senderAddress) {
|
|
2485
|
-
const
|
|
2486
|
-
const inputChainType = CHAIN_BLOCKCHAIN_MAP[
|
|
2487
|
-
if (isEd25519Chain(
|
|
2603
|
+
const inputChain2 = request.inputAsset.chain;
|
|
2604
|
+
const inputChainType = CHAIN_BLOCKCHAIN_MAP[inputChain2];
|
|
2605
|
+
if (isEd25519Chain(inputChain2)) {
|
|
2488
2606
|
const inputKeyBytes = (metaAddr.spendingKey.length - 2) / 2;
|
|
2489
2607
|
if (inputKeyBytes === 32) {
|
|
2490
2608
|
const refundStealth = generateEd25519StealthAddress(metaAddr);
|
|
2491
|
-
if (
|
|
2609
|
+
if (inputChain2 === "solana") {
|
|
2492
2610
|
refundAddress = ed25519PublicKeyToSolanaAddress(refundStealth.stealthAddress.address);
|
|
2493
|
-
} else if (
|
|
2611
|
+
} else if (inputChain2 === "near") {
|
|
2494
2612
|
refundAddress = ed25519PublicKeyToNearAddress(refundStealth.stealthAddress.address);
|
|
2495
2613
|
}
|
|
2496
2614
|
} else {
|
|
2497
2615
|
throw new ValidationError(
|
|
2498
|
-
`Cross-curve refunds not supported: input chain ${
|
|
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.`,
|
|
2499
2617
|
"senderAddress",
|
|
2500
|
-
{ inputChain, inputChainType, metaAddressCurve: "secp256k1" }
|
|
2618
|
+
{ inputChain: inputChain2, inputChainType, metaAddressCurve: "secp256k1" }
|
|
2501
2619
|
);
|
|
2502
2620
|
}
|
|
2503
2621
|
} else if (inputChainType === "evm") {
|
|
@@ -2507,16 +2625,16 @@ var NEARIntentsAdapter = class {
|
|
|
2507
2625
|
refundAddress = publicKeyToEthAddress(refundStealth.stealthAddress.address);
|
|
2508
2626
|
} else {
|
|
2509
2627
|
throw new ValidationError(
|
|
2510
|
-
`Cross-curve refunds not supported: input chain ${
|
|
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.`,
|
|
2511
2629
|
"senderAddress",
|
|
2512
|
-
{ inputChain, inputChainType, metaAddressCurve: "ed25519" }
|
|
2630
|
+
{ inputChain: inputChain2, inputChainType, metaAddressCurve: "ed25519" }
|
|
2513
2631
|
);
|
|
2514
2632
|
}
|
|
2515
2633
|
} else {
|
|
2516
2634
|
throw new ValidationError(
|
|
2517
|
-
`senderAddress is required for refunds on ${
|
|
2635
|
+
`senderAddress is required for refunds on ${inputChain2}. Automatic refund address generation is only supported for EVM, Solana, and NEAR chains.`,
|
|
2518
2636
|
"senderAddress",
|
|
2519
|
-
{ inputChain, inputChainType }
|
|
2637
|
+
{ inputChain: inputChain2, inputChainType }
|
|
2520
2638
|
);
|
|
2521
2639
|
}
|
|
2522
2640
|
}
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sip-protocol/sdk",
|
|
3
|
-
"version": "0.2.
|
|
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
|
|
@@ -91,7 +91,39 @@ export class OneClickClient {
|
|
|
91
91
|
*/
|
|
92
92
|
async quote(request: OneClickQuoteRequest): Promise<OneClickQuoteResponse> {
|
|
93
93
|
this.validateQuoteRequest(request)
|
|
94
|
-
|
|
94
|
+
// The 1Click API returns a nested structure: { quote: {...}, signature, timestamp }
|
|
95
|
+
// We flatten it to match our OneClickQuoteResponse type
|
|
96
|
+
const rawResponse = await this.post<{
|
|
97
|
+
quote: {
|
|
98
|
+
amountIn: string
|
|
99
|
+
amountInFormatted: string
|
|
100
|
+
amountInUsd?: string
|
|
101
|
+
amountOut: string
|
|
102
|
+
amountOutFormatted: string
|
|
103
|
+
amountOutUsd?: string
|
|
104
|
+
depositAddress: string
|
|
105
|
+
deadline: string
|
|
106
|
+
timeEstimate: number
|
|
107
|
+
}
|
|
108
|
+
quoteRequest: OneClickQuoteRequest
|
|
109
|
+
signature: string
|
|
110
|
+
timestamp: string
|
|
111
|
+
}>('/v0/quote', request)
|
|
112
|
+
|
|
113
|
+
// Flatten the response
|
|
114
|
+
return {
|
|
115
|
+
quoteId: rawResponse.timestamp, // Use timestamp as quoteId since API doesn't provide one
|
|
116
|
+
depositAddress: rawResponse.quote.depositAddress,
|
|
117
|
+
amountIn: rawResponse.quote.amountIn,
|
|
118
|
+
amountInFormatted: rawResponse.quote.amountInFormatted,
|
|
119
|
+
amountOut: rawResponse.quote.amountOut,
|
|
120
|
+
amountOutFormatted: rawResponse.quote.amountOutFormatted,
|
|
121
|
+
amountOutUsd: rawResponse.quote.amountOutUsd,
|
|
122
|
+
deadline: rawResponse.quote.deadline,
|
|
123
|
+
timeEstimate: rawResponse.quote.timeEstimate,
|
|
124
|
+
signature: rawResponse.signature,
|
|
125
|
+
request: rawResponse.quoteRequest,
|
|
126
|
+
}
|
|
95
127
|
}
|
|
96
128
|
|
|
97
129
|
/**
|
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
|
/**
|