@solana/rpc-transport-http
This package allows developers to create custom RPC transports. With this library, one can implement highly specialized functionality for leveraging multiple transports, attempting/handling retries, and more.
Functions
createHttpTransport()
Call this to create a function that conforms to the RpcTransport
interface (see @solana/rpc-spec
). You can use that function in your programs to make POST
requests with headers suitable for sending JSON data to a server.
import { createHttpTransport } from '@solana/rpc-transport-http';
const transport = createHttpTransport({ url: 'https://api.mainnet-beta.solana.com' });
const response = await transport({
payload: { id: 1, jsonrpc: '2.0', method: 'getSlot' },
});
const data = await response.json();
Config
dispatcher_NODE_ONLY
In Node environments you can tune how requests are dispatched to the network. Use this config parameter to install a undici.Dispatcher
in your transport.
import { createHttpTransport } from '@solana/rpc-transport-http';
import { Agent, BalancedPool } from 'undici';
const dispatcher = new Agent({
factory(origin, opts) {
if (origin === 'https://mypool') {
const upstreams = [
'https://api.mainnet-beta.solana.com',
'https://mainnet.helius-rpc.com',
'https://several-neat-iguana.quiknode.pro',
];
return new BalancedPool(upstreams, {
...opts,
bodyTimeout: 60e3,
headersTimeout: 5e3,
keepAliveTimeout: 19e3,
});
} else {
return new Pool(origin, opts);
}
},
});
const transport = createHttpTransport({
dispatcher_NODE_ONLY: dispatcher,
url: 'https://mypool',
});
let id = 0;
const balances = await Promise.allSettled(
accounts.map(async account => {
const response = await transport({
payload: {
id: ++id,
jsonrpc: '2.0',
method: 'getBalance',
params: [account],
},
});
return await response.json();
}),
);
An object of headers to set on the request. Avoid forbidden headers. Additionally, the headers Accept
, Content-Length
, and Content-Type
are disallowed.
import { createHttpTransport } from '@solana/rpc-transport-http';
const transport = createHttpTransport({
headers: {
Authorization: `Bearer ${process.env.RPC_AUTH_TOKEN}`,
},
url: 'https://several-neat-iguana.quiknode.pro',
});
url
A string representing the target endpoint. In Node, it must be an absolute URL using the http
or https
protocol.
Augmenting Transports
Using this core transport, you can implement specialized functionality for leveraging multiple transports, attempting/handling retries, and more.
Round Robin
Here’s an example of how someone might implement a “round robin” approach to distribute requests to multiple transports:
import { RpcResponse, RpcTransport } from '@solana/rpc-spec';
import { createHttpTransport } from '@solana/rpc-transport-http';
const transports = [
createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' }),
createHttpTransport({ url: 'https://mainnet-beta.my-server-2.com' }),
createHttpTransport({ url: 'https://mainnet-beta.my-server-3.com' }),
];
let nextTransport = 0;
async function roundRobinTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {
const transport = transports[nextTransport];
nextTransport = (nextTransport + 1) % transports.length;
return await transport(...args);
}
Sharding
Another example of a possible customization for a transport is to shard requests deterministically among a set of servers. Here’s an example:
Perhaps your application needs to make a large number of requests, or needs to fan request for different methods out to different servers. Here’s an example of an implementation that does the latter:
import { RpcResponse, RpcTransport } from '@solana/rpc-spec';
import { createHttpTransport } from '@solana/rpc-transport-http';
const transportA = createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' });
const transportB = createHttpTransport({ url: 'https://mainnet-beta.my-server-2.com' });
const transportC = createHttpTransport({ url: 'https://mainnet-beta.my-server-3.com' });
const transportD = createHttpTransport({ url: 'https://mainnet-beta.my-server-4.com' });
function selectShard(method: string): RpcTransport {
switch (method) {
case 'getAccountInfo':
case 'getBalance':
return transportA;
case 'getTransaction':
case 'getRecentBlockhash':
return transportB;
case 'sendTransaction':
return transportC;
default:
return transportD;
}
}
async function shardingTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {
const payload = args[0].payload as { method: string };
const selectedTransport = selectShard(payload.method);
return await selectedTransport(...args);
}
Retry Logic
The transport library can also be used to implement custom retry logic on any request:
import { RpcResponse, RpcTransport } from '@solana/rpc-spec';
import { createHttpTransport } from '@solana/rpc-transport-http';
const MAX_ATTEMPTS = 4;
const defaultTransport = createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' });
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
function calculateRetryDelay(attempt: number): number {
return Math.min(100 * Math.pow(2, attempt), 1500);
}
async function retryingTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {
let requestError;
for (let attempts = 0; attempts < MAX_ATTEMPTS; attempts++) {
try {
return await defaultTransport(...args);
} catch (err) {
requestError = err;
if (attempts < MAX_ATTEMPTS - 1) {
const retryDelay = calculateRetryDelay(attempts);
await sleep(retryDelay);
}
}
}
throw requestError;
}
Failover
Here’s an example of some failover logic integrated into a transport:
import { RpcResponse, RpcTransport } from '@solana/rpc-spec';
import { createHttpTransport } from '@solana/rpc-transport-http';
const transports = [
createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' }),
createHttpTransport({ url: 'https://mainnet-beta.my-server-2.com' }),
createHttpTransport({ url: 'https://mainnet-beta.my-server-2.com' }),
];
async function failoverTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {
let requestError;
for (const transport of transports) {
try {
return await transport(...args);
} catch (err) {
requestError = err;
console.error(err);
}
}
throw requestError;
}