Overview
While the SDK provides default public RPC endpoints for all supported chains, you may want to use custom RPC providers (Alchemy, Infura, QuickNode, etc.) for better performance, higher rate limits, or specific requirements.
The ApplyRPCOverrides function allows you to override RPC URLs without modifying the rest of the chain configuration.
ApplyRPCOverrides
func ApplyRPCOverrides (
chains [] Chain ,
overrides map [ string ] string ,
) [] Chain
Original chain configurations to modify
overrides
map[string]string
required
Map of chain names to custom RPC URLs
Returns a new slice of chains with overridden RPC endpoints. Original chain configurations are not modified.
Basic Usage
// Get default mainnet chains
chains := cctp . GetChains ( false )
// Define custom RPC endpoints
overrides := map [ string ] string {
cctp . Ethereum : "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY" ,
cctp . Avalanche : "https://avalanche-mainnet.infura.io/v3/YOUR_KEY" ,
cctp . Base : "https://base-mainnet.g.alchemy.com/v2/YOUR_KEY" ,
}
// Apply overrides
customChains := cctp . ApplyRPCOverrides ( chains , overrides )
// Use custom chains for lookups
ethChain , _ := cctp . GetChainByName ( cctp . Ethereum , false )
for _ , chain := range customChains {
if chain . Name == cctp . Ethereum {
ethChain = & chain
break
}
}
fmt . Printf ( "Using RPC: %s \n " , ethChain . RPC )
// Output: Using RPC: https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
Environment-Based Configuration
Load RPC URLs from environment variables to keep credentials out of source code.
import " os "
func getCustomChains ( testnet bool ) [] cctp . Chain {
chains := cctp . GetChains ( testnet )
overrides := make ( map [ string ] string )
// Load from environment
if rpc := os . Getenv ( "ETHEREUM_RPC" ); rpc != "" {
overrides [ cctp . Ethereum ] = rpc
}
if rpc := os . Getenv ( "AVALANCHE_RPC" ); rpc != "" {
overrides [ cctp . Avalanche ] = rpc
}
if rpc := os . Getenv ( "BASE_RPC" ); rpc != "" {
overrides [ cctp . Base ] = rpc
}
return cctp . ApplyRPCOverrides ( chains , overrides )
}
// Usage
chains := getCustomChains ( false )
sourceChain , _ := cctp . GetChainByName ( cctp . Ethereum , false )
for _ , chain := range chains {
if chain . Name == cctp . Ethereum {
sourceChain = & chain
break
}
}
Configuration File
Load RPC overrides from a JSON configuration file:
config.json
Load Configuration
{
"rpc_overrides" : {
"Ethereum" : "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY" ,
"Avalanche" : "https://avalanche-mainnet.infura.io/v3/YOUR_KEY" ,
"Base" : "https://base-mainnet.g.alchemy.com/v2/YOUR_KEY" ,
"Arbitrum" : "https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY"
}
}
Partial Overrides
You only need to override chains you’re using. Unspecified chains keep their default RPCs:
// Only override source and destination chains
overrides := map [ string ] string {
params . SourceChain . Name : os . Getenv ( "SOURCE_RPC" ),
params . DestChain . Name : os . Getenv ( "DEST_RPC" ),
}
chains := cctp . ApplyRPCOverrides ( cctp . GetChains ( false ), overrides )
// Find updated chain configs
var sourceChain , destChain * cctp . Chain
for i , chain := range chains {
if chain . Name == params . SourceChain . Name {
sourceChain = & chains [ i ]
}
if chain . Name == params . DestChain . Name {
destChain = & chains [ i ]
}
}
params . SourceChain = sourceChain
params . DestChain = destChain
Per-Environment Configuration
Production
Staging
Development
func getProductionChains () [] cctp . Chain {
chains := cctp . GetChains ( false ) // Mainnet
overrides := map [ string ] string {
cctp . Ethereum : os . Getenv ( "PROD_ETHEREUM_RPC" ),
cctp . Avalanche : os . Getenv ( "PROD_AVALANCHE_RPC" ),
cctp . Base : os . Getenv ( "PROD_BASE_RPC" ),
}
return cctp . ApplyRPCOverrides ( chains , overrides )
}
func getStagingChains () [] cctp . Chain {
chains := cctp . GetChains ( true ) // Testnet
overrides := map [ string ] string {
cctp . EthereumSepolia : os . Getenv ( "STAGING_SEPOLIA_RPC" ),
cctp . AvalancheFuji : os . Getenv ( "STAGING_FUJI_RPC" ),
cctp . BaseSepolia : os . Getenv ( "STAGING_BASE_RPC" ),
}
return cctp . ApplyRPCOverrides ( chains , overrides )
}
func getDevelopmentChains () [] cctp . Chain {
// Use defaults for development
return cctp . GetChains ( true )
}
Integration with Transfer Orchestrator
Apply RPC overrides before creating the orchestrator:
package main
import (
" os "
" github.com/circlefin/cctp-go "
)
func main () {
// Get base configurations
chains := cctp . GetChains ( false )
// Apply custom RPCs
overrides := map [ string ] string {
cctp . Ethereum : os . Getenv ( "ETHEREUM_RPC" ),
cctp . Base : os . Getenv ( "BASE_RPC" ),
}
customChains := cctp . ApplyRPCOverrides ( chains , overrides )
// Find custom chain configs
var sourceChain , destChain * cctp . Chain
for i := range customChains {
if customChains [ i ]. Name == cctp . Ethereum {
sourceChain = & customChains [ i ]
}
if customChains [ i ]. Name == cctp . Base {
destChain = & customChains [ i ]
}
}
// Create transfer params with custom chains
params := & cctp . TransferParams {
SourceChain : sourceChain ,
DestChain : destChain ,
Amount : amount ,
RecipientAddress : recipient ,
}
// Orchestrator will use custom RPC endpoints
orchestrator , err := cctp . NewTransferOrchestrator (
wallet ,
params ,
irisURL ,
)
if err != nil {
log . Fatal ( err )
}
defer orchestrator . Close ()
}
Implementation Details
The function creates a new slice to avoid modifying the original:
func ApplyRPCOverrides ( chains [] Chain , overrides map [ string ] string ) [] Chain {
if len ( overrides ) == 0 {
return chains
}
result := make ([] Chain , len ( chains ))
for i , chain := range chains {
result [ i ] = chain
if rpcURL , ok := overrides [ chain . Name ]; ok && rpcURL != "" {
result [ i ]. RPC = rpcURL
}
}
return result
}
Empty string values in the overrides map are ignored, keeping the original RPC endpoint.
RPC Provider Examples
Alchemy ETHEREUM_RPC = "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
BASE_RPC = "https://base-mainnet.g.alchemy.com/v2/YOUR_KEY"
ARBITRUM_RPC = "https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY"
Infura ETHEREUM_RPC = "https://mainnet.infura.io/v3/YOUR_KEY"
AVALANCHE_RPC = "https://avalanche-mainnet.infura.io/v3/YOUR_KEY"
POLYGON_RPC = "https://polygon-mainnet.infura.io/v3/YOUR_KEY"
QuickNode ETHEREUM_RPC = "https://your-endpoint.quiknode.pro/YOUR_KEY/"
BASE_RPC = "https://your-endpoint.quiknode.pro/YOUR_KEY/"
Ankr ETHEREUM_RPC = "https://rpc.ankr.com/eth/YOUR_KEY"
AVALANCHE_RPC = "https://rpc.ankr.com/avalanche/YOUR_KEY"
POLYGON_RPC = "https://rpc.ankr.com/polygon/YOUR_KEY"
Best Practices
Use Environment Variables
Store RPC URLs in environment variables, never commit them to version control: export ETHEREUM_RPC = "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
export BASE_RPC = "https://base-mainnet.g.alchemy.com/v2/YOUR_KEY"
Validate URLs
Test RPC endpoints before using them in production: func validateRPC ( rpcURL string ) error {
client , err := ethclient . Dial ( rpcURL )
if err != nil {
return err
}
defer client . Close ()
_ , err = client . ChainID ( context . Background ())
return err
}
Fallback to Defaults
Gracefully handle missing or invalid overrides: customRPC := os . Getenv ( "ETHEREUM_RPC" )
if customRPC == "" {
log . Println ( "Using default Ethereum RPC" )
} else if err := validateRPC ( customRPC ); err != nil {
log . Printf ( "Invalid RPC, using default: %v " , err )
customRPC = ""
}
Monitor RPC Health
Implement health checks for custom RPC endpoints: func checkRPCHealth ( chains [] cctp . Chain ) {
for _ , chain := range chains {
client , err := ethclient . Dial ( chain . RPC )
if err != nil {
log . Printf ( "[ %s ] RPC unhealthy: %v " , chain . Name , err )
continue
}
client . Close ()
log . Printf ( "[ %s ] RPC healthy" , chain . Name )
}
}
Next Steps
Chain Configuration Learn about chain metadata and lookup functions
Transfer Orchestrator Use custom chains for transfers