@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
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helius Enhanced Transactions Integration
|
|
3
|
+
*
|
|
4
|
+
* Provides human-readable transaction data with SIP-specific parsing
|
|
5
|
+
* for privacy-preserving display to viewing key holders.
|
|
6
|
+
*
|
|
7
|
+
* @see https://docs.helius.dev/solana-apis/enhanced-transactions
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { HeliusEnhanced } from '@sip-protocol/sdk'
|
|
12
|
+
*
|
|
13
|
+
* const helius = new HeliusEnhanced({
|
|
14
|
+
* apiKey: process.env.HELIUS_API_KEY!,
|
|
15
|
+
* cluster: 'mainnet-beta'
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* // Parse a specific transaction
|
|
19
|
+
* const tx = await helius.parseTransaction('5rfFLBUp5YPr...')
|
|
20
|
+
* console.log(tx.description) // "Alice sent 1.5 SOL to Bob"
|
|
21
|
+
*
|
|
22
|
+
* // Get transaction history with SIP metadata
|
|
23
|
+
* const history = await helius.getTransactionHistory('7xK9...', {
|
|
24
|
+
* type: 'TRANSFER',
|
|
25
|
+
* limit: 50
|
|
26
|
+
* })
|
|
27
|
+
*
|
|
28
|
+
* // Get human-readable summaries for UI
|
|
29
|
+
* const summaries = await helius.getTransactionSummaries('7xK9...', {
|
|
30
|
+
* viewingPrivateKey: myViewingKey
|
|
31
|
+
* })
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { ValidationError, NetworkError } from '../../../errors'
|
|
36
|
+
import {
|
|
37
|
+
SOLANA_ADDRESS_MIN_LENGTH,
|
|
38
|
+
SOLANA_ADDRESS_MAX_LENGTH,
|
|
39
|
+
HELIUS_API_KEY_MIN_LENGTH,
|
|
40
|
+
sanitizeUrl,
|
|
41
|
+
SIP_MEMO_PREFIX,
|
|
42
|
+
getExplorerUrl,
|
|
43
|
+
} from '../constants'
|
|
44
|
+
import type {
|
|
45
|
+
EnhancedTransaction,
|
|
46
|
+
SIPEnhancedTransaction,
|
|
47
|
+
SIPTransactionMetadata,
|
|
48
|
+
GetTransactionHistoryOptions,
|
|
49
|
+
PrivacyDisplayOptions,
|
|
50
|
+
TransactionSummary,
|
|
51
|
+
EnhancedTransactionType,
|
|
52
|
+
} from './helius-enhanced-types'
|
|
53
|
+
|
|
54
|
+
/** Default fetch timeout in milliseconds */
|
|
55
|
+
const DEFAULT_FETCH_TIMEOUT_MS = 30000
|
|
56
|
+
|
|
57
|
+
/** Maximum transactions per parse request */
|
|
58
|
+
const MAX_PARSE_BATCH_SIZE = 100
|
|
59
|
+
|
|
60
|
+
/** Default history limit */
|
|
61
|
+
const DEFAULT_HISTORY_LIMIT = 100
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Mask API key for safe logging/error messages
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
function maskApiKey(apiKey: string): string {
|
|
68
|
+
if (apiKey.length <= HELIUS_API_KEY_MIN_LENGTH) return '***'
|
|
69
|
+
return `${apiKey.slice(0, 4)}...${apiKey.slice(-4)}`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Fetch with configurable timeout
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
async function fetchWithTimeout(
|
|
77
|
+
url: string,
|
|
78
|
+
options: RequestInit,
|
|
79
|
+
timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS
|
|
80
|
+
): Promise<Response> {
|
|
81
|
+
const controller = new AbortController()
|
|
82
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const response = await fetch(url, {
|
|
86
|
+
...options,
|
|
87
|
+
signal: controller.signal,
|
|
88
|
+
})
|
|
89
|
+
return response
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
92
|
+
throw new NetworkError(
|
|
93
|
+
`Request timeout after ${timeoutMs}ms`,
|
|
94
|
+
undefined,
|
|
95
|
+
{ endpoint: sanitizeUrl(url) }
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
throw error
|
|
99
|
+
} finally {
|
|
100
|
+
clearTimeout(timeoutId)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Helius Enhanced Transactions configuration
|
|
106
|
+
*/
|
|
107
|
+
export interface HeliusEnhancedConfig {
|
|
108
|
+
/** Helius API key (required) */
|
|
109
|
+
apiKey: string
|
|
110
|
+
/** Solana cluster (default: mainnet-beta) */
|
|
111
|
+
cluster?: 'mainnet-beta' | 'devnet'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Helius Enhanced Transactions Provider
|
|
116
|
+
*
|
|
117
|
+
* Extends the base Helius provider with Enhanced Transactions API
|
|
118
|
+
* for human-readable transaction parsing and SIP-specific metadata extraction.
|
|
119
|
+
*/
|
|
120
|
+
export class HeliusEnhanced {
|
|
121
|
+
private readonly apiKey: string
|
|
122
|
+
private readonly cluster: 'mainnet-beta' | 'devnet'
|
|
123
|
+
private readonly baseUrl: string
|
|
124
|
+
|
|
125
|
+
constructor(config: HeliusEnhancedConfig) {
|
|
126
|
+
// Validate API key
|
|
127
|
+
if (!config.apiKey) {
|
|
128
|
+
throw new ValidationError(
|
|
129
|
+
'Helius API key is required. Get one at https://dev.helius.xyz',
|
|
130
|
+
'apiKey'
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (typeof config.apiKey !== 'string' || config.apiKey.length < HELIUS_API_KEY_MIN_LENGTH) {
|
|
135
|
+
throw new ValidationError('Invalid Helius API key format', 'apiKey')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.apiKey = config.apiKey
|
|
139
|
+
this.cluster = config.cluster ?? 'mainnet-beta'
|
|
140
|
+
|
|
141
|
+
// REST endpoint for Enhanced Transactions API
|
|
142
|
+
this.baseUrl = this.cluster === 'devnet'
|
|
143
|
+
? 'https://api-devnet.helius.xyz/v0'
|
|
144
|
+
: 'https://api.helius.xyz/v0'
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Parse one or more transactions into human-readable format
|
|
149
|
+
*
|
|
150
|
+
* @param signatures - Transaction signature(s) to parse
|
|
151
|
+
* @returns Array of enhanced transactions with human-readable data
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```typescript
|
|
155
|
+
* const txs = await helius.parseTransactions(['5rfFLBUp5YPr...', 'abc123...'])
|
|
156
|
+
* for (const tx of txs) {
|
|
157
|
+
* console.log(`${tx.type}: ${tx.description}`)
|
|
158
|
+
* }
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
async parseTransactions(
|
|
162
|
+
signatures: string | string[]
|
|
163
|
+
): Promise<EnhancedTransaction[]> {
|
|
164
|
+
const sigs = Array.isArray(signatures) ? signatures : [signatures]
|
|
165
|
+
|
|
166
|
+
// Validate signatures
|
|
167
|
+
if (sigs.length === 0) {
|
|
168
|
+
throw new ValidationError('At least one signature is required', 'signatures')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (sigs.length > MAX_PARSE_BATCH_SIZE) {
|
|
172
|
+
throw new ValidationError(
|
|
173
|
+
`Maximum ${MAX_PARSE_BATCH_SIZE} transactions per request`,
|
|
174
|
+
'signatures'
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (const sig of sigs) {
|
|
179
|
+
if (!sig || typeof sig !== 'string' || sig.length < 32) {
|
|
180
|
+
throw new ValidationError(`Invalid signature format: ${sig?.slice(0, 10)}...`, 'signatures')
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const url = `${this.baseUrl}/transactions`
|
|
185
|
+
|
|
186
|
+
const response = await fetchWithTimeout(url, {
|
|
187
|
+
method: 'POST',
|
|
188
|
+
headers: {
|
|
189
|
+
'Content-Type': 'application/json',
|
|
190
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
191
|
+
},
|
|
192
|
+
body: JSON.stringify({
|
|
193
|
+
transactions: sigs,
|
|
194
|
+
}),
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
throw new NetworkError(
|
|
199
|
+
`Helius Enhanced API error: ${response.status} ${response.statusText} (key: ${maskApiKey(this.apiKey)})`,
|
|
200
|
+
undefined,
|
|
201
|
+
{ endpoint: sanitizeUrl(url), statusCode: response.status }
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const data = await response.json() as EnhancedTransaction[]
|
|
206
|
+
return data
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Parse a single transaction
|
|
211
|
+
*
|
|
212
|
+
* Convenience method for parsing a single transaction.
|
|
213
|
+
*
|
|
214
|
+
* @param signature - Transaction signature
|
|
215
|
+
* @returns Enhanced transaction or null if not found
|
|
216
|
+
*/
|
|
217
|
+
async parseTransaction(signature: string): Promise<EnhancedTransaction | null> {
|
|
218
|
+
const results = await this.parseTransactions([signature])
|
|
219
|
+
return results[0] ?? null
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get transaction history for an address
|
|
224
|
+
*
|
|
225
|
+
* Retrieves parsed transaction history with optional type filtering.
|
|
226
|
+
*
|
|
227
|
+
* @param address - Solana address
|
|
228
|
+
* @param options - Filter and pagination options
|
|
229
|
+
* @returns Array of enhanced transactions
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* // Get all transfers
|
|
234
|
+
* const transfers = await helius.getTransactionHistory(address, {
|
|
235
|
+
* type: 'TRANSFER',
|
|
236
|
+
* limit: 50
|
|
237
|
+
* })
|
|
238
|
+
*
|
|
239
|
+
* // Get all swaps
|
|
240
|
+
* const swaps = await helius.getTransactionHistory(address, {
|
|
241
|
+
* type: 'SWAP'
|
|
242
|
+
* })
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
async getTransactionHistory(
|
|
246
|
+
address: string,
|
|
247
|
+
options: GetTransactionHistoryOptions = {}
|
|
248
|
+
): Promise<EnhancedTransaction[]> {
|
|
249
|
+
// Validate address
|
|
250
|
+
if (!address || typeof address !== 'string') {
|
|
251
|
+
throw new ValidationError('address is required', 'address')
|
|
252
|
+
}
|
|
253
|
+
if (address.length < SOLANA_ADDRESS_MIN_LENGTH || address.length > SOLANA_ADDRESS_MAX_LENGTH) {
|
|
254
|
+
throw new ValidationError('invalid Solana address format', 'address')
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Build URL with query parameters
|
|
258
|
+
const params = new URLSearchParams()
|
|
259
|
+
if (options.type) {
|
|
260
|
+
params.set('type', options.type)
|
|
261
|
+
}
|
|
262
|
+
if (options.limit) {
|
|
263
|
+
params.set('limit', Math.min(options.limit, DEFAULT_HISTORY_LIMIT).toString())
|
|
264
|
+
}
|
|
265
|
+
if (options.before) {
|
|
266
|
+
params.set('before', options.before)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const queryString = params.toString()
|
|
270
|
+
const url = `${this.baseUrl}/addresses/${address}/transactions${queryString ? `?${queryString}` : ''}`
|
|
271
|
+
|
|
272
|
+
const response = await fetchWithTimeout(url, {
|
|
273
|
+
method: 'GET',
|
|
274
|
+
headers: {
|
|
275
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
276
|
+
},
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
if (!response.ok) {
|
|
280
|
+
throw new NetworkError(
|
|
281
|
+
`Helius Enhanced API error: ${response.status} ${response.statusText}`,
|
|
282
|
+
undefined,
|
|
283
|
+
{ endpoint: sanitizeUrl(url), statusCode: response.status }
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const data = await response.json() as EnhancedTransaction[]
|
|
288
|
+
return data
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Get SIP-enhanced transactions with metadata extraction
|
|
293
|
+
*
|
|
294
|
+
* Parses transactions and extracts SIP-specific metadata from memo
|
|
295
|
+
* instructions (stealth addresses, view tags, encrypted amounts).
|
|
296
|
+
*
|
|
297
|
+
* @param address - Solana address
|
|
298
|
+
* @param options - Filter and pagination options
|
|
299
|
+
* @returns Transactions with SIP metadata
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* const sipTxs = await helius.getSIPTransactionHistory(address)
|
|
304
|
+
* for (const tx of sipTxs) {
|
|
305
|
+
* if (tx.sipMetadata.isSIPTransaction) {
|
|
306
|
+
* console.log('SIP Transfer:', tx.sipMetadata.stealthAddress)
|
|
307
|
+
* }
|
|
308
|
+
* }
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
async getSIPTransactionHistory(
|
|
312
|
+
address: string,
|
|
313
|
+
options: GetTransactionHistoryOptions = {}
|
|
314
|
+
): Promise<SIPEnhancedTransaction[]> {
|
|
315
|
+
const transactions = await this.getTransactionHistory(address, options)
|
|
316
|
+
|
|
317
|
+
return transactions.map((tx) => ({
|
|
318
|
+
...tx,
|
|
319
|
+
sipMetadata: this.extractSIPMetadata(tx),
|
|
320
|
+
}))
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Extract SIP metadata from a transaction
|
|
325
|
+
*
|
|
326
|
+
* Parses memo program instructions to find SIP announcements.
|
|
327
|
+
* SIP memo format: SIP:1:<ephemeral_pubkey_base58>:<view_tag_hex>
|
|
328
|
+
*
|
|
329
|
+
* @param tx - Enhanced transaction
|
|
330
|
+
* @returns SIP metadata if found
|
|
331
|
+
* @internal
|
|
332
|
+
*/
|
|
333
|
+
private extractSIPMetadata(tx: EnhancedTransaction): SIPTransactionMetadata {
|
|
334
|
+
const metadata: SIPTransactionMetadata = {
|
|
335
|
+
isSIPTransaction: false,
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Look for SIP memo in description or raw data
|
|
339
|
+
const description = tx.description || ''
|
|
340
|
+
|
|
341
|
+
// Check if this looks like a SIP transaction
|
|
342
|
+
// SIP transactions have a memo with format: SIP:1:<ephemeral_pubkey>:<view_tag>
|
|
343
|
+
if (description.includes(SIP_MEMO_PREFIX) || description.includes('SIP:')) {
|
|
344
|
+
metadata.isSIPTransaction = true
|
|
345
|
+
|
|
346
|
+
// Try to extract SIP memo data
|
|
347
|
+
// The memo format is: SIP:1:<ephemeral_pubkey_base58>:<view_tag_hex>
|
|
348
|
+
const sipMemoMatch = description.match(/SIP:1:([A-Za-z0-9]{32,44}):([0-9a-fA-F]{2})/)
|
|
349
|
+
if (sipMemoMatch) {
|
|
350
|
+
metadata.ephemeralPubKey = sipMemoMatch[1]
|
|
351
|
+
metadata.viewTag = parseInt(sipMemoMatch[2], 16)
|
|
352
|
+
metadata.rawMemo = sipMemoMatch[0]
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check token transfers for potential stealth addresses
|
|
357
|
+
if (tx.tokenTransfers && tx.tokenTransfers.length > 0) {
|
|
358
|
+
const transfer = tx.tokenTransfers[0]
|
|
359
|
+
// If this is a SIP transaction, the recipient is likely a stealth address
|
|
360
|
+
if (metadata.isSIPTransaction) {
|
|
361
|
+
metadata.stealthAddress = transfer.toUserAccount
|
|
362
|
+
metadata.tokenMint = transfer.mint
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Check native transfers similarly
|
|
367
|
+
if (!metadata.stealthAddress && tx.nativeTransfers && tx.nativeTransfers.length > 0) {
|
|
368
|
+
const transfer = tx.nativeTransfers[0]
|
|
369
|
+
if (metadata.isSIPTransaction) {
|
|
370
|
+
metadata.stealthAddress = transfer.toUserAccount
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return metadata
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Get human-readable transaction summaries
|
|
379
|
+
*
|
|
380
|
+
* Provides clean summaries for UI display with privacy-aware formatting.
|
|
381
|
+
* Amounts are only shown to authorized viewers with the viewing key.
|
|
382
|
+
*
|
|
383
|
+
* @param address - Solana address
|
|
384
|
+
* @param options - Display and privacy options
|
|
385
|
+
* @returns Array of transaction summaries
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* ```typescript
|
|
389
|
+
* // Without viewing key - amounts hidden for SIP transactions
|
|
390
|
+
* const summaries = await helius.getTransactionSummaries(address)
|
|
391
|
+
*
|
|
392
|
+
* // With viewing key - full details visible
|
|
393
|
+
* const fullSummaries = await helius.getTransactionSummaries(address, {
|
|
394
|
+
* viewingPrivateKey: myViewingKey
|
|
395
|
+
* })
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
async getTransactionSummaries(
|
|
399
|
+
address: string,
|
|
400
|
+
options: PrivacyDisplayOptions & GetTransactionHistoryOptions = {}
|
|
401
|
+
): Promise<TransactionSummary[]> {
|
|
402
|
+
const sipTxs = await this.getSIPTransactionHistory(address, options)
|
|
403
|
+
|
|
404
|
+
return sipTxs.map((tx) => this.createSummary(tx, address, options))
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Create a human-readable summary from an enhanced transaction
|
|
409
|
+
* @internal
|
|
410
|
+
*/
|
|
411
|
+
private createSummary(
|
|
412
|
+
tx: SIPEnhancedTransaction,
|
|
413
|
+
viewerAddress: string,
|
|
414
|
+
options: PrivacyDisplayOptions
|
|
415
|
+
): TransactionSummary {
|
|
416
|
+
const isAuthorized = this.isAuthorizedViewer(tx, options)
|
|
417
|
+
const isSIP = tx.sipMetadata.isSIPTransaction
|
|
418
|
+
|
|
419
|
+
// Determine transaction direction relative to viewer
|
|
420
|
+
const tokens = this.extractTokenInfo(tx, viewerAddress, isAuthorized || !isSIP)
|
|
421
|
+
|
|
422
|
+
// Create human-readable title
|
|
423
|
+
const title = this.createTitle(tx.type, tokens, isSIP)
|
|
424
|
+
|
|
425
|
+
// Create description
|
|
426
|
+
const description = isSIP && !isAuthorized
|
|
427
|
+
? 'Shielded transaction (viewing key required for details)'
|
|
428
|
+
: tx.description || this.createDescription(tx.type, tokens)
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
signature: tx.signature,
|
|
432
|
+
title,
|
|
433
|
+
description,
|
|
434
|
+
type: tx.type,
|
|
435
|
+
timestamp: new Date(tx.timestamp * 1000),
|
|
436
|
+
feeInSol: tx.fee / 1e9,
|
|
437
|
+
isAuthorizedViewer: isAuthorized,
|
|
438
|
+
tokens,
|
|
439
|
+
status: tx.transactionError ? 'failed' : 'success',
|
|
440
|
+
explorerUrl: getExplorerUrl(tx.signature, this.cluster),
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Check if viewer is authorized to see full transaction details
|
|
446
|
+
* @internal
|
|
447
|
+
*/
|
|
448
|
+
private isAuthorizedViewer(
|
|
449
|
+
tx: SIPEnhancedTransaction,
|
|
450
|
+
options: PrivacyDisplayOptions
|
|
451
|
+
): boolean {
|
|
452
|
+
// If not a SIP transaction, everyone can see details
|
|
453
|
+
if (!tx.sipMetadata.isSIPTransaction) {
|
|
454
|
+
return true
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// If no viewing key provided, not authorized
|
|
458
|
+
if (!options.viewingPrivateKey) {
|
|
459
|
+
return false
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// TODO: Implement actual viewing key verification
|
|
463
|
+
// This would involve:
|
|
464
|
+
// 1. Deriving the shared secret from ephemeral pubkey + viewing private key
|
|
465
|
+
// 2. Computing the expected view tag
|
|
466
|
+
// 3. Comparing with the transaction's view tag
|
|
467
|
+
// For now, we just check if a viewing key was provided
|
|
468
|
+
return !!options.viewingPrivateKey
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Extract token information from transaction
|
|
473
|
+
* @internal
|
|
474
|
+
*/
|
|
475
|
+
private extractTokenInfo(
|
|
476
|
+
tx: SIPEnhancedTransaction,
|
|
477
|
+
viewerAddress: string,
|
|
478
|
+
showAmounts: boolean
|
|
479
|
+
): TransactionSummary['tokens'] {
|
|
480
|
+
const tokens: TransactionSummary['tokens'] = []
|
|
481
|
+
|
|
482
|
+
// Process token transfers
|
|
483
|
+
for (const transfer of tx.tokenTransfers || []) {
|
|
484
|
+
const isIncoming = transfer.toUserAccount === viewerAddress
|
|
485
|
+
const amount = showAmounts
|
|
486
|
+
? this.formatAmount(transfer.tokenAmount, transfer.decimals ?? 0)
|
|
487
|
+
: '***'
|
|
488
|
+
|
|
489
|
+
tokens.push({
|
|
490
|
+
symbol: transfer.tokenSymbol || transfer.mint.slice(0, 4) + '...',
|
|
491
|
+
name: transfer.tokenName,
|
|
492
|
+
amount,
|
|
493
|
+
direction: isIncoming ? 'in' : 'out',
|
|
494
|
+
})
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Process native SOL transfers
|
|
498
|
+
for (const transfer of tx.nativeTransfers || []) {
|
|
499
|
+
const isIncoming = transfer.toUserAccount === viewerAddress
|
|
500
|
+
const amount = showAmounts
|
|
501
|
+
? this.formatAmount(transfer.amount, 9)
|
|
502
|
+
: '***'
|
|
503
|
+
|
|
504
|
+
tokens.push({
|
|
505
|
+
symbol: 'SOL',
|
|
506
|
+
name: 'Solana',
|
|
507
|
+
amount,
|
|
508
|
+
direction: isIncoming ? 'in' : 'out',
|
|
509
|
+
})
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return tokens
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Format amount with proper decimal places
|
|
517
|
+
* @internal
|
|
518
|
+
*/
|
|
519
|
+
private formatAmount(amount: number, decimals: number): string {
|
|
520
|
+
const value = amount / Math.pow(10, decimals)
|
|
521
|
+
// Use appropriate precision based on value
|
|
522
|
+
if (value >= 1000) {
|
|
523
|
+
return value.toLocaleString(undefined, { maximumFractionDigits: 2 })
|
|
524
|
+
} else if (value >= 1) {
|
|
525
|
+
return value.toLocaleString(undefined, { maximumFractionDigits: 4 })
|
|
526
|
+
} else {
|
|
527
|
+
return value.toLocaleString(undefined, { maximumFractionDigits: 6 })
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Create human-readable title
|
|
533
|
+
* @internal
|
|
534
|
+
*/
|
|
535
|
+
private createTitle(
|
|
536
|
+
type: EnhancedTransactionType,
|
|
537
|
+
tokens: TransactionSummary['tokens'],
|
|
538
|
+
isSIP: boolean
|
|
539
|
+
): string {
|
|
540
|
+
const prefix = isSIP ? 'Shielded ' : ''
|
|
541
|
+
|
|
542
|
+
switch (type) {
|
|
543
|
+
case 'TRANSFER':
|
|
544
|
+
if (tokens.length > 0) {
|
|
545
|
+
const token = tokens[0]
|
|
546
|
+
return `${prefix}${token.direction === 'in' ? 'Received' : 'Sent'} ${token.symbol}`
|
|
547
|
+
}
|
|
548
|
+
return `${prefix}Transfer`
|
|
549
|
+
|
|
550
|
+
case 'SWAP':
|
|
551
|
+
return `${prefix}Swap`
|
|
552
|
+
|
|
553
|
+
case 'NFT_SALE':
|
|
554
|
+
return 'NFT Sale'
|
|
555
|
+
|
|
556
|
+
case 'NFT_MINT':
|
|
557
|
+
case 'COMPRESSED_NFT_MINT':
|
|
558
|
+
return 'NFT Mint'
|
|
559
|
+
|
|
560
|
+
case 'ADD_LIQUIDITY':
|
|
561
|
+
return 'Added Liquidity'
|
|
562
|
+
|
|
563
|
+
case 'REMOVE_LIQUIDITY':
|
|
564
|
+
return 'Removed Liquidity'
|
|
565
|
+
|
|
566
|
+
case 'STAKE':
|
|
567
|
+
return 'Staked'
|
|
568
|
+
|
|
569
|
+
case 'UNSTAKE':
|
|
570
|
+
return 'Unstaked'
|
|
571
|
+
|
|
572
|
+
case 'CLAIM_REWARDS':
|
|
573
|
+
return 'Claimed Rewards'
|
|
574
|
+
|
|
575
|
+
default:
|
|
576
|
+
return `${prefix}${type.replace(/_/g, ' ').toLowerCase()}`
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Create human-readable description
|
|
582
|
+
* @internal
|
|
583
|
+
*/
|
|
584
|
+
private createDescription(
|
|
585
|
+
type: EnhancedTransactionType,
|
|
586
|
+
tokens: TransactionSummary['tokens']
|
|
587
|
+
): string {
|
|
588
|
+
switch (type) {
|
|
589
|
+
case 'TRANSFER':
|
|
590
|
+
if (tokens.length > 0) {
|
|
591
|
+
const token = tokens[0]
|
|
592
|
+
const action = token.direction === 'in' ? 'Received' : 'Sent'
|
|
593
|
+
const amountStr = token.amount !== '***' ? `${token.amount} ` : ''
|
|
594
|
+
return `${action} ${amountStr}${token.symbol}`
|
|
595
|
+
}
|
|
596
|
+
return 'Token transfer'
|
|
597
|
+
|
|
598
|
+
case 'SWAP': {
|
|
599
|
+
const inputs = tokens.filter(t => t.direction === 'out')
|
|
600
|
+
const outputs = tokens.filter(t => t.direction === 'in')
|
|
601
|
+
if (inputs.length > 0 && outputs.length > 0) {
|
|
602
|
+
return `Swapped ${inputs[0].symbol} for ${outputs[0].symbol}`
|
|
603
|
+
}
|
|
604
|
+
return 'Token swap'
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
default:
|
|
608
|
+
return `${type.replace(/_/g, ' ').toLowerCase()} transaction`
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Create a HeliusEnhanced instance
|
|
615
|
+
*
|
|
616
|
+
* Factory function for creating enhanced transactions provider.
|
|
617
|
+
*
|
|
618
|
+
* @param config - Provider configuration
|
|
619
|
+
* @returns HeliusEnhanced instance
|
|
620
|
+
*/
|
|
621
|
+
export function createHeliusEnhanced(config: HeliusEnhancedConfig): HeliusEnhanced {
|
|
622
|
+
return new HeliusEnhanced(config)
|
|
623
|
+
}
|