@sip-protocol/sdk 0.7.3 → 0.7.4
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/LICENSE +21 -0
- 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 +54 -21
- 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,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QuickNode RPC Provider
|
|
3
|
+
*
|
|
4
|
+
* Leverages QuickNode's Solana RPC for queries and Yellowstone gRPC
|
|
5
|
+
* (Geyser plugin) for real-time streaming subscriptions.
|
|
6
|
+
*
|
|
7
|
+
* @see https://www.quicknode.com/docs/solana/yellowstone-grpc/overview
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { QuickNodeProvider } from '@sip-protocol/sdk'
|
|
12
|
+
*
|
|
13
|
+
* const quicknode = new QuickNodeProvider({
|
|
14
|
+
* endpoint: 'https://example.solana-mainnet.quiknode.pro/abc123',
|
|
15
|
+
* cluster: 'mainnet-beta'
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* // Query assets (standard RPC)
|
|
19
|
+
* const assets = await quicknode.getAssetsByOwner('7xK9...')
|
|
20
|
+
*
|
|
21
|
+
* // Real-time subscriptions (Yellowstone gRPC)
|
|
22
|
+
* if (quicknode.supportsSubscriptions()) {
|
|
23
|
+
* const unsubscribe = await quicknode.subscribeToTransfers('7xK9...', (asset) => {
|
|
24
|
+
* console.log('Transfer received:', asset)
|
|
25
|
+
* })
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
Connection,
|
|
32
|
+
PublicKey,
|
|
33
|
+
} from '@solana/web3.js'
|
|
34
|
+
import {
|
|
35
|
+
TOKEN_PROGRAM_ID,
|
|
36
|
+
getAssociatedTokenAddress,
|
|
37
|
+
getAccount,
|
|
38
|
+
getMint,
|
|
39
|
+
} from '@solana/spl-token'
|
|
40
|
+
import Client, {
|
|
41
|
+
CommitmentLevel,
|
|
42
|
+
type SubscribeRequest,
|
|
43
|
+
type SubscribeUpdate,
|
|
44
|
+
} from '@triton-one/yellowstone-grpc'
|
|
45
|
+
import type { ClientDuplexStream } from '@grpc/grpc-js'
|
|
46
|
+
import { base58 } from '@scure/base'
|
|
47
|
+
import type { SolanaRPCProvider, TokenAsset, ProviderConfig } from './interface'
|
|
48
|
+
import { sanitizeUrl } from '../constants'
|
|
49
|
+
import { ValidationError, ErrorCode } from '../../../errors'
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Type alias for Yellowstone gRPC subscription stream
|
|
53
|
+
*/
|
|
54
|
+
type GrpcSubscriptionStream = ClientDuplexStream<SubscribeRequest, SubscribeUpdate>
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* QuickNode provider configuration
|
|
58
|
+
*/
|
|
59
|
+
export interface QuickNodeProviderConfig extends ProviderConfig {
|
|
60
|
+
/**
|
|
61
|
+
* QuickNode endpoint URL
|
|
62
|
+
* Format: https://<subdomain>.solana-<cluster>.quiknode.pro/<api-key>
|
|
63
|
+
*/
|
|
64
|
+
endpoint: string
|
|
65
|
+
/**
|
|
66
|
+
* Solana cluster (default: mainnet-beta)
|
|
67
|
+
*/
|
|
68
|
+
cluster?: 'mainnet-beta' | 'devnet'
|
|
69
|
+
/**
|
|
70
|
+
* Enable Yellowstone gRPC for real-time subscriptions
|
|
71
|
+
* Requires Yellowstone add-on on your QuickNode endpoint
|
|
72
|
+
* @default true
|
|
73
|
+
*/
|
|
74
|
+
enableGrpc?: boolean
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validate a Solana address (base58)
|
|
79
|
+
* @throws Error if address is invalid
|
|
80
|
+
*/
|
|
81
|
+
function validateSolanaAddress(address: string, paramName: string): PublicKey {
|
|
82
|
+
try {
|
|
83
|
+
return new PublicKey(address)
|
|
84
|
+
} catch {
|
|
85
|
+
throw new ValidationError('invalid Solana address format', paramName, undefined, ErrorCode.INVALID_ADDRESS)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Extract gRPC endpoint from QuickNode RPC endpoint
|
|
91
|
+
* QuickNode gRPC runs on port 10000
|
|
92
|
+
*/
|
|
93
|
+
function getGrpcEndpoint(rpcEndpoint: string): string {
|
|
94
|
+
const url = new URL(rpcEndpoint)
|
|
95
|
+
// gRPC uses port 10000
|
|
96
|
+
url.port = '10000'
|
|
97
|
+
// Remove trailing slash and query params (API key is in path)
|
|
98
|
+
return url.origin + url.pathname.replace(/\/$/, '')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* QuickNode RPC Provider implementation
|
|
103
|
+
*
|
|
104
|
+
* Uses standard Solana RPC for queries and Yellowstone gRPC
|
|
105
|
+
* for real-time subscriptions. Recommended for applications
|
|
106
|
+
* that need real-time token transfer notifications.
|
|
107
|
+
*/
|
|
108
|
+
export class QuickNodeProvider implements SolanaRPCProvider {
|
|
109
|
+
readonly name = 'quicknode'
|
|
110
|
+
private connection: Connection
|
|
111
|
+
private grpcEndpoint: string
|
|
112
|
+
private grpcEnabled: boolean
|
|
113
|
+
private grpcClient: Client | null = null
|
|
114
|
+
/** Active gRPC subscription streams */
|
|
115
|
+
private activeStreams: Set<GrpcSubscriptionStream> = new Set()
|
|
116
|
+
/** Cache for mint decimals to avoid repeated RPC calls */
|
|
117
|
+
private mintDecimalsCache: Map<string, number> = new Map()
|
|
118
|
+
|
|
119
|
+
constructor(config: QuickNodeProviderConfig) {
|
|
120
|
+
if (!config.endpoint) {
|
|
121
|
+
throw new ValidationError(
|
|
122
|
+
'endpoint is required. Get one at https://www.quicknode.com/endpoints',
|
|
123
|
+
'endpoint',
|
|
124
|
+
undefined,
|
|
125
|
+
ErrorCode.MISSING_REQUIRED
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Validate endpoint URL
|
|
130
|
+
try {
|
|
131
|
+
new URL(config.endpoint)
|
|
132
|
+
} catch {
|
|
133
|
+
// H-2 FIX: Sanitize URL to prevent credential exposure in error messages
|
|
134
|
+
throw new ValidationError(
|
|
135
|
+
`invalid endpoint URL format: ${sanitizeUrl(config.endpoint)}`,
|
|
136
|
+
'endpoint',
|
|
137
|
+
undefined,
|
|
138
|
+
ErrorCode.INVALID_INPUT
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.connection = new Connection(config.endpoint, 'confirmed')
|
|
143
|
+
this.grpcEndpoint = getGrpcEndpoint(config.endpoint)
|
|
144
|
+
this.grpcEnabled = config.enableGrpc !== false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get all token assets owned by an address using standard RPC
|
|
149
|
+
*
|
|
150
|
+
* Uses getParsedTokenAccountsByOwner for comprehensive asset information.
|
|
151
|
+
*/
|
|
152
|
+
async getAssetsByOwner(owner: string): Promise<TokenAsset[]> {
|
|
153
|
+
const ownerPubkey = validateSolanaAddress(owner, 'owner')
|
|
154
|
+
|
|
155
|
+
const accounts = await this.connection.getParsedTokenAccountsByOwner(
|
|
156
|
+
ownerPubkey,
|
|
157
|
+
{ programId: TOKEN_PROGRAM_ID }
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const assets: TokenAsset[] = []
|
|
161
|
+
|
|
162
|
+
for (const { account } of accounts.value) {
|
|
163
|
+
const parsed = account.data.parsed
|
|
164
|
+
if (parsed.type !== 'account') continue
|
|
165
|
+
|
|
166
|
+
const info = parsed.info
|
|
167
|
+
const amount = BigInt(info.tokenAmount.amount)
|
|
168
|
+
|
|
169
|
+
// Skip zero balances
|
|
170
|
+
if (amount === 0n) continue
|
|
171
|
+
|
|
172
|
+
assets.push({
|
|
173
|
+
mint: info.mint,
|
|
174
|
+
amount,
|
|
175
|
+
decimals: info.tokenAmount.decimals,
|
|
176
|
+
// Standard RPC doesn't provide symbol/name, those need metadata lookup
|
|
177
|
+
symbol: undefined,
|
|
178
|
+
name: undefined,
|
|
179
|
+
logoUri: undefined,
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return assets
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get token balance for a specific mint
|
|
188
|
+
*
|
|
189
|
+
* Uses getAccount on the associated token address.
|
|
190
|
+
*/
|
|
191
|
+
async getTokenBalance(owner: string, mint: string): Promise<bigint> {
|
|
192
|
+
const ownerPubkey = validateSolanaAddress(owner, 'owner')
|
|
193
|
+
const mintPubkey = validateSolanaAddress(mint, 'mint')
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const ata = await getAssociatedTokenAddress(
|
|
197
|
+
mintPubkey,
|
|
198
|
+
ownerPubkey,
|
|
199
|
+
true // allowOwnerOffCurve for PDAs
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
const account = await getAccount(this.connection, ata)
|
|
203
|
+
return account.amount
|
|
204
|
+
} catch {
|
|
205
|
+
// Account doesn't exist or other RPC error
|
|
206
|
+
return 0n
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Check if provider supports real-time subscriptions
|
|
212
|
+
*
|
|
213
|
+
* QuickNode supports Yellowstone gRPC for real-time streaming
|
|
214
|
+
* when the add-on is enabled on the endpoint.
|
|
215
|
+
*/
|
|
216
|
+
supportsSubscriptions(): boolean {
|
|
217
|
+
return this.grpcEnabled
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get token decimals from mint metadata with caching
|
|
222
|
+
*
|
|
223
|
+
* @param mint - Token mint address (base58)
|
|
224
|
+
* @returns Token decimals (0-18), defaults to 9 if fetch fails
|
|
225
|
+
*/
|
|
226
|
+
private async getMintDecimals(mint: string): Promise<number> {
|
|
227
|
+
// Check cache first
|
|
228
|
+
const cached = this.mintDecimalsCache.get(mint)
|
|
229
|
+
if (cached !== undefined) {
|
|
230
|
+
return cached
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const mintPubkey = new PublicKey(mint)
|
|
235
|
+
const mintInfo = await getMint(this.connection, mintPubkey)
|
|
236
|
+
const decimals = mintInfo.decimals
|
|
237
|
+
this.mintDecimalsCache.set(mint, decimals)
|
|
238
|
+
return decimals
|
|
239
|
+
} catch {
|
|
240
|
+
// Default to 9 (SOL decimals) if fetch fails
|
|
241
|
+
// This is a safe fallback since most Solana tokens use 9 decimals
|
|
242
|
+
return 9
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Initialize gRPC client lazily
|
|
248
|
+
*/
|
|
249
|
+
private async getGrpcClient(): Promise<Client> {
|
|
250
|
+
if (!this.grpcEnabled) {
|
|
251
|
+
throw new ValidationError(
|
|
252
|
+
'gRPC subscriptions are disabled. Set enableGrpc: true and ensure Yellowstone add-on is enabled',
|
|
253
|
+
'enableGrpc',
|
|
254
|
+
undefined,
|
|
255
|
+
ErrorCode.INVALID_INPUT
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!this.grpcClient) {
|
|
260
|
+
// QuickNode uses the API key from the URL path, no separate token needed
|
|
261
|
+
this.grpcClient = new Client(this.grpcEndpoint, undefined, {})
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return this.grpcClient
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Subscribe to token transfers for an address
|
|
269
|
+
*
|
|
270
|
+
* Uses Yellowstone gRPC to receive real-time notifications when
|
|
271
|
+
* tokens are transferred to the specified address.
|
|
272
|
+
*
|
|
273
|
+
* @param address - Solana address to watch for incoming transfers
|
|
274
|
+
* @param callback - Called when a transfer is detected
|
|
275
|
+
* @returns Unsubscribe function
|
|
276
|
+
*/
|
|
277
|
+
async subscribeToTransfers(
|
|
278
|
+
address: string,
|
|
279
|
+
callback: (asset: TokenAsset) => void
|
|
280
|
+
): Promise<() => void> {
|
|
281
|
+
validateSolanaAddress(address, 'address')
|
|
282
|
+
|
|
283
|
+
const client = await this.getGrpcClient()
|
|
284
|
+
const stream = await client.subscribe()
|
|
285
|
+
|
|
286
|
+
this.activeStreams.add(stream)
|
|
287
|
+
|
|
288
|
+
// Handle incoming data
|
|
289
|
+
stream.on('data', (update) => {
|
|
290
|
+
// Check for account update
|
|
291
|
+
if (update.account?.account) {
|
|
292
|
+
const accountData = update.account.account
|
|
293
|
+
|
|
294
|
+
// Only process token account updates
|
|
295
|
+
if (accountData.owner?.toString() === TOKEN_PROGRAM_ID.toBase58()) {
|
|
296
|
+
try {
|
|
297
|
+
// Parse SPL Token account data
|
|
298
|
+
// Token account structure: mint (32) + owner (32) + amount (8) + ...
|
|
299
|
+
const data = accountData.data
|
|
300
|
+
if (data && data.length >= 72) {
|
|
301
|
+
const mint = new PublicKey(data.slice(0, 32)).toBase58()
|
|
302
|
+
const amount = BigInt(
|
|
303
|
+
'0x' + Buffer.from(data.slice(64, 72)).reverse().toString('hex')
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
if (amount > 0n) {
|
|
307
|
+
// Fetch decimals asynchronously and invoke callback
|
|
308
|
+
this.getMintDecimals(mint).then((decimals) => {
|
|
309
|
+
callback({
|
|
310
|
+
mint,
|
|
311
|
+
amount,
|
|
312
|
+
decimals,
|
|
313
|
+
symbol: undefined,
|
|
314
|
+
name: undefined,
|
|
315
|
+
logoUri: undefined,
|
|
316
|
+
})
|
|
317
|
+
}).catch(() => {
|
|
318
|
+
// Still invoke callback with default decimals on error
|
|
319
|
+
callback({
|
|
320
|
+
mint,
|
|
321
|
+
amount,
|
|
322
|
+
decimals: 9,
|
|
323
|
+
symbol: undefined,
|
|
324
|
+
name: undefined,
|
|
325
|
+
logoUri: undefined,
|
|
326
|
+
})
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
} catch {
|
|
331
|
+
// Skip malformed account data
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
stream.on('error', (err) => {
|
|
338
|
+
console.error('[QuickNodeProvider] gRPC stream error:', err.message)
|
|
339
|
+
this.activeStreams.delete(stream)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
stream.on('end', () => {
|
|
343
|
+
this.activeStreams.delete(stream)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
// Create subscription request for token accounts owned by address
|
|
347
|
+
const request: SubscribeRequest = {
|
|
348
|
+
accounts: {
|
|
349
|
+
stealth: {
|
|
350
|
+
account: [],
|
|
351
|
+
owner: [TOKEN_PROGRAM_ID.toBase58()],
|
|
352
|
+
filters: [
|
|
353
|
+
{
|
|
354
|
+
memcmp: {
|
|
355
|
+
offset: '32', // Owner field offset in token account
|
|
356
|
+
bytes: new Uint8Array(base58.decode(address)),
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
commitment: CommitmentLevel.CONFIRMED,
|
|
363
|
+
accountsDataSlice: [],
|
|
364
|
+
slots: {},
|
|
365
|
+
transactions: {},
|
|
366
|
+
transactionsStatus: {},
|
|
367
|
+
blocks: {},
|
|
368
|
+
blocksMeta: {},
|
|
369
|
+
entry: {},
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Send subscription request
|
|
373
|
+
await new Promise<void>((resolve, reject) => {
|
|
374
|
+
stream.write(request, (err: Error | null | undefined) => {
|
|
375
|
+
if (err) {
|
|
376
|
+
reject(err)
|
|
377
|
+
} else {
|
|
378
|
+
resolve()
|
|
379
|
+
}
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
// Return unsubscribe function
|
|
384
|
+
return () => {
|
|
385
|
+
stream.end()
|
|
386
|
+
this.activeStreams.delete(stream)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get the underlying Connection object
|
|
392
|
+
*
|
|
393
|
+
* Useful for advanced operations that need direct RPC access.
|
|
394
|
+
*/
|
|
395
|
+
getConnection(): Connection {
|
|
396
|
+
return this.connection
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Close all active subscriptions and cleanup resources
|
|
401
|
+
*/
|
|
402
|
+
async close(): Promise<void> {
|
|
403
|
+
for (const stream of this.activeStreams) {
|
|
404
|
+
stream.end()
|
|
405
|
+
}
|
|
406
|
+
this.activeStreams.clear()
|
|
407
|
+
this.grpcClient = null
|
|
408
|
+
}
|
|
409
|
+
}
|