Skip to content

Commit

Permalink
feat(experimental): add programNotifications websocket method (#1651)
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec authored Oct 6, 2023
1 parent d9dcd1c commit 5432cbe
Show file tree
Hide file tree
Showing 5 changed files with 381 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ export function getAllowedNumericKeypathsForNotification(): AllowedNumericKeypat
if (!memoizedNotificationKeypaths) {
memoizedNotificationKeypaths = {
accountNotifications: jsonParsedAccountsConfigs.map(c => ['value', ...c]),
programNotifications: jsonParsedAccountsConfigs.flatMap(c => [
['value', KEYPATH_WILDCARD, 'account', ...c],
[KEYPATH_WILDCARD, 'account', ...c],
]),
};
}
return memoizedNotificationKeypaths;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Commitment } from '../../rpc-methods';

describe('programNotifications', () => {
(['confirmed', 'finalized', 'processed'] as Commitment[]).forEach(commitment => {
describe(`when called with \`${commitment}\` commitment`, () => {
it.todo('produces program account notifications');
});
});

describe('when called with a program with no accounts', () => {
it.todo('returns an empty list');
});

describe('when called with base58 encoding', () => {
it.todo('produces program account notifications with annotated base58 encoding');
});

describe('when called with base64 encoding', () => {
it.todo('produces program account notifications with annotated base64 encoding');
});

describe('when called with base64+zstd encoding', () => {
it.todo('produces program account notifications with annotated base64+zstd encoding');
});

describe('when called with jsonParsed encoding', () => {
describe('for an account without parse-able JSON data', () => {
it.todo('falls back to annotated base64');
});

describe('for an account with parse-able JSON data', () => {
it.todo('returns parsed JSON data for AddressLookupTable account');

it.todo('returns parsed JSON data for BpfLoaderUpgradeable account');

it.todo('returns parsed JSON data for Config validator account');

it.todo('returns parsed JSON data for Config stake account');

it.todo('returns parsed JSON data for Nonce account');

it.todo('returns parsed JSON data for SPL Token mint account');

it.todo('returns parsed JSON data for SPL Token token account');

it.todo('returns parsed JSON data for SPL token multisig account');

it.todo('returns parsed JSON data for SPL Token 22 mint account');

it.todo('returns parsed JSON data for Stake account');

it.todo('returns parsed JSON data for Sysvar rent account');

it.todo('returns parsed JSON data for Vote account');
});
});

describe('when called with no encoding', () => {
it.todo('returns base58 data without an annotation');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */

import { Base58EncodedAddress } from '@solana/addresses';
import { PendingRpcSubscription, RpcSubscriptions } from '@solana/rpc-transport/dist/types/json-rpc-types';

import { LamportsUnsafeBeyond2Pow53Minus1 } from '../../lamports';
import {
Base58EncodedBytes,
Base58EncodedDataResponse,
Base64EncodedBytes,
Base64EncodedDataResponse,
Base64EncodedZStdCompressedDataResponse,
RpcResponse,
U64UnsafeBeyond2Pow53Minus1,
} from '../../rpc-methods/common';
import { ProgramNotificationsApi } from '../program-notifications';

async () => {
const rpcSubscriptions = null as unknown as RpcSubscriptions<ProgramNotificationsApi>;
// See scripts/fixtures/GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G.json
// Note: Only using this address for type tests. It's not actually a program.
const programId =
'GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G' as Base58EncodedAddress<'GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G'>;

type TNotificationBase = Readonly<{
account: Readonly<{
executable: boolean;
lamports: LamportsUnsafeBeyond2Pow53Minus1;
owner: Base58EncodedAddress;
rentEpoch: U64UnsafeBeyond2Pow53Minus1;
}>;
pubkey: Base58EncodedAddress;
}>;

// No optional configs
rpcSubscriptions.programNotifications(programId) satisfies PendingRpcSubscription<
RpcResponse<
TNotificationBase & {
account: {
data: Base58EncodedBytes;
};
}
>
>;
rpcSubscriptions
.programNotifications(programId)
.subscribe({ abortSignal: new AbortController().signal }) satisfies Promise<
AsyncIterable<
RpcResponse<
TNotificationBase & {
account: {
data: Base58EncodedBytes;
};
}
>
>
>;
// With optional configs
rpcSubscriptions.programNotifications(programId, { commitment: 'confirmed' }) satisfies PendingRpcSubscription<
RpcResponse<
TNotificationBase & {
account: {
data: Base58EncodedBytes;
};
}
>
>;
rpcSubscriptions
.programNotifications(programId, { commitment: 'confirmed' })
.subscribe({ abortSignal: new AbortController().signal }) satisfies Promise<
AsyncIterable<
RpcResponse<
TNotificationBase & {
account: {
data: Base58EncodedBytes;
};
}
>
>
>;
// Base58 encoded data
rpcSubscriptions.programNotifications(programId, { encoding: 'base58' }) satisfies PendingRpcSubscription<
RpcResponse<
TNotificationBase & {
account: {
data: Base58EncodedDataResponse;
};
}
>
>;
rpcSubscriptions
.programNotifications(programId, { encoding: 'base58' })
.subscribe({ abortSignal: new AbortController().signal }) satisfies Promise<
AsyncIterable<
RpcResponse<
TNotificationBase & {
account: {
data: Base58EncodedDataResponse;
};
}
>
>
>;
// Base64 encoded data
rpcSubscriptions.programNotifications(programId, { encoding: 'base64' }) satisfies PendingRpcSubscription<
RpcResponse<
TNotificationBase & {
account: {
data: Base64EncodedDataResponse;
};
}
>
>;
rpcSubscriptions
.programNotifications(programId, { encoding: 'base64' })
.subscribe({ abortSignal: new AbortController().signal }) satisfies Promise<
AsyncIterable<
RpcResponse<
TNotificationBase & {
account: {
data: Base64EncodedDataResponse;
};
}
>
>
>;
// Base64 + ZSTD encoded data
rpcSubscriptions.programNotifications(programId, { encoding: 'base64+zstd' }) satisfies PendingRpcSubscription<
RpcResponse<
TNotificationBase & {
account: {
data: Base64EncodedZStdCompressedDataResponse;
};
}
>
>;
rpcSubscriptions
.programNotifications(programId, { encoding: 'base64+zstd' })
.subscribe({ abortSignal: new AbortController().signal }) satisfies Promise<
AsyncIterable<
RpcResponse<
TNotificationBase & {
account: {
data: Base64EncodedZStdCompressedDataResponse;
};
}
>
>
>;
// JSON parsed data
rpcSubscriptions.programNotifications(programId, { encoding: 'jsonParsed' }) satisfies PendingRpcSubscription<
RpcResponse<
TNotificationBase & {
account: {
data:
| Readonly<{
program: string;
parsed: unknown;
space: U64UnsafeBeyond2Pow53Minus1;
}>
| Base64EncodedDataResponse;
};
}
>
>;
rpcSubscriptions
.programNotifications(programId, { encoding: 'jsonParsed' })
.subscribe({ abortSignal: new AbortController().signal }) satisfies Promise<
AsyncIterable<
RpcResponse<
TNotificationBase & {
account: {
data:
| Readonly<{
program: string;
parsed: unknown;
space: U64UnsafeBeyond2Pow53Minus1;
}>
| Base64EncodedDataResponse;
};
}
>
>
>;

// Filters
({
filters: [
{
bytes: 'bytes' as Base58EncodedBytes,
encoding: 'base58',
offset: 0n as U64UnsafeBeyond2Pow53Minus1,
},
],
}) satisfies Parameters<RpcSubscriptions<ProgramNotificationsApi>['programNotifications']>[1];
// Can't flop them
({
filters: [
{
// @ts-expect-error Can't flop them
bytes: 'bytes' as Base58EncodedBytes,
encoding: 'base64',
offset: 0n as U64UnsafeBeyond2Pow53Minus1,
},
],
}) satisfies Parameters<RpcSubscriptions<ProgramNotificationsApi>['programNotifications']>[1];
({
filters: [
{
bytes: 'bytes' as Base64EncodedBytes,
encoding: 'base64',
offset: 0n as U64UnsafeBeyond2Pow53Minus1,
},
],
}) satisfies Parameters<RpcSubscriptions<ProgramNotificationsApi>['programNotifications']>[1];
// Can't flop them
({
filters: [
{
// @ts-expect-error Can't flop them
bytes: 'bytes' as Base64EncodedBytes,
encoding: 'base58',
offset: 0n as U64UnsafeBeyond2Pow53Minus1,
},
],
}) satisfies Parameters<RpcSubscriptions<ProgramNotificationsApi>['programNotifications']>[1];
};
2 changes: 2 additions & 0 deletions packages/rpc-core/src/rpc-subscriptions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { patchParamsForSolanaLabsRpc } from '../params-patcher';
import { patchResponseForSolanaLabsRpcSubscriptions } from '../response-patcher';
import { AccountNotificationsApi } from './account-notifications';
import { LogsNotificationsApi } from './logs-notifications';
import { ProgramNotificationsApi } from './program-notifications';
import { RootNotificationsApi } from './root-notifications';
import { SignatureNotificationsApi } from './signature-notifications';
import { SlotNotificationsApi } from './slot-notifications';
Expand All @@ -16,6 +17,7 @@ type Config = Readonly<{

export type SolanaRpcSubscriptions = AccountNotificationsApi &
LogsNotificationsApi &
ProgramNotificationsApi &
RootNotificationsApi &
SignatureNotificationsApi &
SlotNotificationsApi;
Expand Down
Loading

0 comments on commit 5432cbe

Please sign in to comment.