@subql/common-solana 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subql/common-solana",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "build": "rm -rf dist && tsc -b",
@@ -16,8 +16,8 @@
16
16
  "dependencies": {
17
17
  "@codama/nodes-from-anchor": "^1.1.11",
18
18
  "@codama/renderers-js": "^1.2.10",
19
- "@subql/common": "5.4.1-1",
20
- "@subql/types-solana": "1.0.0",
19
+ "@subql/common": "^5.7.0",
20
+ "@subql/types-solana": "1.0.1",
21
21
  "codama": "^1.2.11",
22
22
  "js-yaml": "^4.1.0",
23
23
  "pino": "^6.13.3",
@@ -35,5 +35,18 @@
35
35
  "@types/rimraf": "3.0.2",
36
36
  "ejs": "3.1.10",
37
37
  "lodash": "4.17.21"
38
- }
38
+ },
39
+ "files": [
40
+ "/dist",
41
+ "!/dist/**/*.spec.js",
42
+ "!/dist/**/*.spec.d.ts",
43
+ "!/dist/**/*.spec.js.map",
44
+ "!/dist/**/*.test.js",
45
+ "!/dist/**/*.test.d.ts",
46
+ "!/dist/**/*.test.js.map",
47
+ "package.json",
48
+ "README.md",
49
+ "CHANGELOG.md",
50
+ "LICENSE"
51
+ ]
39
52
  }
