@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.
Files changed (264) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +267 -0
  3. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  4. package/dist/browser.d.mts +10 -4
  5. package/dist/browser.d.ts +10 -4
  6. package/dist/browser.js +47556 -19603
  7. package/dist/browser.mjs +628 -48
  8. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  9. package/dist/chunk-5D7A3L3W.mjs +717 -0
  10. package/dist/chunk-64AYA5F5.mjs +7834 -0
  11. package/dist/chunk-GMDGB22A.mjs +379 -0
  12. package/dist/chunk-I534WKN7.mjs +328 -0
  13. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  14. package/dist/chunk-PRRZAWJE.mjs +223 -0
  15. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  16. package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
  17. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  18. package/dist/constants-LHAAUC2T.mjs +51 -0
  19. package/dist/dist-2OGQ7FED.mjs +3957 -0
  20. package/dist/dist-IFHPYLDX.mjs +254 -0
  21. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  22. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  23. package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
  24. package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
  25. package/dist/index.d.mts +9 -3
  26. package/dist/index.d.ts +9 -3
  27. package/dist/index.js +48396 -19623
  28. package/dist/index.mjs +537 -19
  29. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  30. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  31. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  32. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  33. package/dist/proofs/halo2.d.mts +151 -0
  34. package/dist/proofs/halo2.d.ts +151 -0
  35. package/dist/proofs/halo2.js +350 -0
  36. package/dist/proofs/halo2.mjs +11 -0
  37. package/dist/proofs/kimchi.d.mts +160 -0
  38. package/dist/proofs/kimchi.d.ts +160 -0
  39. package/dist/proofs/kimchi.js +431 -0
  40. package/dist/proofs/kimchi.mjs +13 -0
  41. package/dist/proofs/noir.d.mts +1 -1
  42. package/dist/proofs/noir.d.ts +1 -1
  43. package/dist/proofs/noir.js +74 -18
  44. package/dist/proofs/noir.mjs +84 -24
  45. package/dist/solana-U3MEGU7W.mjs +280 -0
  46. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  47. package/package.json +54 -21
  48. package/src/adapters/index.ts +41 -0
  49. package/src/adapters/jupiter.ts +571 -0
  50. package/src/adapters/near-intents.ts +135 -0
  51. package/src/advisor/advisor.ts +653 -0
  52. package/src/advisor/index.ts +54 -0
  53. package/src/advisor/tools.ts +303 -0
  54. package/src/advisor/types.ts +164 -0
  55. package/src/chains/ethereum/announcement.ts +536 -0
  56. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  57. package/src/chains/ethereum/commitment.ts +522 -0
  58. package/src/chains/ethereum/constants.ts +462 -0
  59. package/src/chains/ethereum/deployment.ts +596 -0
  60. package/src/chains/ethereum/gas-estimation.ts +538 -0
  61. package/src/chains/ethereum/index.ts +268 -0
  62. package/src/chains/ethereum/optimizations.ts +614 -0
  63. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  64. package/src/chains/ethereum/registry.ts +584 -0
  65. package/src/chains/ethereum/rpc.ts +905 -0
  66. package/src/chains/ethereum/stealth.ts +491 -0
  67. package/src/chains/ethereum/token.ts +790 -0
  68. package/src/chains/ethereum/transfer.ts +637 -0
  69. package/src/chains/ethereum/types.ts +456 -0
  70. package/src/chains/ethereum/viewing-key.ts +455 -0
  71. package/src/chains/near/commitment.ts +608 -0
  72. package/src/chains/near/constants.ts +284 -0
  73. package/src/chains/near/function-call.ts +871 -0
  74. package/src/chains/near/history.ts +654 -0
  75. package/src/chains/near/implicit-account.ts +840 -0
  76. package/src/chains/near/index.ts +393 -0
  77. package/src/chains/near/native-transfer.ts +658 -0
  78. package/src/chains/near/nep141.ts +775 -0
  79. package/src/chains/near/privacy-adapter.ts +889 -0
  80. package/src/chains/near/resolver.ts +971 -0
  81. package/src/chains/near/rpc.ts +1016 -0
  82. package/src/chains/near/stealth.ts +419 -0
  83. package/src/chains/near/types.ts +317 -0
  84. package/src/chains/near/viewing-key.ts +876 -0
  85. package/src/chains/solana/anchor-transfer.ts +386 -0
  86. package/src/chains/solana/commitment.ts +577 -0
  87. package/src/chains/solana/constants.ts +126 -12
  88. package/src/chains/solana/ephemeral-keys.ts +543 -0
  89. package/src/chains/solana/index.ts +252 -1
  90. package/src/chains/solana/key-derivation.ts +418 -0
  91. package/src/chains/solana/kit-compat.ts +334 -0
  92. package/src/chains/solana/optimizations.ts +560 -0
  93. package/src/chains/solana/privacy-adapter.ts +605 -0
  94. package/src/chains/solana/providers/generic.ts +47 -6
  95. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  96. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  97. package/src/chains/solana/providers/helius.ts +186 -33
  98. package/src/chains/solana/providers/index.ts +31 -0
  99. package/src/chains/solana/providers/interface.ts +61 -18
  100. package/src/chains/solana/providers/quicknode.ts +409 -0
  101. package/src/chains/solana/providers/triton.ts +426 -0
  102. package/src/chains/solana/providers/webhook.ts +338 -67
  103. package/src/chains/solana/rpc-client.ts +1150 -0
  104. package/src/chains/solana/scan.ts +83 -66
  105. package/src/chains/solana/sol-transfer.ts +732 -0
  106. package/src/chains/solana/spl-transfer.ts +886 -0
  107. package/src/chains/solana/stealth-scanner.ts +703 -0
  108. package/src/chains/solana/sunspot-verifier.ts +453 -0
  109. package/src/chains/solana/transaction-builder.ts +755 -0
  110. package/src/chains/solana/transfer.ts +74 -5
  111. package/src/chains/solana/types.ts +57 -6
  112. package/src/chains/solana/utils.ts +110 -0
  113. package/src/chains/solana/viewing-key.ts +807 -0
  114. package/src/compliance/fireblocks.ts +921 -0
  115. package/src/compliance/index.ts +23 -0
  116. package/src/compliance/range-sas.ts +398 -33
  117. package/src/config/endpoints.ts +100 -0
  118. package/src/crypto.ts +11 -8
  119. package/src/errors.ts +82 -0
  120. package/src/evm/erc4337-relayer.ts +830 -0
  121. package/src/evm/index.ts +47 -0
  122. package/src/fees/calculator.ts +396 -0
  123. package/src/fees/index.ts +87 -0
  124. package/src/fees/near-contract.ts +429 -0
  125. package/src/fees/types.ts +268 -0
  126. package/src/index.ts +686 -1
  127. package/src/intent.ts +6 -3
  128. package/src/logger.ts +324 -0
  129. package/src/network/index.ts +80 -0
  130. package/src/network/proxy.ts +691 -0
  131. package/src/optimizations/index.ts +541 -0
  132. package/src/oracle/types.ts +1 -0
  133. package/src/privacy-backends/arcium-types.ts +727 -0
  134. package/src/privacy-backends/arcium.ts +719 -0
  135. package/src/privacy-backends/combined-privacy.ts +866 -0
  136. package/src/privacy-backends/cspl-token.ts +595 -0
  137. package/src/privacy-backends/cspl-types.ts +512 -0
  138. package/src/privacy-backends/cspl.ts +907 -0
  139. package/src/privacy-backends/health.ts +488 -0
  140. package/src/privacy-backends/inco-types.ts +323 -0
  141. package/src/privacy-backends/inco.ts +616 -0
  142. package/src/privacy-backends/index.ts +254 -4
  143. package/src/privacy-backends/interface.ts +649 -6
  144. package/src/privacy-backends/lru-cache.ts +343 -0
  145. package/src/privacy-backends/magicblock.ts +458 -0
  146. package/src/privacy-backends/mock.ts +258 -0
  147. package/src/privacy-backends/privacycash.ts +13 -17
  148. package/src/privacy-backends/private-swap.ts +570 -0
  149. package/src/privacy-backends/rate-limiter.ts +683 -0
  150. package/src/privacy-backends/registry.ts +414 -2
  151. package/src/privacy-backends/router.ts +283 -3
  152. package/src/privacy-backends/shadowwire.ts +449 -0
  153. package/src/privacy-backends/sip-native.ts +3 -0
  154. package/src/privacy-logger.ts +191 -0
  155. package/src/production-safety.ts +373 -0
  156. package/src/proofs/aggregator.ts +1029 -0
  157. package/src/proofs/browser-composer.ts +1150 -0
  158. package/src/proofs/browser.ts +113 -25
  159. package/src/proofs/cache/index.ts +127 -0
  160. package/src/proofs/cache/interface.ts +545 -0
  161. package/src/proofs/cache/key-generator.ts +188 -0
  162. package/src/proofs/cache/lru-cache.ts +481 -0
  163. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  164. package/src/proofs/cache/persistent-cache.ts +788 -0
  165. package/src/proofs/compliance-proof.ts +872 -0
  166. package/src/proofs/composer/base.ts +923 -0
  167. package/src/proofs/composer/index.ts +25 -0
  168. package/src/proofs/composer/interface.ts +518 -0
  169. package/src/proofs/composer/types.ts +383 -0
  170. package/src/proofs/converters/halo2.ts +452 -0
  171. package/src/proofs/converters/index.ts +208 -0
  172. package/src/proofs/converters/interface.ts +363 -0
  173. package/src/proofs/converters/kimchi.ts +462 -0
  174. package/src/proofs/converters/noir.ts +451 -0
  175. package/src/proofs/fallback.ts +888 -0
  176. package/src/proofs/halo2.ts +42 -0
  177. package/src/proofs/index.ts +471 -0
  178. package/src/proofs/interface.ts +13 -0
  179. package/src/proofs/kimchi.ts +42 -0
  180. package/src/proofs/lazy.ts +1004 -0
  181. package/src/proofs/mock.ts +25 -1
  182. package/src/proofs/noir.ts +110 -29
  183. package/src/proofs/orchestrator.ts +960 -0
  184. package/src/proofs/parallel/concurrency.ts +297 -0
  185. package/src/proofs/parallel/dependency-graph.ts +602 -0
  186. package/src/proofs/parallel/executor.ts +420 -0
  187. package/src/proofs/parallel/index.ts +131 -0
  188. package/src/proofs/parallel/interface.ts +685 -0
  189. package/src/proofs/parallel/worker-pool.ts +644 -0
  190. package/src/proofs/providers/halo2.ts +560 -0
  191. package/src/proofs/providers/index.ts +34 -0
  192. package/src/proofs/providers/kimchi.ts +641 -0
  193. package/src/proofs/validator.ts +881 -0
  194. package/src/proofs/verifier.ts +867 -0
  195. package/src/quantum/index.ts +112 -0
  196. package/src/quantum/winternitz-vault.ts +639 -0
  197. package/src/quantum/wots.ts +611 -0
  198. package/src/settlement/backends/direct-chain.ts +1 -0
  199. package/src/settlement/index.ts +9 -0
  200. package/src/settlement/router.ts +732 -46
  201. package/src/solana/index.ts +72 -0
  202. package/src/solana/jito-relayer.ts +687 -0
  203. package/src/solana/noir-verifier-types.ts +430 -0
  204. package/src/solana/noir-verifier.ts +816 -0
  205. package/src/stealth/address-derivation.ts +193 -0
  206. package/src/stealth/ed25519.ts +431 -0
  207. package/src/stealth/index.ts +233 -0
  208. package/src/stealth/meta-address.ts +221 -0
  209. package/src/stealth/secp256k1.ts +368 -0
  210. package/src/stealth/utils.ts +194 -0
  211. package/src/stealth.ts +50 -1504
  212. package/src/sync/index.ts +106 -0
  213. package/src/sync/manager.ts +504 -0
  214. package/src/sync/mock-provider.ts +318 -0
  215. package/src/sync/oblivious.ts +625 -0
  216. package/src/tokens/index.ts +15 -0
  217. package/src/tokens/registry.ts +301 -0
  218. package/src/utils/deprecation.ts +94 -0
  219. package/src/utils/index.ts +9 -0
  220. package/src/wallet/ethereum/index.ts +68 -0
  221. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  222. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  223. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  224. package/src/wallet/ethereum/types.ts +3 -1
  225. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  226. package/src/wallet/hardware/index.ts +10 -0
  227. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  228. package/src/wallet/index.ts +71 -0
  229. package/src/wallet/near/adapter.ts +626 -0
  230. package/src/wallet/near/index.ts +86 -0
  231. package/src/wallet/near/meteor-wallet.ts +1153 -0
  232. package/src/wallet/near/my-near-wallet.ts +790 -0
  233. package/src/wallet/near/wallet-selector.ts +702 -0
  234. package/src/wallet/solana/adapter.ts +6 -4
  235. package/src/wallet/solana/index.ts +13 -0
  236. package/src/wallet/solana/privacy-adapter.ts +567 -0
  237. package/src/wallet/sui/types.ts +6 -4
  238. package/src/zcash/rpc-client.ts +13 -6
  239. package/dist/chunk-2XIVXWHA.mjs +0 -1930
  240. package/dist/chunk-3INS3PR5.mjs +0 -884
  241. package/dist/chunk-3OVABDRH.mjs +0 -17096
  242. package/dist/chunk-7RFRWDCW.mjs +0 -1504
  243. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  244. package/dist/chunk-E6SZWREQ.mjs +0 -57
  245. package/dist/chunk-F6F73W35.mjs +0 -16166
  246. package/dist/chunk-G33LB27A.mjs +0 -16166
  247. package/dist/chunk-HGU6HZRC.mjs +0 -231
  248. package/dist/chunk-L2K34JCU.mjs +0 -1496
  249. package/dist/chunk-OFDBEIEK.mjs +0 -16166
  250. package/dist/chunk-SF7YSLF5.mjs +0 -1515
  251. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  252. package/dist/chunk-WWUSGOXE.mjs +0 -17129
  253. package/dist/constants-VOI7BSLK.mjs +0 -27
  254. package/dist/index-B71aXVzk.d.ts +0 -13264
  255. package/dist/index-BYZbDjal.d.ts +0 -11390
  256. package/dist/index-CHB3KuOB.d.mts +0 -11859
  257. package/dist/index-CzWPI6Le.d.ts +0 -11859
  258. package/dist/index-pOIIuwfV.d.mts +0 -13264
  259. package/dist/index-xbWjohNq.d.mts +0 -11390
  260. package/dist/solana-4O4K45VU.mjs +0 -46
  261. package/dist/solana-5EMCTPTS.mjs +0 -46
  262. package/dist/solana-NDABAZ6P.mjs +0 -56
  263. package/dist/solana-Q4NAVBTS.mjs +0 -46
  264. 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
+ }