Skip to content

Commit d6ab2ed

Browse files
feat: add solana chain definitions (#84)
This pr replaces #60. This is need because of a merge order mistake that I did.
2 parents 2ae777d + c55371a commit d6ab2ed

File tree

15 files changed

+269
-56
lines changed

15 files changed

+269
-56
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@
6868
"types": "./dist/evm/releases/index.d.ts",
6969
"default": "./dist/evm/releases/index.js"
7070
},
71+
"./solana": {
72+
"types": "./dist/solana/index.d.ts",
73+
"default": "./dist/solana/index.js"
74+
},
7175
"./package.json": "./package.json",
7276
"./dist/*": "./dist/*"
7377
},

src/evm/chains/queries.ts

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,5 @@
1-
import { sortChains } from "@src/helpers";
1+
import { createChainQueries } from "@src/internal/factories/chains";
22
import type { Sablier } from "@src/types";
3-
import _ from "lodash";
43
import * as chains from "./data";
54

6-
export const chainsQueries = {
7-
get: (chainId: number): Sablier.EVM.Chain | undefined => {
8-
return _.find(chains, (c) => c.id === chainId);
9-
},
10-
getAll: (): Sablier.EVM.Chain[] => {
11-
return sortChains(_.values(chains));
12-
},
13-
getBySlug: (slug: string): Sablier.EVM.Chain | undefined => {
14-
return _.find(chains, (c) => c.slug === slug);
15-
},
16-
getMainnets: (): Sablier.EVM.Chain[] => {
17-
return sortChains(_.filter(_.values(chains), (c) => !c.isTestnet));
18-
},
19-
getOrThrow: (chainId: number): Sablier.EVM.Chain => {
20-
const chain = _.find(chains, (c) => c.id === chainId);
21-
if (!chain) {
22-
throw new Error(`Sablier SDK: Chain with ID ${chainId} not found`);
23-
}
24-
return chain;
25-
},
26-
getTestnets: (): Sablier.EVM.Chain[] => {
27-
return sortChains(_.filter(_.values(chains), (c) => c.isTestnet));
28-
},
29-
};
5+
export const chainsQueries = createChainQueries<Sablier.EVM.Chain>(chains);

src/evm/types.ts

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
1-
import type { Chain as ViemChain } from "viem";
1+
import type { Shared } from "@src/shared/types";
22
import type * as enums from "./enums";
33

