Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions bench/BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
|------|-------|------|---------------|
| Static | `/` | 140 kB | 570 kB |
| Static | `/_not-found` | 0 B | 450 kB |
| Dynamic | `/address/[address]` | 470 kB | 890 kB |
| Dynamic | `/address/[address]` | 450 kB | 910 kB |
| Dynamic | `/address/[address]/account-data` | 460 kB | 900 kB |
| Dynamic | `/address/[address]/anchor-account` | 420 kB | 870 kB |
| Dynamic | `/address/[address]/attestation` | 420 kB | 870 kB |
Expand Down Expand Up @@ -50,10 +50,10 @@
| Dynamic | `/api/verification/coingecko/[address]` | — | — |
| Dynamic | `/api/verification/jupiter/[mintAddress]` | — | — |
| Dynamic | `/api/verification/rugcheck/[mintAddress]` | — | — |
| Dynamic | `/block/[slot]` | 170 kB | 590 kB |
| Dynamic | `/block/[slot]` | 170 kB | 610 kB |
| Dynamic | `/block/[slot]/accounts` | 150 kB | 590 kB |
| Dynamic | `/block/[slot]/programs` | 140 kB | 590 kB |
| Dynamic | `/block/[slot]/rewards` | 160 kB | 580 kB |
| Dynamic | `/block/[slot]/rewards` | 160 kB | 600 kB |
| Dynamic | `/epoch/[epoch]` | 20 kB | 460 kB |
| Static | `/feature-gates` | 50 kB | 490 kB |
| Dynamic | `/mcp` | — | — |
Expand All @@ -64,6 +64,6 @@
| Static | `/opengraph-image.png` | — | — |
| Dynamic | `/robots.txt` | — | — |
| Static | `/tos` | 10 kB | 460 kB |
| Dynamic | `/tx/[signature]` | 470 kB | 900 kB |
| Dynamic | `/tx/[signature]/inspect` | 450 kB | 890 kB |
| Static | `/tx/inspector` | 450 kB | 890 kB |
| Dynamic | `/tx/[signature]` | 470 kB | 920 kB |
| Dynamic | `/tx/[signature]/inspect` | 450 kB | 910 kB |
| Static | `/tx/inspector` | 450 kB | 910 kB |
4 changes: 2 additions & 2 deletions packages/decoder-mango/src/detection.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getU32Decoder } from '@solana/kit';
import { type TransactionInstruction } from '@solana/web3.js';

import { MANGO_INSTRUCTION_NAMES } from './instruction-names';
Expand All @@ -9,8 +10,7 @@ export const isMangoInstruction = (instruction: TransactionInstruction): boolean
programIds.has(instruction.programId.toBase58());

// Mango keys its instructions by a leading u32 LE discriminator.
const readDiscriminator = (data: Uint8Array): number =>
new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(0, true);
const readDiscriminator = (data: Uint8Array): number => getU32Decoder().decode(data);

// Name-only resolver for the NAME_SOURCES chain: program-id-gated and never throws, so it composes with other resolvers.
export const resolveMangoInstructionName = (programId: string, discriminator: Uint8Array): string | undefined => {
Expand Down
3 changes: 0 additions & 3 deletions packages/decoder-serum/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,9 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@project-serum/serum": "0.13.61",
"bn.js": "5.2.1",
"superstruct": "0.15.3"
},
"devDependencies": {
"@types/bn.js": "5.1.0",
"typescript": "catalog:tooling",
"vitest": "catalog:vitest"
},
Expand Down
4 changes: 3 additions & 1 deletion packages/decoder-serum/src/__tests__/decoder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ describe('decodeSerumInstruction', () => {
});

it('should throw on data the instruction layout cannot decode', () => {
expect(() => decodeSerumInstruction(makeInstruction(makeRawInstructionData(13), programId))).toThrow(TypeError);
expect(() => decodeSerumInstruction(makeInstruction(makeRawInstructionData(13), programId))).toThrow(
'Unsupported Serum instruction code: 13',
);
});
});

Expand Down
86 changes: 26 additions & 60 deletions packages/decoder-serum/src/__tests__/fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
// encodeInstruction isn't re-exported from the package root; reach into lib/instructions.js for authentic fixtures.
import { encodeInstruction } from '@project-serum/serum/lib/instructions.js';
import { Keypair, PublicKey, TransactionInstruction } from '@solana/web3.js';
import BN from 'bn.js';

