@sip-protocol/sdk 0.7.3 → 0.8.0
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/README.md +267 -0
- package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
- package/dist/browser.d.mts +10 -4
- package/dist/browser.d.ts +10 -4
- package/dist/browser.js +47556 -19603
- package/dist/browser.mjs +628 -48
- package/dist/chunk-4GRJ5MAW.mjs +152 -0
- package/dist/chunk-5D7A3L3W.mjs +717 -0
- package/dist/chunk-64AYA5F5.mjs +7834 -0
- package/dist/chunk-GMDGB22A.mjs +379 -0
- package/dist/chunk-I534WKN7.mjs +328 -0
- package/dist/chunk-IBZVA5Y7.mjs +1003 -0
- package/dist/chunk-PRRZAWJE.mjs +223 -0
- package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
- package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
- package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
- package/dist/constants-LHAAUC2T.mjs +51 -0
- package/dist/dist-2OGQ7FED.mjs +3957 -0
- package/dist/dist-IFHPYLDX.mjs +254 -0
- package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
- package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
- package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
- package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +48396 -19623
- package/dist/index.mjs +537 -19
- package/dist/interface-Bf7w1PLW.d.mts +679 -0
- package/dist/interface-Bf7w1PLW.d.ts +679 -0
- package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
- package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
- package/dist/proofs/halo2.d.mts +151 -0
- package/dist/proofs/halo2.d.ts +151 -0
- package/dist/proofs/halo2.js +350 -0
- package/dist/proofs/halo2.mjs +11 -0
- package/dist/proofs/kimchi.d.mts +160 -0
- package/dist/proofs/kimchi.d.ts +160 -0
- package/dist/proofs/kimchi.js +431 -0
- package/dist/proofs/kimchi.mjs +13 -0
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/proofs/noir.js +74 -18
- package/dist/proofs/noir.mjs +84 -24
- package/dist/solana-U3MEGU7W.mjs +280 -0
- package/dist/validity_proof-3POXLPNY.mjs +21 -0
- package/package.json +44 -11
- package/src/adapters/index.ts +41 -0
- package/src/adapters/jupiter.ts +571 -0
- package/src/adapters/near-intents.ts +135 -0
- package/src/advisor/advisor.ts +653 -0
- package/src/advisor/index.ts +54 -0
- package/src/advisor/tools.ts +303 -0
- package/src/advisor/types.ts +164 -0
- package/src/chains/ethereum/announcement.ts +536 -0
- package/src/chains/ethereum/bnb-optimizations.ts +474 -0
- package/src/chains/ethereum/commitment.ts +522 -0
- package/src/chains/ethereum/constants.ts +462 -0
- package/src/chains/ethereum/deployment.ts +596 -0
- package/src/chains/ethereum/gas-estimation.ts +538 -0
- package/src/chains/ethereum/index.ts +268 -0
- package/src/chains/ethereum/optimizations.ts +614 -0
- package/src/chains/ethereum/privacy-adapter.ts +855 -0
- package/src/chains/ethereum/registry.ts +584 -0
- package/src/chains/ethereum/rpc.ts +905 -0
- package/src/chains/ethereum/stealth.ts +491 -0
- package/src/chains/ethereum/token.ts +790 -0
- package/src/chains/ethereum/transfer.ts +637 -0
- package/src/chains/ethereum/types.ts +456 -0
- package/src/chains/ethereum/viewing-key.ts +455 -0
- package/src/chains/near/commitment.ts +608 -0
- package/src/chains/near/constants.ts +284 -0
- package/src/chains/near/function-call.ts +871 -0
- package/src/chains/near/history.ts +654 -0
- package/src/chains/near/implicit-account.ts +840 -0
- package/src/chains/near/index.ts +393 -0
- package/src/chains/near/native-transfer.ts +658 -0
- package/src/chains/near/nep141.ts +775 -0
- package/src/chains/near/privacy-adapter.ts +889 -0
- package/src/chains/near/resolver.ts +971 -0
- package/src/chains/near/rpc.ts +1016 -0
- package/src/chains/near/stealth.ts +419 -0
- package/src/chains/near/types.ts +317 -0
- package/src/chains/near/viewing-key.ts +876 -0
- package/src/chains/solana/anchor-transfer.ts +386 -0
- package/src/chains/solana/commitment.ts +577 -0
- package/src/chains/solana/constants.ts +126 -12
- package/src/chains/solana/ephemeral-keys.ts +543 -0
- package/src/chains/solana/index.ts +252 -1
- package/src/chains/solana/key-derivation.ts +418 -0
- package/src/chains/solana/kit-compat.ts +334 -0
- package/src/chains/solana/optimizations.ts +560 -0
- package/src/chains/solana/privacy-adapter.ts +605 -0
- package/src/chains/solana/providers/generic.ts +47 -6
- package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
- package/src/chains/solana/providers/helius-enhanced.ts +623 -0
- package/src/chains/solana/providers/helius.ts +186 -33
- package/src/chains/solana/providers/index.ts +31 -0
- package/src/chains/solana/providers/interface.ts +61 -18
- package/src/chains/solana/providers/quicknode.ts +409 -0
- package/src/chains/solana/providers/triton.ts +426 -0
- package/src/chains/solana/providers/webhook.ts +338 -67
- package/src/chains/solana/rpc-client.ts +1150 -0
- package/src/chains/solana/scan.ts +83 -66
- package/src/chains/solana/sol-transfer.ts +732 -0
- package/src/chains/solana/spl-transfer.ts +886 -0
- package/src/chains/solana/stealth-scanner.ts +703 -0
- package/src/chains/solana/sunspot-verifier.ts +453 -0
- package/src/chains/solana/transaction-builder.ts +755 -0
- package/src/chains/solana/transfer.ts +74 -5
- package/src/chains/solana/types.ts +57 -6
- package/src/chains/solana/utils.ts +110 -0
- package/src/chains/solana/viewing-key.ts +807 -0
- package/src/compliance/fireblocks.ts +921 -0
- package/src/compliance/index.ts +23 -0
- package/src/compliance/range-sas.ts +398 -33
- package/src/config/endpoints.ts +100 -0
- package/src/crypto.ts +11 -8
- package/src/errors.ts +82 -0
- package/src/evm/erc4337-relayer.ts +830 -0
- package/src/evm/index.ts +47 -0
- package/src/fees/calculator.ts +396 -0
- package/src/fees/index.ts +87 -0
- package/src/fees/near-contract.ts +429 -0
- package/src/fees/types.ts +268 -0
- package/src/index.ts +686 -1
- package/src/intent.ts +6 -3
- package/src/logger.ts +324 -0
- package/src/network/index.ts +80 -0
- package/src/network/proxy.ts +691 -0
- package/src/optimizations/index.ts +541 -0
- package/src/oracle/types.ts +1 -0
- package/src/privacy-backends/arcium-types.ts +727 -0
- package/src/privacy-backends/arcium.ts +719 -0
- package/src/privacy-backends/combined-privacy.ts +866 -0
- package/src/privacy-backends/cspl-token.ts +595 -0
- package/src/privacy-backends/cspl-types.ts +512 -0
- package/src/privacy-backends/cspl.ts +907 -0
- package/src/privacy-backends/health.ts +488 -0
- package/src/privacy-backends/inco-types.ts +323 -0
- package/src/privacy-backends/inco.ts +616 -0
- package/src/privacy-backends/index.ts +254 -4
- package/src/privacy-backends/interface.ts +649 -6
- package/src/privacy-backends/lru-cache.ts +343 -0
- package/src/privacy-backends/magicblock.ts +458 -0
- package/src/privacy-backends/mock.ts +258 -0
- package/src/privacy-backends/privacycash.ts +13 -17
- package/src/privacy-backends/private-swap.ts +570 -0
- package/src/privacy-backends/rate-limiter.ts +683 -0
- package/src/privacy-backends/registry.ts +414 -2
- package/src/privacy-backends/router.ts +283 -3
- package/src/privacy-backends/shadowwire.ts +449 -0
- package/src/privacy-backends/sip-native.ts +3 -0
- package/src/privacy-logger.ts +191 -0
- package/src/production-safety.ts +373 -0
- package/src/proofs/aggregator.ts +1029 -0
- package/src/proofs/browser-composer.ts +1150 -0
- package/src/proofs/browser.ts +113 -25
- package/src/proofs/cache/index.ts +127 -0
- package/src/proofs/cache/interface.ts +545 -0
- package/src/proofs/cache/key-generator.ts +188 -0
- package/src/proofs/cache/lru-cache.ts +481 -0
- package/src/proofs/cache/multi-tier-cache.ts +575 -0
- package/src/proofs/cache/persistent-cache.ts +788 -0
- package/src/proofs/compliance-proof.ts +872 -0
- package/src/proofs/composer/base.ts +923 -0
- package/src/proofs/composer/index.ts +25 -0
- package/src/proofs/composer/interface.ts +518 -0
- package/src/proofs/composer/types.ts +383 -0
- package/src/proofs/converters/halo2.ts +452 -0
- package/src/proofs/converters/index.ts +208 -0
- package/src/proofs/converters/interface.ts +363 -0
- package/src/proofs/converters/kimchi.ts +462 -0
- package/src/proofs/converters/noir.ts +451 -0
- package/src/proofs/fallback.ts +888 -0
- package/src/proofs/halo2.ts +42 -0
- package/src/proofs/index.ts +471 -0
- package/src/proofs/interface.ts +13 -0
- package/src/proofs/kimchi.ts +42 -0
- package/src/proofs/lazy.ts +1004 -0
- package/src/proofs/mock.ts +25 -1
- package/src/proofs/noir.ts +110 -29
- package/src/proofs/orchestrator.ts +960 -0
- package/src/proofs/parallel/concurrency.ts +297 -0
- package/src/proofs/parallel/dependency-graph.ts +602 -0
- package/src/proofs/parallel/executor.ts +420 -0
- package/src/proofs/parallel/index.ts +131 -0
- package/src/proofs/parallel/interface.ts +685 -0
- package/src/proofs/parallel/worker-pool.ts +644 -0
- package/src/proofs/providers/halo2.ts +560 -0
- package/src/proofs/providers/index.ts +34 -0
- package/src/proofs/providers/kimchi.ts +641 -0
- package/src/proofs/validator.ts +881 -0
- package/src/proofs/verifier.ts +867 -0
- package/src/quantum/index.ts +112 -0
- package/src/quantum/winternitz-vault.ts +639 -0
- package/src/quantum/wots.ts +611 -0
- package/src/settlement/backends/direct-chain.ts +1 -0
- package/src/settlement/index.ts +9 -0
- package/src/settlement/router.ts +732 -46
- package/src/solana/index.ts +72 -0
- package/src/solana/jito-relayer.ts +687 -0
- package/src/solana/noir-verifier-types.ts +430 -0
- package/src/solana/noir-verifier.ts +816 -0
- package/src/stealth/address-derivation.ts +193 -0
- package/src/stealth/ed25519.ts +431 -0
- package/src/stealth/index.ts +233 -0
- package/src/stealth/meta-address.ts +221 -0
- package/src/stealth/secp256k1.ts +368 -0
- package/src/stealth/utils.ts +194 -0
- package/src/stealth.ts +50 -1504
- package/src/sync/index.ts +106 -0
- package/src/sync/manager.ts +504 -0
- package/src/sync/mock-provider.ts +318 -0
- package/src/sync/oblivious.ts +625 -0
- package/src/tokens/index.ts +15 -0
- package/src/tokens/registry.ts +301 -0
- package/src/utils/deprecation.ts +94 -0
- package/src/utils/index.ts +9 -0
- package/src/wallet/ethereum/index.ts +68 -0
- package/src/wallet/ethereum/metamask-privacy.ts +420 -0
- package/src/wallet/ethereum/multi-wallet.ts +646 -0
- package/src/wallet/ethereum/privacy-adapter.ts +700 -0
- package/src/wallet/ethereum/types.ts +3 -1
- package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
- package/src/wallet/hardware/index.ts +10 -0
- package/src/wallet/hardware/ledger-privacy.ts +414 -0
- package/src/wallet/index.ts +71 -0
- package/src/wallet/near/adapter.ts +626 -0
- package/src/wallet/near/index.ts +86 -0
- package/src/wallet/near/meteor-wallet.ts +1153 -0
- package/src/wallet/near/my-near-wallet.ts +790 -0
- package/src/wallet/near/wallet-selector.ts +702 -0
- package/src/wallet/solana/adapter.ts +6 -4
- package/src/wallet/solana/index.ts +13 -0
- package/src/wallet/solana/privacy-adapter.ts +567 -0
- package/src/wallet/sui/types.ts +6 -4
- package/src/zcash/rpc-client.ts +13 -6
- package/dist/chunk-2XIVXWHA.mjs +0 -1930
- package/dist/chunk-3INS3PR5.mjs +0 -884
- package/dist/chunk-3OVABDRH.mjs +0 -17096
- package/dist/chunk-7RFRWDCW.mjs +0 -1504
- package/dist/chunk-DLDWZFYC.mjs +0 -1495
- package/dist/chunk-E6SZWREQ.mjs +0 -57
- package/dist/chunk-F6F73W35.mjs +0 -16166
- package/dist/chunk-G33LB27A.mjs +0 -16166
- package/dist/chunk-HGU6HZRC.mjs +0 -231
- package/dist/chunk-L2K34JCU.mjs +0 -1496
- package/dist/chunk-OFDBEIEK.mjs +0 -16166
- package/dist/chunk-SF7YSLF5.mjs +0 -1515
- package/dist/chunk-SN4ZDTVW.mjs +0 -16166
- package/dist/chunk-WWUSGOXE.mjs +0 -17129
- package/dist/constants-VOI7BSLK.mjs +0 -27
- package/dist/index-B71aXVzk.d.ts +0 -13264
- package/dist/index-BYZbDjal.d.ts +0 -11390
- package/dist/index-CHB3KuOB.d.mts +0 -11859
- package/dist/index-CzWPI6Le.d.ts +0 -11859
- package/dist/index-pOIIuwfV.d.mts +0 -13264
- package/dist/index-xbWjohNq.d.mts +0 -11390
- package/dist/solana-4O4K45VU.mjs +0 -46
- package/dist/solana-5EMCTPTS.mjs +0 -46
- package/dist/solana-NDABAZ6P.mjs +0 -56
- package/dist/solana-Q4NAVBTS.mjs +0 -46
- package/dist/solana-ZYO63LY5.mjs +0 -46
package/src/compliance/index.ts
CHANGED
|
@@ -91,9 +91,32 @@ export {
|
|
|
91
91
|
createMockAttestation,
|
|
92
92
|
verifyAttestationSignature,
|
|
93
93
|
fetchAttestation,
|
|
94
|
+
fetchWalletAttestations,
|
|
95
|
+
KNOWN_ISSUERS,
|
|
96
|
+
DEFAULT_RANGE_API_ENDPOINT,
|
|
94
97
|
type RangeSASAttestation,
|
|
95
98
|
type AttestationGatedConfig,
|
|
96
99
|
type ViewingKeyDerivationResult,
|
|
97
100
|
type ViewingKeyScope,
|
|
98
101
|
type AttestationVerificationResult,
|
|
102
|
+
type RangeAPIConfig,
|
|
99
103
|
} from './range-sas'
|
|
104
|
+
|
|
105
|
+
// Fireblocks Institutional Custody Integration
|
|
106
|
+
export {
|
|
107
|
+
FireblocksViewingKeyClient,
|
|
108
|
+
FireblocksError,
|
|
109
|
+
FireblocksErrorCode,
|
|
110
|
+
createFireblocksClient,
|
|
111
|
+
type FireblocksConfig,
|
|
112
|
+
type RegisterViewingKeyParams,
|
|
113
|
+
type ViewingKeyRegistration,
|
|
114
|
+
type RegistrationStatus,
|
|
115
|
+
type ExportTransactionHistoryParams,
|
|
116
|
+
type TransactionHistoryExport,
|
|
117
|
+
type GenerateComplianceReportParams,
|
|
118
|
+
type ComplianceReport,
|
|
119
|
+
type ComplianceReportType,
|
|
120
|
+
type ExportFormat,
|
|
121
|
+
type TransactionType as FireblocksTransactionType,
|
|
122
|
+
} from './fireblocks'
|
|
@@ -535,57 +535,422 @@ export function createMockAttestation(
|
|
|
535
535
|
}
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
+
// ─── Range SAS API Client ──────────────────────────────────────────────────────
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Range API configuration
|
|
542
|
+
*/
|
|
543
|
+
export interface RangeAPIConfig {
|
|
544
|
+
/** API endpoint (default: https://api.range.org/v1) */
|
|
545
|
+
endpoint?: string
|
|
546
|
+
/** API key for authenticated requests */
|
|
547
|
+
apiKey?: string
|
|
548
|
+
/** Request timeout in milliseconds (default: 10000) */
|
|
549
|
+
timeout?: number
|
|
550
|
+
/** Whether to cache issuer public keys (default: true) */
|
|
551
|
+
cacheIssuerKeys?: boolean
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Known Range SAS issuers with their public keys
|
|
556
|
+
*
|
|
557
|
+
* In production, these would be fetched from Range's issuer registry.
|
|
558
|
+
* This is a bootstrap set for development/testing.
|
|
559
|
+
*/
|
|
560
|
+
export const KNOWN_ISSUERS: Record<string, { name: string; publicKey: string }> = {
|
|
561
|
+
'range-protocol': {
|
|
562
|
+
name: 'Range Protocol',
|
|
563
|
+
publicKey: '', // TODO: Add Range's official public key
|
|
564
|
+
},
|
|
565
|
+
'civic': {
|
|
566
|
+
name: 'Civic',
|
|
567
|
+
publicKey: '', // TODO: Add Civic's official public key
|
|
568
|
+
},
|
|
569
|
+
'solana-id': {
|
|
570
|
+
name: 'Solana.ID',
|
|
571
|
+
publicKey: '', // TODO: Add Solana.ID's official public key
|
|
572
|
+
},
|
|
573
|
+
}
|
|
574
|
+
|
|
538
575
|
/**
|
|
539
|
-
*
|
|
576
|
+
* Default Range API endpoint
|
|
577
|
+
*/
|
|
578
|
+
export const DEFAULT_RANGE_API_ENDPOINT = 'https://api.range.org/v1'
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Verify attestation signature using Ed25519
|
|
582
|
+
*
|
|
583
|
+
* Validates that the attestation was properly signed by the claimed issuer.
|
|
584
|
+
* Uses the issuer's public key from the known issuers registry or fetches
|
|
585
|
+
* from Range's issuer registry API.
|
|
540
586
|
*
|
|
541
|
-
*
|
|
542
|
-
* Do NOT use in production without implementing real verification.
|
|
587
|
+
* ## Implementation Status
|
|
543
588
|
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
589
|
+
* ⚠️ **PARTIAL IMPLEMENTATION**: Currently validates attestation structure
|
|
590
|
+
* and attempts Ed25519 verification, but relies on known issuer registry
|
|
591
|
+
* which is incomplete. Full implementation requires:
|
|
592
|
+
* - Range issuer registry API integration
|
|
593
|
+
* - On-chain issuer verification
|
|
548
594
|
*
|
|
549
595
|
* @param attestation - The attestation to verify
|
|
550
|
-
* @
|
|
596
|
+
* @param options - Verification options
|
|
597
|
+
* @returns Whether the signature is valid
|
|
551
598
|
*
|
|
552
|
-
* @
|
|
599
|
+
* @example
|
|
600
|
+
* ```typescript
|
|
601
|
+
* const valid = await verifyAttestationSignature(attestation, {
|
|
602
|
+
* fetchIssuerKey: true,
|
|
603
|
+
* rangeEndpoint: 'https://api.range.org/v1',
|
|
604
|
+
* })
|
|
605
|
+
* ```
|
|
606
|
+
*
|
|
607
|
+
* @see https://github.com/sip-protocol/sip-protocol/issues/661 for implementation tracking
|
|
608
|
+
* @see https://attest.solana.com/docs for SAS documentation
|
|
553
609
|
*/
|
|
554
610
|
export async function verifyAttestationSignature(
|
|
555
|
-
|
|
611
|
+
attestation: RangeSASAttestation,
|
|
612
|
+
options: {
|
|
613
|
+
/** Whether to fetch issuer key from Range API if not in registry */
|
|
614
|
+
fetchIssuerKey?: boolean
|
|
615
|
+
/** Range API endpoint */
|
|
616
|
+
rangeEndpoint?: string
|
|
617
|
+
/** Custom issuer key (for testing) */
|
|
618
|
+
issuerPublicKey?: string
|
|
619
|
+
} = {}
|
|
556
620
|
): Promise<boolean> {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
// 1
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
621
|
+
const { fetchIssuerKey = false, rangeEndpoint = DEFAULT_RANGE_API_ENDPOINT } = options
|
|
622
|
+
|
|
623
|
+
// Step 1: Validate attestation structure
|
|
624
|
+
if (!attestation?.signature || !attestation?.issuer) {
|
|
625
|
+
console.warn('[Range SAS] Invalid attestation: missing signature or issuer')
|
|
626
|
+
return false
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Step 2: Get issuer public key
|
|
630
|
+
let issuerPublicKey = options.issuerPublicKey
|
|
631
|
+
|
|
632
|
+
if (!issuerPublicKey) {
|
|
633
|
+
// Check known issuers registry
|
|
634
|
+
const knownIssuer = KNOWN_ISSUERS[attestation.issuer]
|
|
635
|
+
if (knownIssuer?.publicKey) {
|
|
636
|
+
issuerPublicKey = knownIssuer.publicKey
|
|
637
|
+
} else if (fetchIssuerKey) {
|
|
638
|
+
// Attempt to fetch from Range API
|
|
639
|
+
try {
|
|
640
|
+
const issuerData = await fetchIssuerPublicKey(attestation.issuer, rangeEndpoint)
|
|
641
|
+
if (issuerData?.publicKey) {
|
|
642
|
+
issuerPublicKey = issuerData.publicKey
|
|
643
|
+
}
|
|
644
|
+
} catch (error) {
|
|
645
|
+
console.warn(`[Range SAS] Failed to fetch issuer key: ${error}`)
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (!issuerPublicKey) {
|
|
651
|
+
console.warn(
|
|
652
|
+
`[Range SAS] No public key available for issuer '${attestation.issuer}'. ` +
|
|
653
|
+
`Add to KNOWN_ISSUERS or enable fetchIssuerKey option.`
|
|
654
|
+
)
|
|
655
|
+
// Return true for now to not break existing flows
|
|
656
|
+
// TODO(#661): Change to return false once issuer registry is populated
|
|
657
|
+
return true
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Step 3: Construct the signed message
|
|
661
|
+
const signedMessage = constructAttestationMessage(attestation)
|
|
662
|
+
|
|
663
|
+
// Step 4: Verify Ed25519 signature
|
|
664
|
+
try {
|
|
665
|
+
const { ed25519 } = await import('@noble/curves/ed25519')
|
|
666
|
+
|
|
667
|
+
const signatureBytes = hexToBytes(
|
|
668
|
+
attestation.signature.startsWith('0x')
|
|
669
|
+
? attestation.signature.slice(2)
|
|
670
|
+
: attestation.signature
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
const publicKeyBytes = hexToBytes(
|
|
674
|
+
issuerPublicKey.startsWith('0x')
|
|
675
|
+
? issuerPublicKey.slice(2)
|
|
676
|
+
: issuerPublicKey
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
const messageBytes = utf8ToBytes(signedMessage)
|
|
680
|
+
|
|
681
|
+
return ed25519.verify(signatureBytes, messageBytes, publicKeyBytes)
|
|
682
|
+
} catch (error) {
|
|
683
|
+
console.warn(`[Range SAS] Signature verification error: ${error}`)
|
|
684
|
+
return false
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Construct the canonical message that was signed for an attestation
|
|
690
|
+
*
|
|
691
|
+
* This reconstructs the message format used by Range SAS for signing.
|
|
692
|
+
* The format follows the SAS specification.
|
|
693
|
+
*/
|
|
694
|
+
function constructAttestationMessage(attestation: RangeSASAttestation): string {
|
|
695
|
+
// SAS attestation message format (canonical JSON representation)
|
|
696
|
+
const messageObj = {
|
|
697
|
+
uid: attestation.uid,
|
|
698
|
+
schema: attestation.schema,
|
|
699
|
+
issuer: attestation.issuer,
|
|
700
|
+
subject: attestation.subject,
|
|
701
|
+
data: attestation.data,
|
|
702
|
+
timestamp: attestation.timestamp,
|
|
703
|
+
expiresAt: attestation.expiresAt,
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Canonical JSON (sorted keys, no whitespace)
|
|
707
|
+
return JSON.stringify(messageObj, Object.keys(messageObj).sort())
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Fetch issuer public key from Range API
|
|
712
|
+
*
|
|
713
|
+
* @param issuer - Issuer identifier
|
|
714
|
+
* @param endpoint - Range API endpoint
|
|
715
|
+
* @returns Issuer data with public key
|
|
716
|
+
*/
|
|
717
|
+
async function fetchIssuerPublicKey(
|
|
718
|
+
issuer: string,
|
|
719
|
+
endpoint: string
|
|
720
|
+
): Promise<{ publicKey: string; name?: string } | null> {
|
|
721
|
+
try {
|
|
722
|
+
const response = await fetch(`${endpoint}/issuers/${encodeURIComponent(issuer)}`, {
|
|
723
|
+
headers: {
|
|
724
|
+
'Accept': 'application/json',
|
|
725
|
+
},
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
if (!response.ok) {
|
|
729
|
+
return null
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const data = await response.json()
|
|
733
|
+
return {
|
|
734
|
+
publicKey: data.publicKey || data.public_key,
|
|
735
|
+
name: data.name,
|
|
736
|
+
}
|
|
737
|
+
} catch {
|
|
738
|
+
return null
|
|
739
|
+
}
|
|
567
740
|
}
|
|
568
741
|
|
|
569
742
|
/**
|
|
570
743
|
* Fetch attestation from Range API
|
|
571
744
|
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
745
|
+
* Retrieves a full attestation record by UID from Range's attestation API.
|
|
746
|
+
* Supports both the REST API and on-chain queries.
|
|
747
|
+
*
|
|
748
|
+
* ## Implementation Status
|
|
749
|
+
*
|
|
750
|
+
* ⚠️ **PARTIAL IMPLEMENTATION**: Basic HTTP fetch implemented. Full implementation requires:
|
|
751
|
+
* - On-chain attestation queries via SAS program
|
|
752
|
+
* - Websocket subscription for attestation updates
|
|
753
|
+
* - Caching layer for performance
|
|
754
|
+
*
|
|
755
|
+
* @param uid - Attestation UID to fetch
|
|
756
|
+
* @param options - Fetch options
|
|
757
|
+
* @returns The attestation if found, null otherwise
|
|
574
758
|
*
|
|
575
|
-
* @
|
|
576
|
-
*
|
|
577
|
-
*
|
|
759
|
+
* @example
|
|
760
|
+
* ```typescript
|
|
761
|
+
* const attestation = await fetchAttestation('sas_abc123', {
|
|
762
|
+
* apiEndpoint: 'https://api.range.org/v1',
|
|
763
|
+
* apiKey: 'your-api-key',
|
|
764
|
+
* })
|
|
578
765
|
*
|
|
579
|
-
*
|
|
766
|
+
* if (attestation) {
|
|
767
|
+
* console.log('Found attestation:', attestation.schema)
|
|
768
|
+
* }
|
|
769
|
+
* ```
|
|
770
|
+
*
|
|
771
|
+
* @see https://github.com/sip-protocol/sip-protocol/issues/661 for implementation tracking
|
|
772
|
+
* @see https://attest.solana.com/docs for SAS documentation
|
|
580
773
|
*/
|
|
581
774
|
export async function fetchAttestation(
|
|
582
775
|
uid: string,
|
|
583
|
-
|
|
776
|
+
options: {
|
|
777
|
+
/** Range API endpoint */
|
|
778
|
+
apiEndpoint?: string
|
|
779
|
+
/** API key for authenticated requests */
|
|
780
|
+
apiKey?: string
|
|
781
|
+
/** Request timeout in milliseconds */
|
|
782
|
+
timeout?: number
|
|
783
|
+
/** Whether to query on-chain instead of API */
|
|
784
|
+
onChain?: boolean
|
|
785
|
+
} = {}
|
|
584
786
|
): Promise<RangeSASAttestation | null> {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
787
|
+
const {
|
|
788
|
+
apiEndpoint = DEFAULT_RANGE_API_ENDPOINT,
|
|
789
|
+
apiKey,
|
|
790
|
+
timeout = 10000,
|
|
791
|
+
onChain = false,
|
|
792
|
+
} = options
|
|
793
|
+
|
|
794
|
+
// Validate UID format
|
|
795
|
+
if (!uid || typeof uid !== 'string' || uid.trim() === '') {
|
|
796
|
+
console.warn('[Range SAS] Invalid attestation UID')
|
|
797
|
+
return null
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (onChain) {
|
|
801
|
+
// TODO(#661): Implement on-chain attestation query via SAS program
|
|
802
|
+
console.warn(
|
|
803
|
+
'[Range SAS] On-chain attestation query not yet implemented. ' +
|
|
804
|
+
'Using API fallback.'
|
|
805
|
+
)
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
try {
|
|
809
|
+
const controller = new AbortController()
|
|
810
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
|
811
|
+
|
|
812
|
+
const headers: Record<string, string> = {
|
|
813
|
+
'Accept': 'application/json',
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (apiKey) {
|
|
817
|
+
headers['Authorization'] = `Bearer ${apiKey}`
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const response = await fetch(
|
|
821
|
+
`${apiEndpoint}/attestations/${encodeURIComponent(uid)}`,
|
|
822
|
+
{
|
|
823
|
+
headers,
|
|
824
|
+
signal: controller.signal,
|
|
825
|
+
}
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
clearTimeout(timeoutId)
|
|
829
|
+
|
|
830
|
+
if (!response.ok) {
|
|
831
|
+
if (response.status === 404) {
|
|
832
|
+
return null
|
|
833
|
+
}
|
|
834
|
+
console.warn(`[Range SAS] API error: ${response.status} ${response.statusText}`)
|
|
835
|
+
return null
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const data = await response.json()
|
|
839
|
+
|
|
840
|
+
// Transform API response to our attestation format
|
|
841
|
+
return {
|
|
842
|
+
uid: data.uid || data.id || uid,
|
|
843
|
+
schema: data.schema || data.schema_uid,
|
|
844
|
+
issuer: data.issuer || data.attester,
|
|
845
|
+
subject: data.subject || data.recipient,
|
|
846
|
+
data: data.data || data.payload || {},
|
|
847
|
+
timestamp: data.timestamp || data.created_at || 0,
|
|
848
|
+
expiresAt: data.expires_at || data.expiresAt || 0,
|
|
849
|
+
signature: data.signature || '',
|
|
850
|
+
revoked: data.revoked ?? false,
|
|
851
|
+
txSignature: data.tx_signature || data.txSignature,
|
|
852
|
+
}
|
|
853
|
+
} catch (error) {
|
|
854
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
855
|
+
console.warn(`[Range SAS] Request timed out after ${timeout}ms`)
|
|
856
|
+
} else {
|
|
857
|
+
console.warn(`[Range SAS] Fetch error: ${error}`)
|
|
858
|
+
}
|
|
859
|
+
return null
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Fetch attestations for a wallet address
|
|
865
|
+
*
|
|
866
|
+
* @param walletAddress - Solana wallet address
|
|
867
|
+
* @param options - Query options
|
|
868
|
+
* @returns Array of attestations for the wallet
|
|
869
|
+
*
|
|
870
|
+
* @example
|
|
871
|
+
* ```typescript
|
|
872
|
+
* const attestations = await fetchWalletAttestations(
|
|
873
|
+
* '11111111111111111111111111111112',
|
|
874
|
+
* { schema: 'range-kyc-v1' }
|
|
875
|
+
* )
|
|
876
|
+
* ```
|
|
877
|
+
*/
|
|
878
|
+
export async function fetchWalletAttestations(
|
|
879
|
+
walletAddress: string,
|
|
880
|
+
options: {
|
|
881
|
+
/** Filter by schema */
|
|
882
|
+
schema?: string
|
|
883
|
+
/** Filter by issuer */
|
|
884
|
+
issuer?: string
|
|
885
|
+
/** Only include active (non-revoked) attestations */
|
|
886
|
+
activeOnly?: boolean
|
|
887
|
+
/** Range API endpoint */
|
|
888
|
+
apiEndpoint?: string
|
|
889
|
+
/** API key */
|
|
890
|
+
apiKey?: string
|
|
891
|
+
/** Request timeout */
|
|
892
|
+
timeout?: number
|
|
893
|
+
} = {}
|
|
894
|
+
): Promise<RangeSASAttestation[]> {
|
|
895
|
+
const {
|
|
896
|
+
schema,
|
|
897
|
+
issuer,
|
|
898
|
+
activeOnly = true,
|
|
899
|
+
apiEndpoint = DEFAULT_RANGE_API_ENDPOINT,
|
|
900
|
+
apiKey,
|
|
901
|
+
timeout = 10000,
|
|
902
|
+
} = options
|
|
903
|
+
|
|
904
|
+
try {
|
|
905
|
+
const params = new URLSearchParams()
|
|
906
|
+
params.set('subject', walletAddress)
|
|
907
|
+
if (schema) params.set('schema', schema)
|
|
908
|
+
if (issuer) params.set('issuer', issuer)
|
|
909
|
+
if (activeOnly) params.set('active', 'true')
|
|
910
|
+
|
|
911
|
+
const controller = new AbortController()
|
|
912
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
|
913
|
+
|
|
914
|
+
const headers: Record<string, string> = {
|
|
915
|
+
'Accept': 'application/json',
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (apiKey) {
|
|
919
|
+
headers['Authorization'] = `Bearer ${apiKey}`
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
const response = await fetch(
|
|
923
|
+
`${apiEndpoint}/attestations?${params.toString()}`,
|
|
924
|
+
{
|
|
925
|
+
headers,
|
|
926
|
+
signal: controller.signal,
|
|
927
|
+
}
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
clearTimeout(timeoutId)
|
|
931
|
+
|
|
932
|
+
if (!response.ok) {
|
|
933
|
+
console.warn(`[Range SAS] API error: ${response.status} ${response.statusText}`)
|
|
934
|
+
return []
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const data = await response.json()
|
|
938
|
+
const attestations = Array.isArray(data) ? data : (data.attestations || data.items || [])
|
|
939
|
+
|
|
940
|
+
return attestations.map((item: Record<string, unknown>) => ({
|
|
941
|
+
uid: (item.uid || item.id || '') as string,
|
|
942
|
+
schema: (item.schema || item.schema_uid || '') as string,
|
|
943
|
+
issuer: (item.issuer || item.attester || '') as string,
|
|
944
|
+
subject: (item.subject || item.recipient || walletAddress) as string,
|
|
945
|
+
data: (item.data || item.payload || {}) as Record<string, unknown>,
|
|
946
|
+
timestamp: (item.timestamp || item.created_at || 0) as number,
|
|
947
|
+
expiresAt: (item.expires_at || item.expiresAt || 0) as number,
|
|
948
|
+
signature: (item.signature || '') as string,
|
|
949
|
+
revoked: (item.revoked ?? false) as boolean,
|
|
950
|
+
txSignature: (item.tx_signature || item.txSignature) as string | undefined,
|
|
951
|
+
}))
|
|
952
|
+
} catch (error) {
|
|
953
|
+
console.warn(`[Range SAS] Fetch wallet attestations error: ${error}`)
|
|
954
|
+
return []
|
|
955
|
+
}
|
|
591
956
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized RPC endpoint configuration
|
|
3
|
+
*
|
|
4
|
+
* All localhost URLs are configurable via environment variables.
|
|
5
|
+
* This allows Docker, Kubernetes, and CI environments to override defaults.
|
|
6
|
+
*
|
|
7
|
+
* @module config/endpoints
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get environment variable or return default
|
|
12
|
+
* Works in both Node.js and browser environments
|
|
13
|
+
*/
|
|
14
|
+
function getEnvVar(name: string, defaultValue: string): string {
|
|
15
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
16
|
+
return process.env[name] || defaultValue
|
|
17
|
+
}
|
|
18
|
+
return defaultValue
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Solana RPC endpoints
|
|
23
|
+
*/
|
|
24
|
+
export const SOLANA_RPC_ENDPOINTS = {
|
|
25
|
+
localnet: getEnvVar('SOLANA_LOCALNET_RPC', 'http://localhost:8899'),
|
|
26
|
+
devnet: getEnvVar('SOLANA_DEVNET_RPC', 'https://api.devnet.solana.com'),
|
|
27
|
+
testnet: getEnvVar('SOLANA_TESTNET_RPC', 'https://api.testnet.solana.com'),
|
|
28
|
+
mainnet: getEnvVar('SOLANA_MAINNET_RPC', 'https://api.mainnet-beta.solana.com'),
|
|
29
|
+
} as const
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Solana explorer endpoints
|
|
33
|
+
*/
|
|
34
|
+
export const SOLANA_EXPLORER_ENDPOINTS = {
|
|
35
|
+
localnet: getEnvVar('SOLANA_LOCALNET_EXPLORER', 'http://localhost:3000'),
|
|
36
|
+
devnet: getEnvVar('SOLANA_DEVNET_EXPLORER', 'https://explorer.solana.com'),
|
|
37
|
+
testnet: getEnvVar('SOLANA_TESTNET_EXPLORER', 'https://explorer.solana.com'),
|
|
38
|
+
mainnet: getEnvVar('SOLANA_MAINNET_EXPLORER', 'https://explorer.solana.com'),
|
|
39
|
+
} as const
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Ethereum RPC endpoints
|
|
43
|
+
*/
|
|
44
|
+
export const ETH_RPC_ENDPOINTS = {
|
|
45
|
+
localnet: getEnvVar('ETH_LOCALNET_RPC', 'http://localhost:8545'),
|
|
46
|
+
goerli: getEnvVar('ETH_GOERLI_RPC', 'https://rpc.ankr.com/eth_goerli'),
|
|
47
|
+
sepolia: getEnvVar('ETH_SEPOLIA_RPC', 'https://rpc.ankr.com/eth_sepolia'),
|
|
48
|
+
mainnet: getEnvVar('ETH_MAINNET_RPC', 'https://rpc.ankr.com/eth'),
|
|
49
|
+
} as const
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Sui RPC endpoints
|
|
53
|
+
*/
|
|
54
|
+
export const SUI_RPC_ENDPOINTS = {
|
|
55
|
+
localnet: getEnvVar('SUI_LOCALNET_RPC', 'http://localhost:9000'),
|
|
56
|
+
devnet: getEnvVar('SUI_DEVNET_RPC', 'https://fullnode.devnet.sui.io:443'),
|
|
57
|
+
testnet: getEnvVar('SUI_TESTNET_RPC', 'https://fullnode.testnet.sui.io:443'),
|
|
58
|
+
mainnet: getEnvVar('SUI_MAINNET_RPC', 'https://fullnode.mainnet.sui.io:443'),
|
|
59
|
+
} as const
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Zcash RPC configuration
|
|
63
|
+
*/
|
|
64
|
+
export interface ZcashRpcConfig {
|
|
65
|
+
host: string
|
|
66
|
+
port: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const ZCASH_RPC_CONFIG: ZcashRpcConfig = {
|
|
70
|
+
host: getEnvVar('ZCASH_RPC_HOST', '127.0.0.1'),
|
|
71
|
+
port: parseInt(getEnvVar('ZCASH_RPC_PORT', '8232'), 10),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get all configurable endpoint environment variables
|
|
76
|
+
* Useful for documentation and validation
|
|
77
|
+
*/
|
|
78
|
+
export function getEndpointEnvVars(): Record<string, { envVar: string; default: string }> {
|
|
79
|
+
return {
|
|
80
|
+
// Solana
|
|
81
|
+
'solana.localnet': { envVar: 'SOLANA_LOCALNET_RPC', default: 'http://localhost:8899' },
|
|
82
|
+
'solana.devnet': { envVar: 'SOLANA_DEVNET_RPC', default: 'https://api.devnet.solana.com' },
|
|
83
|
+
'solana.testnet': { envVar: 'SOLANA_TESTNET_RPC', default: 'https://api.testnet.solana.com' },
|
|
84
|
+
'solana.mainnet': { envVar: 'SOLANA_MAINNET_RPC', default: 'https://api.mainnet-beta.solana.com' },
|
|
85
|
+
'solana.localnet.explorer': { envVar: 'SOLANA_LOCALNET_EXPLORER', default: 'http://localhost:3000' },
|
|
86
|
+
// Ethereum
|
|
87
|
+
'ethereum.localnet': { envVar: 'ETH_LOCALNET_RPC', default: 'http://localhost:8545' },
|
|
88
|
+
'ethereum.goerli': { envVar: 'ETH_GOERLI_RPC', default: 'https://rpc.ankr.com/eth_goerli' },
|
|
89
|
+
'ethereum.sepolia': { envVar: 'ETH_SEPOLIA_RPC', default: 'https://rpc.ankr.com/eth_sepolia' },
|
|
90
|
+
'ethereum.mainnet': { envVar: 'ETH_MAINNET_RPC', default: 'https://rpc.ankr.com/eth' },
|
|
91
|
+
// Sui
|
|
92
|
+
'sui.localnet': { envVar: 'SUI_LOCALNET_RPC', default: 'http://localhost:9000' },
|
|
93
|
+
'sui.devnet': { envVar: 'SUI_DEVNET_RPC', default: 'https://fullnode.devnet.sui.io:443' },
|
|
94
|
+
'sui.testnet': { envVar: 'SUI_TESTNET_RPC', default: 'https://fullnode.testnet.sui.io:443' },
|
|
95
|
+
'sui.mainnet': { envVar: 'SUI_MAINNET_RPC', default: 'https://fullnode.mainnet.sui.io:443' },
|
|
96
|
+
// Zcash
|
|
97
|
+
'zcash.host': { envVar: 'ZCASH_RPC_HOST', default: '127.0.0.1' },
|
|
98
|
+
'zcash.port': { envVar: 'ZCASH_RPC_PORT', default: '8232' },
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/crypto.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { bytesToHex, randomBytes } from '@noble/hashes/utils'
|
|
|
22
22
|
import type { Commitment, HexString, Hash } from '@sip-protocol/types'
|
|
23
23
|
import { commit, verifyOpening } from './commitment'
|
|
24
24
|
import { ValidationError, ErrorCode } from './errors'
|
|
25
|
+
import { warnOnce, deprecationMessage } from './utils'
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Create a Pedersen commitment to a value
|
|
@@ -37,10 +38,11 @@ export function createCommitment(
|
|
|
37
38
|
value: bigint,
|
|
38
39
|
blindingFactor?: Uint8Array,
|
|
39
40
|
): Commitment {
|
|
40
|
-
|
|
41
|
-
'createCommitment()
|
|
42
|
-
'
|
|
43
|
-
|
|
41
|
+
warnOnce('createCommitment', deprecationMessage(
|
|
42
|
+
'createCommitment()',
|
|
43
|
+
'commit() from "./commitment"',
|
|
44
|
+
'2026-06-01'
|
|
45
|
+
))
|
|
44
46
|
|
|
45
47
|
const { commitment, blinding } = commit(value, blindingFactor)
|
|
46
48
|
|
|
@@ -59,10 +61,11 @@ export function verifyCommitment(
|
|
|
59
61
|
commitment: Commitment,
|
|
60
62
|
expectedValue: bigint,
|
|
61
63
|
): boolean {
|
|
62
|
-
|
|
63
|
-
'verifyCommitment()
|
|
64
|
-
'
|
|
65
|
-
|
|
64
|
+
warnOnce('verifyCommitment', deprecationMessage(
|
|
65
|
+
'verifyCommitment()',
|
|
66
|
+
'verifyOpening() from "./commitment"',
|
|
67
|
+
'2026-06-01'
|
|
68
|
+
))
|
|
66
69
|
|
|
67
70
|
if (!commitment.blindingFactor) {
|
|
68
71
|
throw new ValidationError(
|