Skip to content

Commit cd0b6c6

Browse files
authored
refactor(experimental): add function to create a Solana RPC using only an URL (#2238)
This PR changes the `createSolanaRpc` function so that it accepts a `ClusterUrl` as a first parameter and an optional config object as a second parameter. This makes it much easier for 95% of RPC use-cases to get started with the library: ```ts const rpc = createSolanaRpc("http://localhost:8899"); // Cluster-aware RPCs: const rpc = createSolanaRpc(devnet("https://api.devnet.solana.com")); const rpc = createSolanaRpc(testnet("https://api.testnet.solana.com")); const rpc = createSolanaRpc(mainnet("https://api.mainnet-beta.solana.com")); // With configs: const rpc = createSolanaRpc("http://localhost:8899", { headers: myHeaders, dispatcher_NODE_ONLY: myDispatcher, }); ``` Additionally, this PR adds a `createSolanaRpcFromTransport` function that allows the remaining 5% of use-cases to customize their own transport just as easily: ```ts const rpc = createSolanaRpcFromTransport(myCustomTransport); ``` Note that `createSolanaRpcFromTransport` is cluster aware so, if `myCustomTransport` is of type `RpcTransportMainnet`, then `rpc` will automatically be of type `RpcMainnet<SolanaRpcApiMainnet>`. Finally, this PR refactors the cluster typetests to make it clearer what we are trying to tests. --- See #2119
1 parent 9fb0650 commit cd0b6c6

File tree

4 files changed

+172
-85
lines changed

4 files changed

+172
-85
lines changed

packages/rpc/src/__tests__/rpc-integer-overflow-test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { SolanaError } from '@solana/errors';
22
import type { RpcTransport } from '@solana/rpc-spec';
33

4-
import { createSolanaRpc } from '../rpc';
4+
import { createSolanaRpcFromTransport } from '../rpc';
55

66
describe('RPC integer overflow behavior', () => {
7-
let rpc: ReturnType<typeof createSolanaRpc>;
7+
let rpc: ReturnType<typeof createSolanaRpcFromTransport>;
88
beforeEach(() => {
99
const transport = jest.fn(
1010
() =>
1111
new Promise(_ => {
1212
/* never resolve */
1313
}),
1414
) as RpcTransport;
15-
rpc = createSolanaRpc({ transport });
15+
rpc = createSolanaRpcFromTransport(transport);
1616
});
1717
it('does not throw when called with a value up to `Number.MAX_SAFE_INTEGER`', () => {
1818
expect(() => {

packages/rpc/src/__typetests__/rpc-clusters-typetest.ts

Lines changed: 152 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { SolanaRpcApi, SolanaRpcApiDevnet, SolanaRpcApiMainnet, SolanaRpcAp
22
import type { Rpc, RpcTransport } from '@solana/rpc-spec';
33
import { devnet, mainnet, testnet } from '@solana/rpc-types';
44

5-
import { createSolanaRpc } from '../rpc';
5+
import { createSolanaRpc, createSolanaRpcFromTransport } from '../rpc';
66
import type {
77
RpcDevnet,
88
RpcMainnet,
@@ -13,85 +13,165 @@ import type {
1313
} from '../rpc-clusters';
1414
import { createDefaultRpcTransport } from '../rpc-transport';
1515

16-
// Creating default HTTP transports
16+
// Define cluster-aware URLs and transports.
1717

1818
const genericUrl = 'http://localhost:8899';
1919
const devnetUrl = devnet('https://api.devnet.solana.com');
2020
const testnetUrl = testnet('https://api.testnet.solana.com');
2121
const mainnetUrl = mainnet('https://api.mainnet-beta.solana.com');
2222

23-
// No cluster specified should be generic `RpcTransport`
24-
createDefaultRpcTransport({ url: genericUrl }) satisfies RpcTransport;
25-
//@ts-expect-error Should not be a devnet transport
26-
createDefaultRpcTransport({ url: genericUrl }) satisfies RpcTransportDevnet;
27-
//@ts-expect-error Should not be a testnet transport
28-
createDefaultRpcTransport({ url: genericUrl }) satisfies RpcTransportTestnet;
29-
//@ts-expect-error Should not be a mainnet transport
30-
createDefaultRpcTransport({ url: genericUrl }) satisfies RpcTransportMainnet;
31-
32-
// Devnet cluster should be `RpcTransportDevnet`
33-
createDefaultRpcTransport({ url: devnetUrl }) satisfies RpcTransportDevnet;
34-
//@ts-expect-error Should not be a testnet transport
35-
createDefaultRpcTransport({ url: devnetUrl }) satisfies RpcTransportTestnet;
36-
//@ts-expect-error Should not be a mainnet transport
37-
createDefaultRpcTransport({ url: devnetUrl }) satisfies RpcTransportMainnet;
38-
39-
// Testnet cluster should be `RpcTransportTestnet`
40-
createDefaultRpcTransport({ url: testnetUrl }) satisfies RpcTransportTestnet;
41-
//@ts-expect-error Should not be a devnet transport
42-
createDefaultRpcTransport({ url: testnetUrl }) satisfies RpcTransportDevnet;
43-
//@ts-expect-error Should not be a mainnet transport
44-
createDefaultRpcTransport({ url: testnetUrl }) satisfies RpcTransportMainnet;
45-
46-
// Mainnet cluster should be `RpcTransportMainnet`
47-
createDefaultRpcTransport({ url: mainnetUrl }) satisfies RpcTransportMainnet;
48-
//@ts-expect-error Should not be a devnet transport
49-
createDefaultRpcTransport({ url: mainnetUrl }) satisfies RpcTransportDevnet;
50-
//@ts-expect-error Should not be a testnet transport
51-
createDefaultRpcTransport({ url: mainnetUrl }) satisfies RpcTransportTestnet;
52-
53-
// Creating JSON RPC clients
54-
5523
const genericTransport = createDefaultRpcTransport({ url: genericUrl });
5624
const devnetTransport = createDefaultRpcTransport({ url: devnetUrl });
5725
const testnetTransport = createDefaultRpcTransport({ url: testnetUrl });
5826
const mainnetTransport = createDefaultRpcTransport({ url: mainnetUrl });
5927

60-
// No cluster specified should be generic `Rpc`
61-
createSolanaRpc({ transport: genericTransport }) satisfies Rpc<SolanaRpcApi>;
62-
//@ts-expect-error Should not be a devnet RPC
63-
createSolanaRpc({ transport: genericTransport }) satisfies RpcDevnet<SolanaRpcApi>;
64-
//@ts-expect-error Should not be a testnet RPC
65-
createSolanaRpc({ transport: genericTransport }) satisfies RpcTestnet<SolanaRpcApi>;
66-
//@ts-expect-error Should not be a mainnet RPC
67-
createSolanaRpc({ transport: genericTransport }) satisfies RpcMainnet<SolanaRpcApi>;
68-
69-
// Devnet cluster should be `RpcDevnet`
70-
createSolanaRpc({ transport: devnetTransport }) satisfies Rpc<SolanaRpcApi>;
71-
createSolanaRpc({ transport: devnetTransport }) satisfies Rpc<SolanaRpcApiDevnet>;
72-
createSolanaRpc({ transport: devnetTransport }) satisfies RpcDevnet<SolanaRpcApi>;
73-
createSolanaRpc({ transport: devnetTransport }) satisfies RpcDevnet<SolanaRpcApiDevnet>; // Same types
74-
//@ts-expect-error Should not be a testnet RPC
75-
createSolanaRpc({ transport: devnetTransport }) satisfies RpcTestnet<SolanaRpcApi>;
76-
//@ts-expect-error Should not be a mainnet RPC
77-
createSolanaRpc({ transport: devnetTransport }) satisfies RpcMainnet<SolanaRpcApi>;
78-
79-
// Testnet cluster should be `RpcTestnet`
80-
createSolanaRpc({ transport: testnetTransport }) satisfies Rpc<SolanaRpcApi>;
81-
createSolanaRpc({ transport: testnetTransport }) satisfies Rpc<SolanaRpcApiTestnet>;
82-
createSolanaRpc({ transport: testnetTransport }) satisfies RpcTestnet<SolanaRpcApi>;
83-
createSolanaRpc({ transport: testnetTransport }) satisfies RpcTestnet<SolanaRpcApiTestnet>; // Same types
84-
//@ts-expect-error Should not be a devnet RPC
85-
createSolanaRpc({ transport: testnetTransport }) satisfies RpcDevnet<SolanaRpcApi>;
86-
//@ts-expect-error Should not be a mainnet RPC
87-
createSolanaRpc({ transport: testnetTransport }) satisfies RpcMainnet<SolanaRpcApi>;
88-
89-
// Mainnet cluster should be `RpcMainnet`
90-
createSolanaRpc({ transport: mainnetTransport }) satisfies Rpc<SolanaRpcApiMainnet>;
91-
createSolanaRpc({ transport: mainnetTransport }) satisfies RpcMainnet<SolanaRpcApiMainnet>;
92-
//@ts-expect-error Should not have `requestAirdrop` method
93-
createSolanaRpc({ transport: mainnetTransport }) satisfies Rpc<RequestAirdropApi>;
94-
//@ts-expect-error Should not be a devnet RPC
95-
createSolanaRpc({ transport: mainnetTransport }) satisfies RpcDevnet<SolanaRpcApi>;
96-
//@ts-expect-error Should not be a testnet RPC
97-
createSolanaRpc({ transport: mainnetTransport }) satisfies RpcTestnet<SolanaRpcApi>;
28+
// [DEFINE] createDefaultRpcTransport.
29+
{
30+
// No cluster specified should be generic `RpcTransport`.
31+
{
32+
genericTransport satisfies RpcTransport;
33+
//@ts-expect-error Should not be a devnet transport
34+
genericTransport satisfies RpcTransportDevnet;
35+
//@ts-expect-error Should not be a testnet transport
36+
genericTransport satisfies RpcTransportTestnet;
37+
//@ts-expect-error Should not be a mainnet transport
38+
genericTransport satisfies RpcTransportMainnet;
39+
}
40+
41+
// Devnet cluster should be `RpcTransportDevnet`.
42+
{
43+
devnetTransport satisfies RpcTransportDevnet;
44+
//@ts-expect-error Should not be a testnet transport
45+
devnetTransport satisfies RpcTransportTestnet;
46+
//@ts-expect-error Should not be a mainnet transport
47+
devnetTransport satisfies RpcTransportMainnet;
48+
}
49+
50+
// Testnet cluster should be `RpcTransportTestnet`.
51+
{
52+
testnetTransport satisfies RpcTransportTestnet;
53+
//@ts-expect-error Should not be a devnet transport
54+
testnetTransport satisfies RpcTransportDevnet;
55+
//@ts-expect-error Should not be a mainnet transport
56+
testnetTransport satisfies RpcTransportMainnet;
57+
}
58+
59+
// Mainnet cluster should be `RpcTransportMainnet`.
60+
{
61+
mainnetTransport satisfies RpcTransportMainnet;
62+
//@ts-expect-error Should not be a devnet transport
63+
mainnetTransport satisfies RpcTransportDevnet;
64+
//@ts-expect-error Should not be a testnet transport
65+
mainnetTransport satisfies RpcTransportTestnet;
66+
}
67+
}
68+
69+
// [DEFINE] createSolanaRpcFromTransport.
70+
{
71+
const genericRpc = createSolanaRpcFromTransport(genericTransport);
72+
const devnetRpc = createSolanaRpcFromTransport(devnetTransport);
73+
const testnetRpc = createSolanaRpcFromTransport(testnetTransport);
74+
const mainnetRpc = createSolanaRpcFromTransport(mainnetTransport);
75+
76+
// No cluster specified should be generic `Rpc`.
77+
{
78+
genericRpc satisfies Rpc<SolanaRpcApi>;
79+
//@ts-expect-error Should not be a devnet RPC
80+
genericRpc satisfies RpcDevnet<SolanaRpcApi>;
81+
//@ts-expect-error Should not be a testnet RPC
82+
genericRpc satisfies RpcTestnet<SolanaRpcApi>;
83+
//@ts-expect-error Should not be a mainnet RPC
84+
genericRpc satisfies RpcMainnet<SolanaRpcApi>;
85+
}
86+
87+
// Devnet cluster should be `RpcDevnet`.
88+
{
89+
devnetRpc satisfies Rpc<SolanaRpcApi>;
90+
devnetRpc satisfies Rpc<SolanaRpcApiDevnet>;
91+
devnetRpc satisfies RpcDevnet<SolanaRpcApi>;
92+
devnetRpc satisfies RpcDevnet<SolanaRpcApiDevnet>; // Same types
93+
//@ts-expect-error Should not be a testnet RPC
94+
devnetRpc satisfies RpcTestnet<SolanaRpcApi>;
95+
//@ts-expect-error Should not be a mainnet RPC
96+
devnetRpc satisfies RpcMainnet<SolanaRpcApi>;
97+
}
98+
99+
// Testnet cluster should be `RpcTestnet`.
100+
{
101+
testnetRpc satisfies Rpc<SolanaRpcApi>;
102+
testnetRpc satisfies Rpc<SolanaRpcApiTestnet>;
103+
testnetRpc satisfies RpcTestnet<SolanaRpcApi>;
104+
testnetRpc satisfies RpcTestnet<SolanaRpcApiTestnet>; // Same types
105+
//@ts-expect-error Should not be a devnet RPC
106+
testnetRpc satisfies RpcDevnet<SolanaRpcApi>;
107+
//@ts-expect-error Should not be a mainnet RPC
108+
testnetRpc satisfies RpcMainnet<SolanaRpcApi>;
109+
}
110+
111+
// Mainnet cluster should be `RpcMainnet`.
112+
{
113+
mainnetRpc satisfies Rpc<SolanaRpcApiMainnet>;
114+
mainnetRpc satisfies RpcMainnet<SolanaRpcApiMainnet>;
115+
//@ts-expect-error Should not have `requestAirdrop` method
116+
mainnetRpc satisfies Rpc<RequestAirdropApi>;
117+
//@ts-expect-error Should not be a devnet RPC
118+
mainnetRpc satisfies RpcDevnet<SolanaRpcApi>;
119+
//@ts-expect-error Should not be a testnet RPC
120+
mainnetRpc satisfies RpcTestnet<SolanaRpcApi>;
121+
}
122+
}
123+
124+
// [DEFINE] createSolanaRpc.
125+
{
126+
const genericRpc = createSolanaRpc(genericUrl);
127+
const devnetRpc = createSolanaRpc(devnetUrl);
128+
const testnetRpc = createSolanaRpc(testnetUrl);
129+
const mainnetRpc = createSolanaRpc(mainnetUrl);
130+
131+
// No cluster specified should be generic `Rpc`.
132+
{
133+
genericRpc satisfies Rpc<SolanaRpcApi>;
134+
//@ts-expect-error Should not be a devnet RPC
135+
genericRpc satisfies RpcDevnet<SolanaRpcApi>;
136+
//@ts-expect-error Should not be a testnet RPC
137+
genericRpc satisfies RpcTestnet<SolanaRpcApi>;
138+
//@ts-expect-error Should not be a mainnet RPC
139+
genericRpc satisfies RpcMainnet<SolanaRpcApi>;
140+
}
141+
142+
// Devnet cluster should be `RpcDevnet`.
143+
{
144+
devnetRpc satisfies Rpc<SolanaRpcApi>;
145+
devnetRpc satisfies Rpc<SolanaRpcApiDevnet>;
146+
devnetRpc satisfies RpcDevnet<SolanaRpcApi>;
147+
devnetRpc satisfies RpcDevnet<SolanaRpcApiDevnet>; // Same types
148+
//@ts-expect-error Should not be a testnet RPC
149+
devnetRpc satisfies RpcTestnet<SolanaRpcApi>;
150+
//@ts-expect-error Should not be a mainnet RPC
151+
devnetRpc satisfies RpcMainnet<SolanaRpcApi>;
152+
}
153+
154+
// Testnet cluster should be `RpcTestnet`.
155+
{
156+
testnetRpc satisfies Rpc<SolanaRpcApi>;
157+
testnetRpc satisfies Rpc<SolanaRpcApiTestnet>;
158+
testnetRpc satisfies RpcTestnet<SolanaRpcApi>;
159+
testnetRpc satisfies RpcTestnet<SolanaRpcApiTestnet>; // Same types
160+
//@ts-expect-error Should not be a devnet RPC
161+
testnetRpc satisfies RpcDevnet<SolanaRpcApi>;
162+
//@ts-expect-error Should not be a mainnet RPC
163+
testnetRpc satisfies RpcMainnet<SolanaRpcApi>;
164+
}
165+
166+
// Mainnet cluster should be `RpcMainnet`.
167+
{
168+
mainnetRpc satisfies Rpc<SolanaRpcApiMainnet>;
169+
mainnetRpc satisfies RpcMainnet<SolanaRpcApiMainnet>;
170+
//@ts-expect-error Should not have `requestAirdrop` method
171+
mainnetRpc satisfies Rpc<RequestAirdropApi>;
172+
//@ts-expect-error Should not be a devnet RPC
173+
mainnetRpc satisfies RpcDevnet<SolanaRpcApi>;
174+
//@ts-expect-error Should not be a testnet RPC
175+
mainnetRpc satisfies RpcTestnet<SolanaRpcApi>;
176+
}
177+
}

packages/rpc/src/rpc-transport.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getRpcTransportWithRequestCoalescing } from './rpc-request-coalescer';
77
import { getSolanaRpcPayloadDeduplicationKey } from './rpc-request-deduplication';
88

99
type RpcTransportConfig = Parameters<typeof createHttpTransport>[0];
10-
interface Config<TClusterUrl extends ClusterUrl> extends RpcTransportConfig {
10+
export interface DefaultRpcTransportConfig<TClusterUrl extends ClusterUrl> extends RpcTransportConfig {
1111
url: TClusterUrl;
1212
}
1313

@@ -25,7 +25,7 @@ function normalizeHeaders<T extends Record<string, string>>(
2525
}
2626

2727
export function createDefaultRpcTransport<TClusterUrl extends ClusterUrl>(
28-
config: Config<TClusterUrl>,
28+
config: DefaultRpcTransportConfig<TClusterUrl>,
2929
): RpcTransportFromClusterUrl<TClusterUrl> {
3030
return pipe(
3131
createHttpTransport({

packages/rpc/src/rpc.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import { createSolanaRpcApi } from '@solana/rpc-api';
22
import { createRpc, RpcTransport } from '@solana/rpc-spec';
3+
import { ClusterUrl } from '@solana/rpc-types';
34

45
import type { RpcFromTransport, SolanaRpcApiFromTransport } from './rpc-clusters';
56
import { DEFAULT_RPC_CONFIG } from './rpc-default-config';
7+
import { createDefaultRpcTransport, DefaultRpcTransportConfig } from './rpc-transport';
68

7-
type RpcConfig<TTransport extends RpcTransport> = Readonly<{
8-
transport: TTransport;
9-
}>;
9+
/** Creates a new Solana RPC using the default decorated HTTP transport. */
10+
export function createSolanaRpc<TClusterUrl extends ClusterUrl>(
11+
clusterUrl: TClusterUrl,
12+
config?: Omit<DefaultRpcTransportConfig<TClusterUrl>, 'url'>,
13+
) {
14+
return createSolanaRpcFromTransport(createDefaultRpcTransport({ url: clusterUrl, ...config }));
15+
}
1016

11-
export function createSolanaRpc<TTransport extends RpcTransport>(
12-
config: RpcConfig<TTransport>,
13-
): RpcFromTransport<SolanaRpcApiFromTransport<TTransport>, TTransport> {
14-
const api = createSolanaRpcApi(DEFAULT_RPC_CONFIG);
15-
return createRpc({ ...config, api }) as RpcFromTransport<SolanaRpcApiFromTransport<TTransport>, TTransport>;
17+
/** Creates a new Solana RPC using a custom transport. */
18+
export function createSolanaRpcFromTransport<TTransport extends RpcTransport>(transport: TTransport) {
19+
return createRpc({
20+
api: createSolanaRpcApi(DEFAULT_RPC_CONFIG),
21+
transport,
22+
}) as RpcFromTransport<SolanaRpcApiFromTransport<TTransport>, TTransport>;
1623
}

0 commit comments

Comments
 (0)