4-
/**
5-
* @see https://github.com/wevm/viem/discussions/3678
6-
*/
7-
type ChainBlockExplorer = {
8-
name: string;
9-
url: string;
10-
apiUrl?: string | undefined;
11-
};
12-
134
type Repository = {
145
commit: string;
156
url: `https://github.com/sablier-labs/${string}`;
@@ -21,32 +12,17 @@ export namespace EVM {
2112
export type Address = `0x${string}`;
2213

2314
export type AbiMap = { [contractName: string]: readonly object[] };
24-
export type Chain = ViemChain & {
25-
blockExplorers: {
26-
[key: string]: ChainBlockExplorer;
27-
default: ChainBlockExplorer;
28-
};
29-
/** Whether this chain is supported by the Sablier Interface at https://app.sablier.com. */
30-
isSupportedByUI: boolean;
31-
/** Whether this is a testnet network. */
32-
isTestnet: boolean;
15+
export type Chain = Shared.Chain & {
3316
/** Whether this is a zkEVM like zkSync. */
3417
isZK: boolean;
35-
nativeCurrency: ViemChain["nativeCurrency"] & {
36-
coinGeckoId: string;
37-
};
38-
rpc: {
18+
rpc: Shared.Chain["rpc"] & {
3919
/** Alchemy RPC URL generator. */
4020
alchemy?: (apiKey: string) => string;
41-
/** Default RPC URL. */
42-
defaults: string[];
4321
/** Infura RPC URL generator. */
4422
infura?: (apiKey: string) => string;
4523
/** RouteMesh RPC URL generator. */
4624
routemesh?: (apiKey: string) => string;
4725
};
48-
/** Used in deployment files to identify the chain, e.g., arbitrum-sepolia. */
49-
slug: string;
5026
};
5127

5228
/**

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
export * from "./evm";
21
export * from "./helpers";
32
export * from "./sablier";
43
export * from "./types";

src/internal/factories/chains.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { sortChains } from "@src/helpers";
2+
import type { Shared } from "@src/shared/types";
3+
import _ from "lodash";
4+
5+
/**
6+
* Generic factory function to create type-safe chain query objects.
7+
*
8+
* @template T - Chain type extending ChainCommon constraint
9+
* @param chains - Record of chain definitions keyed by chain identifier
10+
* @returns Query object with type-safe methods for chain operations
11+
*
12+
* @example
13+
* ```typescript
14+
* const evmQueries = createChainQueries<Sablier.EVM.Chain>(evmChains);
15+
* const solanaQueries = createChainQueries<Sablier.Solana.Chain>(solanaChains);
16+
* ```
17+
*/
18+
export function createChainQueries<T extends Shared.Chain>(chains: Record<string, T>) {
19+
return {
20+
/**
21+
* Find a chain by its numeric ID.
22+
*
23+
* @param chainId - The numeric chain identifier
24+
* @returns The chain if found, undefined otherwise
25+
*/
26+
get: (chainId: number): T | undefined => {
27+
return _.find(chains, (c) => c.id === chainId);
28+
},
29+
30+
/**
31+
* Get all chains sorted by name.
32+
*
33+
* @returns Array of all chains sorted alphabetically
34+
*/
35+
getAll: (): T[] => {
36+
return sortChains(_.values(chains));
37+
},
38+
39+
/**
40+
* Find a chain by its slug identifier.
41+
*
42+
* @param slug - The chain slug (e.g., "ethereum", "solana-mainnet")
43+
* @returns The chain if found, undefined otherwise
44+
*/
45+
getBySlug: (slug: string): T | undefined => {
46+
return _.find(chains, (c) => c.slug === slug);
47+
},
48+
49+
/**
50+
* Get all mainnet chains sorted by name.
51+
*
52+
* @returns Array of mainnet chains sorted alphabetically
53+
*/
54+
getMainnets: (): T[] => {
55+
return sortChains(_.filter(_.values(chains), (c) => !c.isTestnet));
56+
},
57+
58+
/**
59+
* Find a chain by its numeric ID, throwing an error if not found.
60+
*
61+
* @param chainId - The numeric chain identifier
62+
* @returns The chain
63+
* @throws Error if chain with the given ID is not found
64+
*/
65+
getOrThrow: (chainId: number): T => {
66+
const chain = _.find(chains, (c) => c.id === chainId);
67+
if (!chain) {
68+
throw new Error(`Sablier SDK: Chain with ID ${chainId} not found`);
69+
}
70+
return chain;
71+
},
72+
73+
/**
74+
* Get all testnet chains sorted by name.
75+
*
76+
* @returns Array of testnet chains sorted alphabetically
77+
*/
78+
getTestnets: (): T[] => {
79+
return sortChains(_.filter(_.values(chains), (c) => c.isTestnet));
80+
},
81+
};
82+
}

src/sablier.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @file This file exports the Sablier object, singleton that contains the queries for the
3-
* chains, contracts, and releases.
3+
* chains, contracts, and releases for both evm and solana compatible chains.
44
*
55
* @example
66
* ```typescript
@@ -27,6 +27,7 @@ import _ from "lodash";
2727
import { chainsQueries as evmChainsQueries } from "./evm/chains/queries";
2828
import { contractsQueries as evmContractsQueries } from "./evm/contracts/queries";
2929
import { releasesQueries as evmReleasesQueries } from "./evm/releases/queries";
30+
import { chainsQueries as solanaChainsQueries } from "./solana/chains/queries";
3031

3132
/**
3233
* Has to be defined here to avoid circular dependencies.
@@ -53,7 +54,12 @@ const evm = {
5354
releases: evmReleasesQueries,
5455
};
5556

57+
const solana = {
58+
chains: solanaChainsQueries,
59+
};
60+
5661
export const sablier = {
5762
...evm, // re-exporting for backward compatibility
5863
evm,
64+
solana,
5965
};

src/shared/types.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Chain as ViemChain } from "viem";
2+
3+
/**
4+
* @see https://github.com/wevm/viem/discussions/3678
5+
*/
6+
type ChainBlockExplorer = {
7+
name: string;
8+
url: string;
9+
apiUrl?: string | undefined;
10+
};
11+
12+
export namespace Shared {
13+
/**
14+
* Common properties shared by EVM and Solana chains.
15+
* This type represents the minimal interface required for chain queries and operations.
16+
*/
17+
export type Chain = ViemChain & {
18+
blockExplorers: {
19+
[key: string]: ChainBlockExplorer;
20+
default: ChainBlockExplorer;
21+
};
22+
/** Whether this chain is supported by the Sablier Interface at https://app.sablier.com. */
23+
isSupportedByUI: boolean;
24+
/** Whether this is a testnet network. */
25+
isTestnet: boolean;
26+
nativeCurrency: ViemChain["nativeCurrency"] & {
27+
coinGeckoId: string;
28+
};
29+
rpc: {
30+
/** Default RPC URL. */
31+
defaults: string[];
32+
[key: string]: unknown;
33+
};
34+
/** Used in deployment files to identify the chain, e.g., arbitrum-sepolia. */
35+
slug: string;
36+
};
37+
}

src/solana/chains/data.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import type { Sablier } from "@src/types";
2+
3+
/**
4+
* Solana does not have chain IDs. These are made-up numbers so that we can use the same type for EVM and Solana chains.
5+
*/
6+
const CHAIN_ID_SOLANA_MAINNET_BETA = 900000010;
7+
const CHAIN_ID_SOLANA_DEVNET = 900000020;
8+
9+
export const solanaDevnet: Sablier.Solana.Chain = {
10+
blockExplorers: {
11+
default: { name: "Explorer", url: "https://solscan.io?cluster=devnet" },
12+
solanaFm: {
13+
name: "Solana FM",
14+
url: "https://solana.fm?cluster=devnet-alpha",
15+
},
16+
},
17+
chainlink: {
18+
feed: "99B2bTijsU6f1GCT73HmdR7HCFFjGMBcPZY6jZ96ynrR",
19+
program: "HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny",
20+
},
21+
contracts: {},
22+
definition: {
23+
chainCode: "SOLDEV",
24+
chainId: CHAIN_ID_SOLANA_DEVNET,
25+
cluster: "devnet",
26+
},
27+
id: CHAIN_ID_SOLANA_DEVNET,
28+
isSupportedByUI: true,
29+
isTestnet: true,
30+
name: "Devnet",
31+
nativeCurrency: {
32+
coinGeckoId: "solana",
33+
decimals: 9,
34+
name: "Solana",
35+
symbol: "SOL",
36+
},
37+
rpc: {
38+
defaults: ["https://api.devnet-beta.solana.com/"],
39+
helius: (key) => `https://devnet.helius-rpc.com/?api-key=${key}`,
40+
},
41+
rpcUrls: {
42+
default: {
43+
http: ["https://api.devnet-beta.solana.com/"],
44+
},
45+
},
46+
slug: "solana-devnet",
47+
} as const;
48+
49+
export const solanaMainnetBeta: Sablier.Solana.Chain = {
50+
blockExplorers: {
51+
default: { name: "Explorer", url: "https://solscan.io" },
52+
solanaFm: { name: "Solana FM", url: "https://solana.fm" },
53+
},
54+
chainlink: {
55+
feed: "99B2bTijsU6f1GCT73HmdR7HCFFjGMBcPZY6jZ96ynrR",
56+
program: "HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny",
57+
},
58+
contracts: {},
59+
definition: {
60+
chainCode: "SOL",
61+
chainId: CHAIN_ID_SOLANA_MAINNET_BETA,
62+
cluster: "mainnet-beta",
63+
},
64+
id: CHAIN_ID_SOLANA_MAINNET_BETA,
65+
isSupportedByUI: true,
66+
isTestnet: false,
67+
name: "Solana",
68+
nativeCurrency: {
69+
coinGeckoId: "solana",
70+
decimals: 9,
71+
name: "Solana",
72+
symbol: "SOL",
73+
},
74+
rpc: {
75+
defaults: ["https://api.mainnet-beta.solana.com/"],
76+
helius: (key) => `https://mainnet.helius-rpc.com/?api-key=${key}`,
77+
},
78+
rpcUrls: {
79+
default: {
80+
http: ["https://api.mainnet-beta.solana.com/"],
81+
},
82+
},
83+
slug: "solana-mainnet-beta",
84+
} as const;

src/solana/chains/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * as chains from "./data";
2+
export * from "./data";

src/solana/chains/queries.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { createChainQueries } from "@src/internal/factories/chains";
2+
import type { Sablier } from "@src/types";
3+
import * as chains from "./data";
4+
5+
export const chainsQueries = createChainQueries<Sablier.Solana.Chain>(chains);

0 commit comments

Comments
 (0)