@@ -1,145 +0,0 @@
1
- // Copyright 2020-2025 SubQuery Pte Ltd authors & contributors
2
- // SPDX-License-Identifier: GPL-3.0
3
-
4
- import path from 'node:path';
5
- import {SolanaDatasourceKind, SolanaHandlerKind, SubqlRuntimeDatasource} from '@subql/types-solana';
6
- import rimraf from 'rimraf';
7
- import {generateIDLInterfaces, validateDiscriminators} from './codegen';
8
-
9
- const outDir = path.join(__dirname, 'test-output');
10
-
11
- describe('IDL Codegen', () => {
12
- afterEach(() => {
13
- rimraf.sync(outDir);
14
- });
15
-
16
- it('can generate an interface from an IDL', async () => {
17
- const ds: SubqlRuntimeDatasource = {
18
- kind: SolanaDatasourceKind.Runtime,
19
- startBlock: 0,
20
- assets: new Map([
21
- ['jupiter', {file: '../../../../node/test/JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4.idl.json'}],
22
- ]),
23
- mapping: {
24
- file: '',
25
- handlers: [],
26
- },
27
- };
28
-
29
- const renderTemplate = jest.fn().mockImplementation((templatePath, outputPath, templateData) => {
30
- /* DO nothing*/
31
- });
32
-
33
- await expect(generateIDLInterfaces([ds], outDir, renderTemplate)).resolves.not.toThrow();
34
-
35
- expect(renderTemplate).toHaveBeenCalledWith(
36
- path.resolve(__dirname, '../../templates/idl.ts.ejs'),
37
- path.join(outDir, '/src/types/handler-inputs/jupiter.ts'),
38
- {
39
- props: {
40
- name: 'jupiter',
41
- instructions: [
42
- 'Claim',
43
- 'ClaimToken',
44
- 'CloseToken',
45
- 'CreateOpenOrders',
46
- 'CreateProgramOpenOrders',
47
- 'CreateTokenLedger',
48
- 'CreateTokenAccount',
49
- 'ExactOutRoute',
50
- 'Route',
51
- 'RouteWithTokenLedger',
52
- 'SetTokenLedger',
53
- 'SharedAccountsExactOutRoute',
54
- 'SharedAccountsRoute',
55
- 'SharedAccountsRouteWithTokenLedger',
56
- ],
57
- },
58
- }
59
- );
60
- });
61
-
62
- describe('validating discriminators agains IDLs', () => {
63
- it('can validate that a discriminator is part of an IDL', async () => {
64
- const ds: SubqlRuntimeDatasource = {
65
- kind: SolanaDatasourceKind.Runtime,
66
- startBlock: 0,
67
- assets: new Map([
68
- [
69
- 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4',
70
- {file: '../../../../node/test/JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4.idl.json'},
71
- ],
72
- ]),
73
- mapping: {
74
- file: '',
75
- handlers: [
76
- {
77
- handler: 'handleClaimToken',
78
- kind: SolanaHandlerKind.Instruction,
79
- filter: {
80
- programId: 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4',
81
- discriminator: 'ClaimToken',
82
- },
83
- },
84
- ],
85
- },
86
- };
87
-
88
- await expect(validateDiscriminators([ds], outDir)).resolves.not.toThrow();
89
- });
90
-
91
- it('throws when an idl does not include the discriminator', async () => {
92
- const ds: SubqlRuntimeDatasource = {
93
- kind: SolanaDatasourceKind.Runtime,
94
- startBlock: 0,
95
- assets: new Map([
96
- [
97
- 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4',
98
- {file: '../../../../node/test/JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4.idl.json'},
99
- ],
100
- ]),
101
- mapping: {
102
- file: '',
103
- handlers: [
104
- {
105
- handler: 'handleClaimToken',
106
- kind: SolanaHandlerKind.Instruction,
107
- filter: {
108
- programId: 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4',
109
- discriminator: 'InvalidDiscriminator',
110
- },
111
- },
112
- ],
113
- },
114
- };
115
-
116
- await expect(validateDiscriminators([ds], outDir)).rejects.toThrow(
117
- 'Datasource has a handler with a discriminator InvalidDiscriminator but no matching instruction found in IDL'
118
- );
119
- });
120
-
121
- it('throws when there is no IDL', async () => {
122
- const ds: SubqlRuntimeDatasource = {
123
- kind: SolanaDatasourceKind.Runtime,
124
- startBlock: 0,
125
- mapping: {
126
- file: '',
127
- handlers: [
128
- {
129
- handler: 'handleClaimToken',
130
- kind: SolanaHandlerKind.Instruction,
131
- filter: {
132
- programId: 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4',
133
- discriminator: 'ClaimToken',
134
- },
135
- },
136
- ],
137
- },
138
- };
139
-
140
- await expect(validateDiscriminators([ds], outDir)).rejects.toThrow(
141
- 'Datasource has a handler with a discriminator but no IDL file reference'
142
- );
143
- });
144
- });
145
- });
@@ -1,96 +0,0 @@
1
- // Copyright 2020-2025 SubQuery Pte Ltd authors & contributors
2
- // SPDX-License-Identifier: GPL-3.0
3
-
4
- import assert from 'node:assert';
5
- import path from 'node:path';
6
- import {renderVisitor} from '@codama/renderers-js';
7
- import {SolanaDatasourceKind, SolanaHandlerKind, SubqlDatasource, SubqlRuntimeDatasource} from '@subql/types-solana';
8
- import {pascalCase} from 'codama';
9
- import {Data} from 'ejs';
10
- import {parseIdlFromFile, findInstructionDiscriminatorByName} from './idl';
11
-
12
- const INSTRUCTION_TEMPLATE_TS = path.resolve(__dirname, '../../templates/idl.ts.ejs');
13
- const CODAMA_PATH = '/src/types/program-interfaces';
14
- const IDL_PATH = '/src/types/handler-inputs';
15
-
16
- export async function validateDiscriminators(
17
- dataSources: SubqlRuntimeDatasource[],
18
- projectPath: string
19
- ): Promise<void> {
20
- const issues: string[] = [];
21
-
22
- for (const ds of dataSources) {
23
- for (const handler of ds.mapping.handlers) {
24
- if (!handler.filter) {
25
- break;
26
- }
27
- switch (handler.kind) {
28
- case SolanaHandlerKind.Instruction: {
29
- if (handler.filter.discriminator && handler.filter.programId) {
30
- const fileRef = ds.assets?.get(handler.filter.programId);
31
- if (!fileRef) {
32
- issues.push(`Datasource has a handler with a discriminator but no IDL file reference`);
33
- continue;
34
- }
35
-
36
- const idlPath = path.join(projectPath, fileRef.file);
37
- const idl = await parseIdlFromFile(idlPath);
38
-
39
- if (!findInstructionDiscriminatorByName(idl.getRoot(), handler.filter.discriminator)) {
40
- issues.push(
41
- `Datasource has a handler with a discriminator ${handler.filter.discriminator} but no matching instruction found in IDL`
42
- );
43
- }
44
- // TODO check discriminator in IDL
45
- }
46
- break;
47
- }
48
- default:
49
- /* Do nothing, there is nothing extra to validate*/
50
- }
51
- }
52
- }
53
-
54
- assert(issues.length === 0, issues.join('\n'));
55
- }
56
-
57
- export async function generateIDLInterfaces(
58
- dataSources: SubqlDatasource[],
59
- projectPath: string,
60
- renderTemplate: (templatePath: string, outputPath: string, templateData: Data) => Promise<void>
61
- ): Promise<void> {
62
- // @subql/cli package calls this function with datasources as an array of objects
63
- dataSources = dataSources.map((d) => ({
64
- ...d,
65
- assets: d?.assets ? (d.assets instanceof Map ? d.assets : new Map(Object.entries(d.assets))) : undefined,
66
- })) as SubqlRuntimeDatasource[];
67
-
68
- const allAssets = new Map(
69
- dataSources.filter((ds) => ds.assets !== undefined).flatMap((ds) => Array.from(ds.assets!.entries()))
70
- );
71
-
72
- await validateDiscriminators(
73
- dataSources.filter((ds) => ds.kind === SolanaDatasourceKind.Runtime) as SubqlRuntimeDatasource[],
74
- projectPath
75
- );
76
-
77
- for (const [name, fileRef] of allAssets) {
78
- const idlPath = path.join(projectPath, fileRef.file);
79
- const idl = await parseIdlFromFile(idlPath);
80
-
81
- const output = path.join(projectPath, CODAMA_PATH, name);
82
-
83
- await idl.accept(renderVisitor(output, {}));
84
-
85
- const instructionNames = idl.getRoot().program.instructions.map((inst) => pascalCase(inst.name));
86
-
87
- await renderTemplate(INSTRUCTION_TEMPLATE_TS, path.join(projectPath, IDL_PATH, `${name}.ts`), {
88
- props: {
89
- name,
90
- instructions: instructionNames,
91
- },
92
- });
93
-
94
- console.log(`* IDL ${name} generated`);
95
- }
96
- }
@@ -1,89 +0,0 @@
1
- // Copyright 2020-2025 SubQuery Pte Ltd authors & contributors
2
- // SPDX-License-Identifier: GPL-3.0
3
-
4
- import fs from 'node:fs';
5
- import {AnchorIdl, IdlV01, rootNodeFromAnchor} from '@codama/nodes-from-anchor';
6
- import {getBase16Encoder, getBase58Encoder, getBase64Encoder, getUtf8Encoder} from '@solana/codecs-strings';
7
- import {BytesValueNode, InstructionNode, RootNode, Codama, createFromRoot, camelCase} from 'codama';
8
-
9
- export type Idl = AnchorIdl | RootNode;
10
-
11
- export function isAnchorIdl(idl: Idl): idl is AnchorIdl {
12
- return !isRootNode(idl);
13
- }
14
-
15
- export function isAnchorIdlV01(idl: Idl): idl is IdlV01 {
16
- return isAnchorIdl(idl) && (idl.metadata as {spec?: string}).spec === '0.1.0';
17
- }
18
-
19
- export function isRootNode(idl: Idl): idl is RootNode {
20
- return !!(idl as RootNode).program?.publicKey;
21
- }
22
-
23
- export function parseIdl(idl: Idl): Codama {
24
- let codama = createFromRoot(rootNodeFromAnchor(idl as AnchorIdl));
25
- // Check if the idl was an anchor idl
26
- if (codama.getRoot().program.publicKey === '') {
27
- codama = createFromRoot(idl as RootNode);
28
- }
29
-
30
- return codama;
31
- }
32
-
33
- export async function parseIdlFromFile(path: string): Promise<Codama> {
34
- const idlStr = await fs.promises.readFile(path, 'utf-8');
35
- const idlJSON = JSON.parse(idlStr);
36
-
37
- return parseIdl(idlJSON);
38
- }
39
-
40
- export function getBytesFromBytesValueNode(node: BytesValueNode): Uint8Array {
41
- switch (node.encoding) {
42
- case 'utf8':
43
- return getUtf8Encoder().encode(node.data) as Uint8Array;
44
- case 'base16':
45
- return getBase16Encoder().encode(node.data) as Uint8Array;
46
- case 'base58':
47
- return getBase58Encoder().encode(node.data) as Uint8Array;
48
- case 'base64':
49
- default:
50
- return getBase64Encoder().encode(node.data) as Uint8Array;
51
- }
52
- }
53
-
54
- export function getInstructionDiscriminatorBytes(node: InstructionNode): Buffer {
55
- const discArg = node.arguments.find((arg) => arg.name === 'discriminator');
56
- if (!discArg) {
57
- throw new Error(`Instruction ${node.name} does not have a discriminator`);
58
- }
59
-
60
- // TODO what about other types of discriminators or ones that are larger than 1 byte?
61
- switch (discArg.defaultValue?.kind) {
62
- case 'numberValueNode':
63
- return Buffer.from([discArg.defaultValue.number]);
64
- case 'bytesValueNode':
65
- return Buffer.from(getBytesFromBytesValueNode(discArg.defaultValue));
66
- case undefined:
67
- break;
68
- default:
69
- throw new Error(`Unable to handle unknown discriminator type ${discArg.defaultValue?.kind}`);
70
- }
71
-
72
- throw new Error(`Unable to find discriminator for instruction ${node.name}`);
73
- }
74
-
75
- export function findInstructionDiscriminatorByName(rootNode: RootNode, name: string): Buffer | undefined {
76
- const inst = rootNode.program.instructions.find((inst) => inst.name === name || inst.name === camelCase(name));
77
- if (!inst) {
78
- return undefined;
79
- }
80
-
81
- try {
82
- return getInstructionDiscriminatorBytes(inst);
83
- } catch (e) {
84
- // logger.debug(
85
- // `Failed to get discriminator for instruction ${inst.name}: ${e}`,
86
- // );
87
- return undefined;
88
- }
89
- }
@@ -1,5 +0,0 @@
1
- // Copyright 2020-2025 SubQuery Pte Ltd authors & contributors
2
- // SPDX-License-Identifier: GPL-3.0
3
-
4
- export * from './codegen';
5
- export * from './idl';
package/src/index.ts DELETED
@@ -1,16 +0,0 @@
1
- // Copyright 2020-2025 SubQuery Pte Ltd authors & contributors
2
- // SPDX-License-Identifier: GPL-3.0
3
-
4
- export * from './codegen';
5
- export * from './project';
6
-
7
- import {SolanaNetworkModule} from '@subql/types-solana';
8
- import * as c from './codegen';
9
- import * as p from './project';
10
-
11
- // This provides a compiled time check to ensure that the correct exports are provided
12
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
13
- const _ = {
14
- ...p,
15
- ...c,
16
- } satisfies SolanaNetworkModule;
@@ -1,11 +0,0 @@
1
- // Copyright 2020-2025 SubQuery Pte Ltd authors & contributors
2
- // SPDX-License-Identifier: GPL-3.0
3
-
4
- export * from './load';
5
- export * from './models';
6
- export * from './types';
7
- export * from './utils';
8
- export * from './versioned';
9
-
10
- import { parseSolanaProjectManifest } from './load';
11
- export { parseSolanaProjectManifest as parseProjectManifest };
@@ -1,10 +0,0 @@
1
- // Copyright 2020-2025 SubQuery Pte Ltd authors & contributors
2
- // SPDX-License-Identifier: GPL-3.0
3
-
4
- import { SolanaProjectManifestVersioned, VersionedProjectManifest } from './versioned';
5
-
6
- export function parseSolanaProjectManifest(raw: unknown): SolanaProjectManifestVersioned {
7
- const projectManifest = new SolanaProjectManifestVersioned(raw as VersionedProjectManifest);
8
- projectManifest.validate();
9
- return projectManifest;
10
- }
@@ -1,213 +0,0 @@
1
- // Copyright 2020-2025 SubQuery Pte Ltd authors & contributors
2
- // SPDX-License-Identifier: GPL-3.0
3
-
4
- import {BaseDataSource, BlockFilterImpl, forbidNonWhitelisted} from '@subql/common';
5
- import {FileReference} from '@subql/types-core';
6
- import {
7
- SolanaHandlerKind,
8
- SolanaDatasourceKind,
9
- SolanaLogFilter,
10
- SubqlCustomHandler,
11
- SubqlMapping,
12
- SubqlHandler,
13
- SubqlRuntimeHandler,
14
- SubqlRuntimeDatasource,
15
- SubqlCustomDatasource,
16
- SubqlBlockHandler,
17
- SubqlTransactionHandler,
18
- SolanaTransactionFilter,
19
- SolanaBlockFilter,
20
- SolanaInstructionFilter,
21
- SubqlInstructionHandler,
22
- SubqlLogHandler,
23
- InstructionAccountFilter,
24
- } from '@subql/types-solana';
25
- import {plainToClass, Transform, Type} from 'class-transformer';
26
- import {
27
- IsArray,
28
- IsEnum,
29
- IsOptional,
30
- IsString,
31
- IsObject,
32
- ValidateNested,
33
- ValidateIf,
34
- IsDefined,
35
- IsBoolean,
36
- } from 'class-validator';
37
-
38
- export class BlockFilter extends BlockFilterImpl implements SolanaBlockFilter {}
39
-
40
- export class TransactionFilter implements SolanaTransactionFilter {
41
- @IsOptional()
42
- @IsString()
43
- signerAccountKey?: string;
44
- }
45
-
46
- export class InstructionFilter implements SolanaInstructionFilter {
47
- @ValidateIf((o) => o.discriminator !== undefined)
48
- @IsDefined({message: 'programId is required if discriminator is set'})
49
- @IsString()
50
- programId?: string;
51
-
52
- @IsOptional()
53
- @IsString()
54
- discriminator?: string;
55
-
56
- @IsOptional()
57
- @IsArray()
58
- accounts?: [
59
- InstructionAccountFilter,
60
- InstructionAccountFilter?,
61
- InstructionAccountFilter?,
62
- InstructionAccountFilter?,
63
- InstructionAccountFilter?,
64
- InstructionAccountFilter?,
65
- InstructionAccountFilter?,
66
- InstructionAccountFilter?,
67
- InstructionAccountFilter?,
68
- InstructionAccountFilter?,
69
- InstructionAccountFilter?,
70
- InstructionAccountFilter?,
71
- InstructionAccountFilter?,
72
- InstructionAccountFilter?,
73
- InstructionAccountFilter?,
74
- InstructionAccountFilter?
75
- ];
76
-
77
- @IsOptional()
78
- @IsBoolean()
79
- includeFalied?: boolean;
80
- }
81
-
82
- export class LogFilter implements SolanaLogFilter {
83
- @IsOptional()
84
- @IsString()
85
- programId?: string;
86
- }
87
-
88
- export class BlockHandler implements SubqlBlockHandler {
89
- @IsObject()
90
- @IsOptional()
91
- @Type(() => BlockFilter)
92
- filter?: BlockFilter;
93
- @IsEnum(SolanaHandlerKind, {groups: [SolanaHandlerKind.Block]})
94
- kind!: SolanaHandlerKind.Block;
95
- @IsString()
96
- handler!: string;
97
- }
98
-
99
- export class TransactionHandler implements SubqlTransactionHandler {
100
- @IsOptional()
101
- @ValidateNested()
102
- @Type(() => TransactionFilter)
103
- filter?: SolanaTransactionFilter;
104
- @IsEnum(SolanaHandlerKind, {groups: [SolanaHandlerKind.Transaction]})
105
- kind!: SolanaHandlerKind.Transaction;
106
- @IsString()
107
- handler!: string;
108
- }
109
-
110
- export class InstructionHandler implements SubqlInstructionHandler {
111
- @IsOptional()
112
- @ValidateNested()
113
- @Type(() => InstructionFilter)
114
- filter?: SolanaInstructionFilter;
115
- @IsEnum(SolanaHandlerKind, {groups: [SolanaHandlerKind.Instruction]})
116
- kind!: SolanaHandlerKind.Instruction;
117
- @IsString()
118
- handler!: string;
119
- }
120
-
121
- export class LogHandler implements SubqlLogHandler {
122
- @IsOptional()
123
- @ValidateNested()
124
- @Type(() => LogFilter)
125
- filter?: SolanaLogFilter;
126
- @IsEnum(SolanaHandlerKind, {groups: [SolanaHandlerKind.Log]})
127
- kind!: SolanaHandlerKind.Log;
128
- @IsString()
129
- handler!: string;
130
- }
131
-
132
- export class CustomHandler implements SubqlCustomHandler {
133
- @IsString()
134
- kind!: string;
135
- @IsString()
136
- handler!: string;
137
- @IsObject()
138
- @IsOptional()
139
- filter?: Record<string, unknown>;
140
- }
141
-
142
- export class SolanaMapping implements SubqlMapping {
143
- @Transform((params) => {
144
- const handlers: SubqlHandler[] = params.value;
145
- return handlers.map((handler) => {
146
- switch (handler.kind) {
147
- case SolanaHandlerKind.Log:
148
- return plainToClass(LogHandler, handler);
149
- case SolanaHandlerKind.Instruction:
150
- return plainToClass(InstructionHandler, handler);
151
- case SolanaHandlerKind.Transaction:
152
- return plainToClass(TransactionHandler, handler);
153
- case SolanaHandlerKind.Block:
154
- return plainToClass(BlockHandler, handler);
155
- default:
156
- throw new Error(`handler ${(handler as any).kind} not supported`);
157
- }
158
- });
159
- })
160
- @IsArray()
161
- @ValidateNested()
162
- handlers!: SubqlHandler[];
163
- @IsString()
164
- file!: string;
165
- }
166
-
167
- export class CustomMapping implements SubqlMapping<SubqlCustomHandler> {
168
- @IsArray()
169
- @Type(() => CustomHandler)
170
- @ValidateNested()
171
- handlers!: CustomHandler[];
172
- @IsString()
173
- file!: string;
174
- }
175
-
176
- export class RuntimeDataSourceBase<M extends SubqlMapping<SubqlRuntimeHandler>>
177
- extends BaseDataSource
178
- implements SubqlRuntimeDatasource<M>
179
- {
180
- @IsEnum(SolanaDatasourceKind, {
181
- groups: [SolanaDatasourceKind.Runtime],
182
- })
183
- kind!: SolanaDatasourceKind.Runtime;
184
- @Type(() => SolanaMapping)
185
- @ValidateNested()
186
- mapping!: M;
187
- @Type(() => FileReferenceImpl)
188
- @ValidateNested({each: true})
189
- @IsOptional()
190
- assets?: Map<string, FileReference>;
191
- }
192
-
193
- export class FileReferenceImpl implements FileReference {
194
- @IsString()
195
- file!: string;
196
- }
197
-
198
- export class CustomDataSourceBase<K extends string, M extends SubqlMapping = SubqlMapping<SubqlCustomHandler>>
199
- extends BaseDataSource
200
- implements SubqlCustomDatasource<K, M>
201
- {
202
- @IsString()
203
- kind!: K;
204
- @Type(() => CustomMapping)
205
- @ValidateNested()
206
- mapping!: M;
207
- @Type(() => FileReferenceImpl)
208
- @ValidateNested({each: true})
209
- assets!: Map<string, FileReference>;
210
- @Type(() => FileReferenceImpl)
211
- @IsObject()
212
- processor!: FileReference;
213
- }