Security News
JSR Working Group Kicks Off with Ambitious Roadmap and Plans for Open Governance
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
@solana/rpc-transport-http
Advanced tools
An RPC transport that uses HTTP requests
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.
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' },
});
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';
// Create a dispatcher that, when called with a special URL, creates a round-robin pool of RPCs.
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(account =>
transport({
payload: {
id: ++id,
jsonrpc: '2.0',
method: 'getBalance',
params: [account],
},
}),
),
);
headers
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: {
// Authorize with the RPC using a bearer token
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.
Using this core transport, you can implement specialized functionality for leveraging multiple transports, attempting/handling retries, and more.
Here’s an example of how someone might implement a “round robin” approach to distribute requests to multiple transports:
import { RpcTransport } from '@solana/rpc-spec';
import { createHttpTransport } from '@solana/rpc-transport-http';
// Create a transport for each RPC server
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' }),
];
// Create a wrapper transport that distributes requests to them
let nextTransport = 0;
async function roundRobinTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<TResponse> {
const transport = transports[nextTransport];
nextTransport = (nextTransport + 1) % transports.length;
return await transport(...args);
}
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 { RpcTransport } from '@solana/rpc-spec';
import { createHttpTransport } from '@solana/rpc-transport-http';
// Create multiple transports
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 to determine which shard to use based on the request method
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<TResponse> {
const payload = args[0].payload as { method: string };
const selectedTransport = selectShard(payload.method);
return await selectedTransport(...args);
}
The transport library can also be used to implement custom retry logic on any request:
import { RpcTransport } from '@solana/rpc-spec';
import { createHttpTransport } from '@solana/rpc-transport-http';
// Set the maximum number of attempts to retry a request
const MAX_ATTEMPTS = 4;
// Create the default transport
const defaultTransport = createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' });
// Sleep function to wait for a given number of milliseconds
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Calculate the delay for a given attempt
function calculateRetryDelay(attempt: number): number {
// Exponential backoff with a maximum of 1.5 seconds
return Math.min(100 * Math.pow(2, attempt), 1500);
}
// A retrying transport that will retry up to `MAX_ATTEMPTS` times before failing
async function retryingTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<TResponse> {
let requestError;
for (let attempts = 0; attempts < MAX_ATTEMPTS; attempts++) {
try {
return await defaultTransport(...args);
} catch (err) {
requestError = err;
// Only sleep if we have more attempts remaining
if (attempts < MAX_ATTEMPTS - 1) {
const retryDelay = calculateRetryDelay(attempts);
await sleep(retryDelay);
}
}
}
throw requestError;
}
Here’s an example of some failover logic integrated into a transport:
import { RpcTransport } from '@solana/rpc-spec';
import { createHttpTransport } from '@solana/rpc-transport-http';
// Create a transport for each RPC server
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' }),
];
// A failover transport that will try each transport in order until one succeeds before failing
async function failoverTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<TResponse> {
let requestError;
for (const transport of transports) {
try {
return await transport(...args);
} catch (err) {
requestError = err;
console.error(err);
}
}
throw requestError;
}
FAQs
An RPC transport that uses HTTP requests
We found that @solana/rpc-transport-http demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 15 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Security News
Research
An advanced npm supply chain attack is leveraging Ethereum smart contracts for decentralized, persistent malware control, evading traditional defenses.
Security News
Research
Attackers are impersonating Sindre Sorhus on npm with a fake 'chalk-node' package containing a malicious backdoor to compromise developers' projects.