import {
OPEN_BOOK_PROGRAM_IDS,
Expand All @@ -19,64 +16,33 @@ export const SERUM_PROGRAM_IDS_BY_NAME = {
openBook: new PublicKey(OPEN_BOOK_PROGRAM_IDS.mainnet),
} as const;

// Variants 7/8/9/13 are absent from the library's INSTRUCTION_LAYOUT — use makeRawInstructionData for those.
// Bytes produced by @project-serum/serum 0.13.61 `encodeInstruction`, frozen so the vendored layout is checked
// against the original encoder rather than against itself. Variants 7/8/9/13 have no layout — use
// makeRawInstructionData for those.
export const ENCODED_INSTRUCTIONS = {
cancelOrder: encodeInstruction({
cancelOrder: {
openOrders: PublicKey.default,
openOrdersSlot: 3,
orderId: new BN('170141183460469231731687303715884105727'),
side: 'sell',
},
}) as Buffer,
cancelOrderByClientId: encodeInstruction({
cancelOrderByClientId: { clientId: new BN(99) },
}) as Buffer,
cancelOrderByClientIdV2: encodeInstruction({
cancelOrderByClientIdV2: { clientId: new BN(123) },
}) as Buffer,
cancelOrderV2: encodeInstruction({
cancelOrderV2: { orderId: new BN('170141183460469231731687303715884105727'), side: 'buy' },
}) as Buffer,
closeOpenOrders: encodeInstruction({ closeOpenOrders: {} }) as Buffer,
consumeEvents: encodeInstruction({ consumeEvents: { limit: 16 } }) as Buffer,
consumeEventsPermissioned: encodeInstruction({
consumeEventsPermissioned: { limit: 32 },
}) as Buffer,
initOpenOrders: encodeInstruction({ initOpenOrders: {} }) as Buffer,
initializeMarket: encodeInstruction({
initializeMarket: {
baseLotSize: new BN(1_000),
feeRateBps: 25,
quoteDustThreshold: new BN(100),
quoteLotSize: new BN(10),
vaultSignerNonce: new BN(0),
},
}) as Buffer,
matchOrders: encodeInstruction({ matchOrders: { limit: 5 } }) as Buffer,
newOrder: encodeInstruction({
newOrder: {
clientId: new BN(7),
limitPrice: new BN(50_000),
maxQuantity: new BN(10),
orderType: 'limit',
side: 'buy',
},
}) as Buffer,
newOrderV3: encodeInstruction({
newOrderV3: {
clientId: new BN(42),
limit: 65_535,
limitPrice: new BN(60_000),
maxBaseQuantity: new BN(5),
maxQuoteQuantity: new BN(300_000),
orderType: 'postOnly',
selfTradeBehavior: 'decrementTake',
side: 'sell',
},
}) as Buffer,
prune: encodeInstruction({ prune: { limit: 25 } }) as Buffer,
settleFunds: encodeInstruction({ settleFunds: {} }) as Buffer,
cancelOrder: Buffer.from(
'000400000001000000ffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000003',
'hex',
),
cancelOrderByClientId: Buffer.from('00060000006300000000000000', 'hex'),
cancelOrderByClientIdV2: Buffer.from('000c0000007b00000000000000', 'hex'),
cancelOrderV2: Buffer.from('000b00000000000000ffffffffffffffffffffffffffffff7f', 'hex'),
closeOpenOrders: Buffer.from('000e000000', 'hex'),
consumeEvents: Buffer.from('00030000001000', 'hex'),
consumeEventsPermissioned: Buffer.from('00110000002000', 'hex'),
initOpenOrders: Buffer.from('000f000000', 'hex'),
initializeMarket: Buffer.from(
'0000000000e8030000000000000a00000000000000190000000000000000006400000000000000',
'hex',
),
matchOrders: Buffer.from('00020000000500', 'hex'),
newOrder: Buffer.from('00010000000000000050c30000000000000a00000000000000000000000700000000000000', 'hex'),
newOrderV3: Buffer.from(
'000a0000000100000060ea0000000000000500000000000000e09304000000000000000000020000002a00000000000000ffff',
'hex',
),
prune: Buffer.from('00100000001900', 'hex'),
settleFunds: Buffer.from('0005000000', 'hex'),
} as const;

// Version byte 0 + u32 LE code only — for the variants encodeInstruction can't build (7/8/9/13).
Expand Down
12 changes: 0 additions & 12 deletions packages/decoder-serum/src/__tests__/program-ids.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// MARKETS is imported here only to pin the hardcoded ID list; runtime detection must stay free of @project-serum/serum.
import { MARKETS } from '@project-serum/serum';
import { describe, expect, it } from 'vitest';

import {
Expand Down Expand Up @@ -41,16 +39,6 @@ describe('serum program ids', () => {
it('should include OpenBook in the full program id list', () => {
expect(SERUM_PROGRAM_IDS).toEqual([...DEPRECATED_SERUM_PROGRAM_IDS, OPEN_BOOK_PROGRAM_IDS.mainnet]);
});

it('should cover every program id referenced by the MARKETS registry', () => {
const marketProgramIds = new Set(
MARKETS.flatMap(market => (market.programId ? [market.programId.toBase58()] : [])),
);
expect(marketProgramIds.size).toBeGreaterThan(0);
for (const programId of marketProgramIds) {
expect(DEPRECATED_SERUM_PROGRAM_IDS).toContain(programId);
}
});
});

describe('serum program labels', () => {
Expand Down
39 changes: 19 additions & 20 deletions packages/decoder-serum/src/decoder.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/* eslint-disable sort-keys-fix/sort-keys-fix -- `accounts` literal order drives UI row order (Object.entries); data schemas are kept in wire order only to mirror the layout */
import { decodeInstruction } from '@project-serum/serum';
import { type AccountMeta, type PublicKey, type TransactionInstruction } from '@solana/web3.js';
import { enums, type Infer, mask, number, object } from 'superstruct';
import { bigint, enums, type Infer, mask, number, object } from 'superstruct';

import { BigIntFromString } from './superstructHelpers';
import { decodeInstruction } from './instruction-layout';

export type Side = Infer<typeof Side>;
export const Side = enums(['buy', 'sell']);
Expand Down Expand Up @@ -42,11 +41,11 @@ export type InitializeMarket = {
};

export const InitializeMarketInstruction = object({
baseLotSize: BigIntFromString,
quoteLotSize: BigIntFromString,
baseLotSize: bigint(),
quoteLotSize: bigint(),
feeRateBps: number(),
vaultSignerNonce: BigIntFromString,
quoteDustThreshold: BigIntFromString,
vaultSignerNonce: bigint(),
quoteDustThreshold: bigint(),
});

export function decodeInitializeMarket(ix: TransactionInstruction): InitializeMarket {
Expand Down Expand Up @@ -87,10 +86,10 @@ export type NewOrder = {

export const NewOrderInstruction = object({
side: Side,
limitPrice: BigIntFromString,
maxQuantity: BigIntFromString,
limitPrice: bigint(),
maxQuantity: bigint(),
orderType: OrderType,
clientId: BigIntFromString,
clientId: bigint(),
});

export function decodeNewOrder(ix: TransactionInstruction): NewOrder {
Expand Down Expand Up @@ -179,7 +178,7 @@ export type CancelOrder = {

export const CancelOrderInstruction = object({
side: Side,
orderId: BigIntFromString,
orderId: bigint(),
openOrdersSlot: number(),
});

Expand Down Expand Up @@ -240,7 +239,7 @@ export type CancelOrderByClientId = {
};

export const CancelOrderByClientIdInstruction = object({
clientId: BigIntFromString,
clientId: bigint(),
});

export function decodeCancelOrderByClientId(ix: TransactionInstruction): CancelOrderByClientId {
Expand Down Expand Up @@ -276,12 +275,12 @@ export type NewOrderV3 = {

export const NewOrderV3Instruction = object({
side: Side,
limitPrice: BigIntFromString,
maxBaseQuantity: BigIntFromString,
maxQuoteQuantity: BigIntFromString,
limitPrice: bigint(),
maxBaseQuantity: bigint(),
maxQuoteQuantity: bigint(),
selfTradeBehavior: SelfTradeBehavior,
orderType: OrderType,
clientId: BigIntFromString,
clientId: bigint(),
limit: number(),
});

Expand Down Expand Up @@ -320,7 +319,7 @@ export type CancelOrderV2 = {

export const CancelOrderV2Instruction = object({
side: Side,
orderId: BigIntFromString,
orderId: bigint(),
});

export function decodeCancelOrderV2(ix: TransactionInstruction): CancelOrderV2 {
Expand Down Expand Up @@ -352,7 +351,7 @@ export type CancelOrderByClientIdV2 = {
};

export const CancelOrderByClientIdV2Instruction = object({
clientId: BigIntFromString,
clientId: bigint(),
});

export function decodeCancelOrderByClientIdV2(ix: TransactionInstruction): CancelOrderByClientIdV2 {
Expand Down Expand Up @@ -487,8 +486,8 @@ export function parseSerumInstructionKey(instruction: TransactionInstruction): s
return keys[0];
}

// Codes 7/8/9/13 (disableMarket, sweepFees, newOrderV2, sendTake) are absent from the serum lib's
// INSTRUCTION_LAYOUT, so parseSerumInstructionKey throws for them and they can never be dispatched —
// Codes 7/8/9/13 (disableMarket, sweepFees, newOrderV2, sendTake) have no vendored layout,
// so parseSerumInstructionKey throws for them and they can never be dispatched —
// the card renders them as raw data with a name-table title.
export type DecodedSerumInstruction =
| { key: 'cancelOrder'; info: CancelOrder }
Expand Down
2 changes: 1 addition & 1 deletion packages/decoder-serum/src/detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const readInstructionCode = (data: Uint8Array): number =>
export const parseSerumInstructionCode = (instruction: TransactionInstruction): number =>
readInstructionCode(instruction.data);

// Codes 7/8/9/13 (disableMarket, sweepFees, newOrderV2, sendTake) are absent from the serum lib's INSTRUCTION_LAYOUT, so decodeSerumInstruction always throws for them.
// Codes 7/8/9/13 (disableMarket, sweepFees, newOrderV2, sendTake) have no vendored layout, so decodeSerumInstruction always throws for them.
const UNDECODABLE_SERUM_CODES = new Set([7, 8, 9, 13]);

export const isKnownUndecodableSerumInstruction = (instruction: TransactionInstruction): boolean =>
Expand Down
Loading
Loading