Skip to content

Commit 0d05aa2

Browse files
committed
feat: add Helius SDK
1 parent 708c381 commit 0d05aa2

File tree

7 files changed

+1332
-26
lines changed

7 files changed

+1332
-26
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ DATABASE_PROVISION=true
4949
GRAPHQL_PLAYGROUND=true
5050
# JWT Secret (generate a random string with `openssl rand -hex 32`)
5151
JWT_SECRET=
52+
# Helius API Key
53+
HELIUS_API_KEY=
5254
# Host to listen on
5355
HOST=127.0.0.1
5456
# Network Cluster configuration. Configure at least one in order to start the API.

libs/api/core/data-access/src/lib/api-core-network.service.ts

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,27 @@ import { Injectable, Logger } from '@nestjs/common'
55
import { OnEvent } from '@nestjs/event-emitter'
66
import { NetworkCluster } from '@prisma/client'
77
import { AccountInfo, Connection, ParsedAccountData, PublicKey } from '@solana/web3.js'
8+
import { Helius, HeliusCluster } from 'helius-sdk'
89
import { CORE_APP_STARTED } from './api-core.events'
910
import { ApiCoreConfigService, NetworkClusterMap } from './config/api-core-config.service'
1011

12+
function heliusCluster(cluster: NetworkCluster): HeliusCluster {
13+
switch (cluster) {
14+
case NetworkCluster.SolanaMainnet:
15+
return 'mainnet-beta'
16+
case NetworkCluster.SolanaDevnet:
17+
return 'devnet'
18+
default:
19+
throw new Error(`HeliusCluster: Unsupported cluster: ${cluster}`)
20+
}
21+
}
22+
1123
@Injectable()
1224
export class ApiCoreNetworkService {
1325
private readonly logger = new Logger(ApiCoreNetworkService.name)
1426
private readonly connectionMap = new Map<NetworkCluster, Connection>()
1527
private readonly clusterMap: NetworkClusterMap = this.config.networkClusters
28+
private readonly helius: Map<NetworkCluster, Helius> = new Map()
1629
private readonly umis: Map<NetworkCluster, Umi> = new Map()
1730
constructor(readonly config: ApiCoreConfigService) {}
1831

@@ -26,9 +39,15 @@ export class ApiCoreNetworkService {
2639
const connection = this.ensureConnection(cluster as NetworkCluster)
2740
try {
2841
const version = await connection.getVersion()
29-
this.logger.debug(`Connected to ${cluster} cluster, version ${version['solana-core']}`)
42+
this.logger.debug(`[${cluster}] Connected to cluster version ${version['solana-core']}`)
3043
} catch (error) {
31-
this.logger.error(`Error connecting to ${cluster} cluster, ${error}`)
44+
this.logger.error(`[${cluster}] Error connecting to cluster, ${error}`)
45+
}
46+
// Helius SDK only supports devnet and mainnet
47+
if (this.config.heliusApiKey && ['SolanaDevnet', 'SolanaMainnet'].includes(cluster.toString())) {
48+
this.logger.debug(`[${cluster}] Helius SDK enabled for cluster`)
49+
this.helius.set(cluster as NetworkCluster, this.configureHelius(cluster as NetworkCluster))
50+
this.listWebhooks(cluster as NetworkCluster)
3251
}
3352
}
3453
}
@@ -49,11 +68,26 @@ export class ApiCoreNetworkService {
4968
return found
5069
}
5170

52-
getClusterEndpoint(cluster: NetworkCluster): string {
71+
ensureHelius(cluster: NetworkCluster): Helius {
72+
const found = this.getHelius(cluster)
73+
if (!found) {
74+
throw new Error(`Connection ${cluster} not found`)
75+
}
76+
return found
77+
}
78+
79+
private configureHelius(cluster: NetworkCluster) {
80+
if (!this.config.heliusApiKey) {
81+
throw new Error('Helius API key not configured')
82+
}
83+
return new Helius(this.config.heliusApiKey as string, heliusCluster(cluster as NetworkCluster))
84+
}
85+
86+
private getClusterEndpoint(cluster: NetworkCluster): string {
5387
return this.clusterMap[cluster]
5488
}
5589

56-
getConnection(cluster: NetworkCluster) {
90+
private getConnection(cluster: NetworkCluster) {
5791
if (!this.connectionMap.has(cluster)) {
5892
const endpoint = this.ensureClusterEndpoint(cluster)
5993
this.connectionMap.set(cluster, new Connection(endpoint, 'confirmed'))
@@ -65,6 +99,23 @@ export class ApiCoreNetworkService {
6599
return connection
66100
}
67101

102+
private getHelius(cluster: NetworkCluster) {
103+
if (!this.helius.has(cluster)) {
104+
this.helius.set(cluster, new Helius(this.config.heliusApiKey as string, heliusCluster(cluster)))
105+
}
106+
return this.helius.get(cluster)
107+
}
108+
109+
private listWebhooks(cluster: NetworkCluster) {
110+
const helius = this.ensureHelius(cluster)
111+
helius.getAllWebhooks().then((webhooks) => {
112+
this.logger.debug(`[${cluster}] Helius Webhooks: ${webhooks.length} configured`)
113+
for (const webhook of webhooks) {
114+
this.logger.debug(`[${cluster}] - Webhook: ${webhook.accountAddresses.length} addresses`)
115+
}
116+
})
117+
}
118+
68119
async getTokenAccountsByMint({
69120
cluster,
70121
wallet,

libs/api/core/data-access/src/lib/config/api-core-config.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ export class ApiCoreConfigService {
106106
return this.service.get('environment')
107107
}
108108

109+
get heliusApiKey() {
110+
return this.service.get('heliusApiKey')
111+
}
112+
109113
get host() {
110114
return this.service.get<string>('host')
111115
}

libs/api/core/data-access/src/lib/config/configuration.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface ApiCoreConfig {
3535
corsOrigins: string[]
3636
databaseProvision: boolean
3737
environment: Env
38+
heliusApiKey?: string
3839
host: string
3940
jwtSecret: string
4041
networkClusterSolanaCustom?: string
@@ -61,6 +62,7 @@ export function configuration(): ApiCoreConfig {
6162
corsOrigins,
6263
databaseProvision: process.env['DATABASE_PROVISION'] === 'true',
6364
environment: (process.env['NODE_ENV'] as Env) || 'development',
65+
heliusApiKey: process.env['HELIUS_API_KEY'],
6466
host: process.env['HOST'] as string,
6567
jwtSecret: process.env['JWT_SECRET'] as string,
6668
networkClusterSolanaCustom: process.env['NETWORK_CLUSTER_SOLANA_CUSTOM'],

libs/api/core/data-access/src/lib/config/validation-schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const validationSchema = Joi.object({
2121
DATABASE_URL: Joi.string(),
2222
GRAPHQL_PLAYGROUND: Joi.boolean().default(false),
2323
JWT_SECRET: Joi.string().required(),
24+
HELIUS_API_KEY: Joi.string().optional(),
2425
HOST: Joi.string().default('0.0.0.0'),
2526
NETWORK_CLUSTER_SOLANA_CUSTOM: Joi.string().optional(),
2627
NETWORK_CLUSTER_SOLANA_DEVNET: Joi.string().optional(),

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"graphql": "^16.9.0",
7373
"graphql-codegen-typescript-validation-schema": "^0.15.0",
7474
"graphql-scalars": "^1.23.0",
75+
"helius-sdk": "^1.3.5",
7576
"joi": "^17.13.3",
7677
"jotai": "^2.8.3",
7778
"linkify-react": "^4.1.3",

0 commit comments

Comments
 (0)