feat/chainstack-rpc-connector#623
Conversation
- Add src/templates/rpc/chainstack.yml (preferredNodeId only; the API key lives in apiKeys.yml as the single source of truth) - Add chainstack-schema.json + register chainstack namespace in root.yml - Add chainstack to apiKeys.yml + apiKeys-schema.json - Add 'chainstack' to the rpcProvider enum in solana-chain-schema.json and ethereum-chain-schema.json - Include src/templates/rpc/*.yml in the build copy-files glob - Add scripts/test-chainstack-live.js for live integration testing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ChainstackService extends RPCProvider and discovers nodes via the Chainstack Platform API (GET /v1/nodes), mapping the caller's gateway chain/network to the deployed node's https/wss endpoints. - Solana: WebSocket signatureSubscribe monitoring (HeliusService pattern) - Ethereum: StaticJsonRpcProvider wrapped with the rate-limit interceptor - Optional preferredNodeId pins a specific deployment - Uses httpGet/httpPost from src/services/http-client (no axios in src) Includes 18 unit tests covering every public method, the network mapping, preferredNodeId selection, and error paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add an 'else if (rpcProvider === \"chainstack\")' branch in both chain constructors, mirroring the helius/infura pattern - Initialize ChainstackService synchronously with a nodeURL placeholder connection/provider; swap to the discovered Chainstack URL after the async getInstance().init() resolves - On Chainstack discovery failure, log a warning and fall back to nodeURL (consistent with the existing helius/infura fallback) - Expose getChainstackService() getter on Ethereum, mirroring getInfuraService() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both /chains/ethereum/status and the startup banner had hardcoded helius/infura branches to display the real RPC URL. Without a chainstack branch they would have shown the bare nodeURL even when Chainstack was the active provider. - ethereum/routes/status.ts: add chainstack branch using ethereum.getChainstackService()?.getHttpUrl() - startup-banner.ts: pick up the discovered URL after getInstance() resolves (Chainstack discovers its endpoint asynchronously, unlike Helius/Infura which can be constructed synchronously) The Solana status route already used solana.getRpcProviderService() generically, so no change was needed there. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Chainstack node endpoints have the shape https://<subdomain>.core.chainstack.com/<token> where the path segment IS the per-node access credential. The previous redactUrl() regexes only matched ?api-key= query strings (Helius) and /v3/<32-char> paths (Infura), so the new chainstack code paths in this PR were emitting the full credential-bearing URL into the winston file transport on every gateway start. - Add a Chainstack/p2pify pattern to redactUrl() - Add unit tests covering all three provider URL formats plus negative cases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Helius and Infura don't have their own namespaces — they only use the centralized apiKeys.yml entry plus the rpcProvider chain setting. Chainstack shouldn't need one either. - Delete src/templates/rpc/chainstack.yml and chainstack-schema.json - Remove $namespace chainstack from root.yml - Remove preferredNodeId from ChainstackService (always auto-selects the first running node matching the chain/network) - Revert the copy-files rpc/*.yml glob addition in package.json - Remove the two preferredNodeId test cases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Live e2e testing revealed the Chainstack Platform API differs from
the original plan's assumed shape:
- GET /v1/nodes returns paginated { results: [...] }, not a bare array
- Node objects carry a network ID (e.g., "NW-056-237-8"), not a name
- Endpoints are nested under `details` with a separate `auth_key`
- Protocol/network name must be resolved via GET /v1/networks/{id}
Changes:
- Add ChainstackApiNode, ChainstackNodesResponse, ChainstackApiNetwork
interfaces matching the real API
- Add resolveNetwork() to fetch protocol+network from network IDs
- Rewrite initialize() to handle pagination, resolve networks, and
build authenticated endpoint URLs (base + auth_key)
- Cache resolved networks to avoid duplicate lookups
- Update all 16 test cases with real-shaped fixtures
(mkApiNode, mkNetworkResponse, mockNodesAndNetworks helper)
Verified end-to-end against a live Solana mainnet Chainstack node:
ChainstackService.initialize() → discovered node ND-899-167-294
→ healthCheck() passed (getSlot)
→ Connection.getSlot/getBlockHeight/getBalance/getLatestBlockhash OK
→ WSS slotSubscribe OK
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks for this PR — solid work overall. Two things worth addressing before merge: 1. The URL swap after 2. Duplicated network map
Minor (non-blocking):
|
…lana Per PR review, the chain layer was using `instanceof ChainstackService` to decide whether to swap the Solana connection post-initialize. The cleaner shape is a polymorphic getHttpUrl on the base. - RPCProvider.getHttpUrl now returns string | null (default null) instead of being abstract returning string. Subclasses override to return their URL whenever it is available. - ChainstackService.getHttpUrl returns null pre-initialize (no throw). - Helius/Infura get an `override` keyword (signature unchanged). - solana.ts no longer special-cases Chainstack: after initialize(), if getHttpUrl() yields a URL, swap the connection. - Status routes and redactUrl handle null safely. - Test that asserted pre-init throw flipped to expect null. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…orks The live script used to redeclare the supported (chain, network) pairs in GATEWAY_NETWORKS and noted in a comment that it was kept in sync by hand. Per PR review, drift-prone. - ChainstackService.getSupportedNetworks() exposes the NETWORK_MAP keys. - scripts/test-chainstack-live.js converted to .ts and imports the static list directly. expectedChainId stays in the script as gateway-side metadata, since chainIds are not Chainstack's concern. - Run with: npx ts-node scripts/test-chainstack-live.ts Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per PR review, getInfuraService/getChainstackService on Ethereum vs getRpcProviderService on Solana was an inconsistent public API. Now that getHttpUrl lives on the RPCProvider base, the typed getters add no value. - Ethereum exposes a single polymorphic getRpcProviderService() returning RPCProvider | null. getInfuraService and getChainstackService removed. - Private fields infuraService and chainstackService kept inside ethereum.ts since their setup paths differ. - Ethereum status route is provider-agnostic now, mirroring Solana. - startup-banner uses the unified getter. - Status route tests updated to mock getRpcProviderService. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@fengtality Thanks for the review, addressed all four: 1. 2. Added 3. Replaced 4. |
Before submitting this PR, please make sure:
A description of the changes proposed in the pull request:
Adds Chainstack as a third RPC provider for both Solana and EVM chains, alongside Helius and Infura. Chainstack is the first cross-chain provider for the gateway and the first to use API-driven node discovery instead of URL templating: it calls
GET https://api.chainstack.com/v1/nodesonce at init and matches the caller's chain/network to a deployed node'shttps_endpoint/wss_endpoint.Coverage: 100% of gateway-supported networks — Ethereum, Arbitrum, Polygon, Optimism, Base, Avalanche, BSC, Celo, Sepolia, Solana mainnet, Solana devnet — plus 50+ more available on the platform.
Setup for users:
apiKeys.chainstack: '<key>'inapiKeys.ymlrpcProvider: chainstackinchains/solana.ymland/orchains/ethereum.ymlpreferredNodeId: 'ND-...'inrpc/chainstack.ymlto pin a specific deploymentThe PR is split into 4 commits for review:
ChainstackService(extendsRPCProvider) + 18 unit tests/chains/ethereum/statusand the startup bannerTests performed by the developer:
pnpm typecheck— cleanpnpm build— cleanpnpm lint— 0 errors (259 warnings, same baseline asdevelopment)pnpm jest --runInBand test/rpc/ test/chains/ethereum test/chains/solana test/services— 268/268 passpreferredNodeIdselection, and every error path (invalid API key, no matching node, stopped node, unmapped chain)get_deployment_options(the authoritative source)https_endpointwithgetSlot→ returns a valid slot, confirming the URL Chainstack hands out is usable as-ishttpGet/httpPostfromsrc/services/http-client— no axios insrc/, consistent with refactor / replace axios with native fetch for HTTP requests #621Tips for QA testing:
conf/apiKeys.yml:chainstack: '<your key>'conf/chains/solana.yml(orethereum.yml):rpcProvider: chainstackUsing Chainstack RPC URL: ...with the discovered endpointGET /chains/solana/status?network=mainnet-beta(or the ethereum equivalent) —rpcUrlin the response should match the Chainstack endpoint,rpcProvidershould bechainstackrpcProvider: chainstackbut leave the API key empty — the gateway should log a warning and fall back tonodeURLpreferredNodeId: 'ND-...-...-...'inconf/rpc/chainstack.yml