Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a4777bb
update build:info
rogaldh Dec 24, 2025
3819bd1
feat: migrate from Buffer to Uint8Array
rogaldh Dec 24, 2025
ab61f5b
recover some Buffer usage for Transactions
rogaldh Dec 24, 2025
1ea1c8d
replace Buffer.from with bytes
rogaldh Dec 24, 2025
16e01d7
fix timemr for test
rogaldh Dec 24, 2025
b7e262f
chore: update eslint and fix lint issues
rogaldh Dec 30, 2025
f08639f
chore: collect build info
rogaldh Dec 30, 2025
05acf67
rm some comments
rogaldh Dec 30, 2025
c29f603
feat: better implementation for from/to base64 and tests
rogaldh Dec 30, 2025
a684f46
improve to/from base64 covnversions
rogaldh Dec 30, 2025
f296364
feat: improve hex coinversions
rogaldh Dec 30, 2025
b03ddde
feat: improve en/de utf-8 strings
rogaldh Dec 30, 2025
bffb0d5
feat: improve HexData
rogaldh Dec 30, 2025
d94700e
feat: improve isValidBase64 check
rogaldh Dec 30, 2025
76b9362
feat: improve base64 test-cases
rogaldh Dec 30, 2025
ae6fd67
feat: improve alloc and add writeUint32LE
rogaldh Dec 30, 2025
f5f3bbf
fix: replace Buffer with Uint8Array where possible
rogaldh Dec 30, 2025
bcb74d8
nit
rogaldh Dec 30, 2025
32e4c82
chore: improve seed-builder test-cases
rogaldh Dec 30, 2025
c5a452a
fix comment
rogaldh Dec 30, 2025
fc2a736
chore: replace Buffer.from with toBuffer
mikhd Mar 27, 2026
43a827c
chore: replace with toBase64
mikhd Mar 27, 2026
2eda2d8
chore: replace with readUint8, readUint16LE, readUint32LE
mikhd Mar 27, 2026
8bd5674
chore: misc Buffer to Uint8Array fixes
mikhd Mar 27, 2026
953c158
chore: update REGRESSION.md
mikhd Mar 27, 2026
165e7a0
chore: bench build
mikhd Mar 27, 2026
5d6ff31
fix: lint
mikhd Mar 27, 2026
396b413
chore: fix after rebase
mikhd Mar 27, 2026
b5f5d25
fix: packages
mikhd Mar 27, 2026
7808505
fix: formatting
mikhd Mar 27, 2026
8184d1e
fix: cleanup, align regression
mikhd Mar 30, 2026
5f3f273
Merge remote-tracking branch 'origin/master' into development-replace…
rogaldh Mar 31, 2026
6dd5b84
chore: collect build info
rogaldh Mar 31, 2026
b7e4c88
fix: codestyle
rogaldh Mar 31, 2026
adc8061
fix: remove regression
rogaldh Mar 31, 2026
afe5b07
fix: address uneven hex with prefixes
rogaldh Mar 31, 2026
6fb5ad4
fix: improve implementations for fromHex and fromCharCode
rogaldh Mar 31, 2026
96e422c
Merge remote-tracking branch 'origin/master' into development-replace…
rogaldh Mar 31, 2026
a3b1135
chore: collect build info
rogaldh Mar 31, 2026
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
5 changes: 3 additions & 2 deletions app/components/ProgramLogsCardBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { usePathname } from 'next/navigation';
import React from 'react';
import { ChevronsUp } from 'react-feather';

import { fromBase64, toBuffer } from '@/app/shared/lib/bytes';
import { Logger } from '@/app/shared/lib/logger';

const NATIVE_PROGRAMS_MISSING_INVOKE_LOG: string[] = [
Expand Down Expand Up @@ -195,7 +196,7 @@ function ProgramLogRow({
});

txInstruction = new TransactionInstruction({
data: Buffer.from(instruction.data),
data: toBuffer(instruction.data),
keys: accounts,
programId,
});
Expand All @@ -211,7 +212,7 @@ function ProgramLogRow({
});

txInstruction = new TransactionInstruction({
data: Buffer.from(instruction.data, 'base64'),
data: toBuffer(fromBase64(instruction.data)),
keys,
programId,
});
Expand Down
10 changes: 1 addition & 9 deletions app/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ActionMeta, components, ControlProps, InputActionMeta, SelectInstance }
import AsyncSelect from 'react-select/async';
import { is } from 'superstruct';

import { isValidBase64 } from '@/app/shared/lib/bytes';
import { Logger } from '@/app/shared/lib/logger';
import FEATURES from '@/app/utils/feature-gate/featureGates.json';

Expand Down Expand Up @@ -474,15 +475,6 @@ function decodeTransactionFromBase64(base64String: string): {
}
}

function isValidBase64(str: string): boolean {
try {
Buffer.from(str, 'base64');
return true;
} catch (_err) {
return false;
}
}

function KeyIndicator() {
return <div className="key-indicator">/</div>;
}
Expand Down
5 changes: 3 additions & 2 deletions app/components/account/AnchorAccountCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useCluster } from '@providers/cluster';
import { getAnchorProgramName, mapAccountToRows } from '@utils/anchor';
import React, { useMemo } from 'react';

import { equals, toBuffer } from '@/app/shared/lib/bytes';
import { Logger } from '@/app/shared/lib/logger';

export function AnchorAccountCard({ account }: { account: Account }) {
Expand All @@ -22,12 +23,12 @@ export function AnchorAccountCard({ account }: { account: Account }) {
if (anchorProgram && rawData) {
const coder = new BorshAccountsCoder(anchorProgram.idl);
const account = anchorProgram.idl.accounts?.find((accountType: any) =>
(rawData as Buffer).slice(0, 8).equals(coder.accountDiscriminator(accountType.name)),
equals(rawData.slice(0, 8), coder.accountDiscriminator(accountType.name)),
);
if (account) {
accountDef = anchorProgram.idl.types?.find((type: any) => type.name === account.name);
try {
decodedAccountData = coder.decode(account.name, rawData);
decodedAccountData = coder.decode(account.name, toBuffer(rawData));
} catch (err) {
Logger.debug('[components:anchor-account] Failed to decode account data', { error: err });
}
Expand Down
3 changes: 2 additions & 1 deletion app/components/account/CompressedNFTInfoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useCompressedNft,
useCompressedNftProof,
} from '@/app/providers/compressed-nft';
import { toBuffer } from '@/app/shared/lib/bytes';

import { Address } from '../common/Address';
import { TableCardBody } from '../common/TableCardBody';
Expand Down Expand Up @@ -45,7 +46,7 @@ function DasCompressionInfoCard({ proof, compressedNft }: { proof: CompressedNft
});
const canopyDepth =
treeAccountInfo && treeAccountInfo.data && treeAccountInfo.data.data.raw
? ConcurrentMerkleTreeAccount.fromBuffer(treeAccountInfo.data.data.raw).getCanopyDepth()
? ConcurrentMerkleTreeAccount.fromBuffer(toBuffer(treeAccountInfo.data.data.raw)).getCanopyDepth()
: 0;
const proofSize = proof.proof.length - canopyDepth;
return (
Expand Down
6 changes: 4 additions & 2 deletions app/components/account/ConcurrentMerkleTreeCard.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { ConcurrentMerkleTreeAccount } from '@solana/spl-account-compression';
import { PublicKey } from '@solana/web3.js';

import { toBuffer } from '@/app/shared/lib/bytes';

import { Address } from '../common/Address';
import { Slot } from '../common/Slot';
import { TableCardBody } from '../common/TableCardBody';

export function ConcurrentMerkleTreeCard({ data }: { data: Buffer }) {
const cmt = ConcurrentMerkleTreeAccount.fromBuffer(Buffer.from(data));
export function ConcurrentMerkleTreeCard({ data }: { data: Uint8Array }) {
const cmt = ConcurrentMerkleTreeAccount.fromBuffer(toBuffer(data));
const authority = cmt.getAuthority();
const root = cmt.getCurrentRoot();
const seq = cmt.getCurrentSeq();
Expand Down
5 changes: 3 additions & 2 deletions app/components/account/nftoken/isNFTokenAccount.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PublicKey } from '@solana/web3.js';

import { toBase64 } from '@/app/shared/lib/bytes';
import { Logger } from '@/app/shared/lib/logger';

import { Account } from '../../../providers/accounts';
Expand All @@ -24,7 +25,7 @@ export const parseNFTokenNFTAccount = (account: Account): NftokenTypes.NftAccoun
return null;
}

if (Buffer.from(parsed!.discriminator).toString('base64') !== nftokenAccountDisc) {
if (toBase64(new Uint8Array(parsed!.discriminator)) !== nftokenAccountDisc) {
return null;
}

Expand Down Expand Up @@ -58,7 +59,7 @@ export const parseNFTokenCollectionAccount = (account: Account): NftokenTypes.Co
if (!parsed) {
return null;
}
if (Buffer.from(parsed.discriminator).toString('base64') !== collectionAccountDisc) {
if (toBase64(new Uint8Array(parsed.discriminator)) !== collectionAccountDisc) {
return null;
}

Expand Down
4 changes: 3 additions & 1 deletion app/components/account/nftoken/nftoken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import axios from 'axios';
import bs58 from 'bs58';
import pLimit from 'p-limit';

import { fromHex } from '@/app/shared/lib/bytes';

import { NftokenTypes } from './nftoken-types';

export const NFTOKEN_ADDRESS = 'nftokf9qcHSYkVSP3P2gUMmV6d4AwjMueXgUu43HyLL';
Expand All @@ -23,7 +25,7 @@ export namespace NftokenFetcher {
filters: [
{
memcmp: {
bytes: bs58.encode(Buffer.from(nftokenAccountDiscInHex, 'hex')),
bytes: bs58.encode(fromHex(nftokenAccountDiscInHex)),
offset: 0,
},
},
Expand Down
3 changes: 2 additions & 1 deletion app/components/account/sas/AttestationDataCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from 'sas-lib';

import { SolarizedJsonViewer as ReactJson } from '@/app/components/common/JsonViewer';
import { toBase64 } from '@/app/shared/lib/bytes';
import { Logger } from '@/app/shared/lib/logger';
import {
decodeAccount,
Expand Down Expand Up @@ -97,7 +98,7 @@ function AttestationCard({ attestation }: { attestation: SasAttestation }) {
wordBreak: 'break-all',
}}
>
{Buffer.from(attestation.data).toString('base64') || '(empty)'}
{toBase64(new Uint8Array(attestation.data)) || '(empty)'}
</div>
)}
</div>
Expand Down
57 changes: 57 additions & 0 deletions app/components/common/__tests__/HexData.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* eslint-disable no-restricted-syntax -- test assertions use RegExp for pattern matching */
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';

import { HexData } from '../HexData';

describe('HexData', () => {
it('should render hex string from Uint8Array', () => {
const data = new Uint8Array([0x1d, 0x9a, 0xcb, 0x51]);
render(<HexData raw={data} />);
// HexData renders twice (desktop and mobile views), use getAllByText
const elements = screen.getAllByText(/1d 9a cb 51/);
expect(elements.length).toBeGreaterThan(0);
});

it('should handle empty Uint8Array', () => {
const data = new Uint8Array([]);
render(<HexData raw={data} />);
expect(screen.getByText('No data')).toBeInTheDocument();
});

it('should handle null-like input', () => {
render(<HexData raw={null as any} />);
expect(screen.getByText('No data')).toBeInTheDocument();
});

it('should render multiple rows for larger data', () => {
// 16 bytes = 1 full row (4 spans of 4 bytes each)
const data = new Uint8Array([
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
]);
render(<HexData raw={data} />);
// Component renders twice (desktop/mobile), use getAllByText
expect(screen.getAllByText(/00 01 02 03/).length).toBeGreaterThan(0);
expect(screen.getAllByText(/04 05 06 07/).length).toBeGreaterThan(0);
});

it('should handle various byte values including edge cases', () => {
const data = new Uint8Array([0x00, 0xff, 0x7f, 0x80]);
render(<HexData raw={data} />);
expect(screen.getAllByText(/00 ff 7f 80/).length).toBeGreaterThan(0);
});

it('should render hex from Buffer (Uint8Array subclass)', () => {
// Buffer extends Uint8Array, so this should work
const buffer = Buffer.from([0xde, 0xad, 0xbe, 0xef]);
render(<HexData raw={buffer} />);
expect(screen.getAllByText(/de ad be ef/).length).toBeGreaterThan(0);
});

it('should have copyable text matching hex string', () => {
const data = new Uint8Array([0xab, 0xcd]);
render(<HexData raw={data} />);
// The Copyable component should have the full hex string
expect(screen.getAllByText(/ab cd/).length).toBeGreaterThan(0);
});
});
5 changes: 3 additions & 2 deletions app/components/inspector/InspectorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import useSWR from 'swr';

import { useCluster } from '@/app/providers/cluster';
import { DownloadDropdown } from '@/app/shared/components/DownloadDropdown';
import { toBase64 } from '@/app/shared/lib/bytes';

import { AccountsCard } from './AccountsCard';
import { AddressTableLookupsCard } from './AddressTableLookupsCard';
Expand Down Expand Up @@ -224,7 +225,7 @@ function SquadsProposalInspectorCard({ account, onClear }: { account: string; on
})),
compiledInstructions: message.instructions.map(instruction => ({
accountKeyIndexes: Array.from(instruction.accountIndexes),
data: Buffer.from(instruction.data),
data: instruction.data,
programIdIndex: instruction.programIdIndex,
})),
header: {
Expand Down Expand Up @@ -297,7 +298,7 @@ export function TransactionInspectorPage({
}
}

const base64 = btoa(String.fromCharCode.apply(null, Array.from(inspectorData.rawMessage)));
const base64 = toBase64(inspectorData.rawMessage);
const newParam = encodeURIComponent(base64);
if (currentSearchParams.get('message') !== newParam) {
nextQueryParams ||= new URLSearchParams(currentSearchParams?.toString());
Expand Down
4 changes: 4 additions & 0 deletions app/components/inspector/__tests__/InspectorPage.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ vi.mock('next/navigation', () => ({

describe('TransactionInspectorPage with Squads Transaction', () => {
beforeEach(async () => {
vi.useFakeTimers({ shouldAdvanceTime: true });

const params = new URLSearchParams();
params.set('squadsTx', 'ASwDJP5mzxV1dfov2eQz5WAVEy833nwK17VLcjsrZsZf');

Expand All @@ -49,6 +51,8 @@ describe('TransactionInspectorPage with Squads Transaction', () => {
});

afterEach(() => {
vi.runOnlyPendingTimers();
vi.useRealTimers();
vi.clearAllMocks();
});

Expand Down
15 changes: 6 additions & 9 deletions app/components/inspector/into-parsed-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ import {
} from '@solana-program/token';
import { TOKEN_2022_PROGRAM_ADDRESS } from '@solana-program/token-2022';

import { alloc, bytes, equals, toBuffer } from '@/app/shared/lib/bytes';

import { parseTokenProgramInstruction } from './instruction-parsers/spl-token.parser';
import { parseSystemProgramInstruction } from './instruction-parsers/system-program.parser';
import { parseToken2022Instruction } from './instruction-parsers/token-2022-program.parser';
Expand Down Expand Up @@ -175,8 +177,8 @@ function convertUpgradeNonceInfo(parsed: any): UpgradeNonceInfo {
};
}

function discriminatorToBuffer(discrimnator: number): Buffer {
return Buffer.from(Uint8Array.from([discrimnator]));
function discriminatorToBytes(discrimnator: number): Uint8Array {
return bytes([discrimnator]);
}

function intoProgramName(programId: PublicKey): string | undefined {
Expand All @@ -192,11 +194,6 @@ function intoProgramName(programId: PublicKey): string | undefined {
/* add other variants here */
}

function isDataEqual(data1: Buffer, data2: Buffer): boolean {
// Browser will fail if data2 is created with Uint8Array.from
return data1.equals(data2);
}

function intoParsedData(instruction: TransactionInstruction, parsed?: any): any {
const { programId, data } = instruction;
const UNKNOWN_PROGRAM_TYPE = ''; // empty string represents that the program is unknown
Expand All @@ -207,8 +204,8 @@ function intoParsedData(instruction: TransactionInstruction, parsed?: any): any
let instructionData = data;

// workaround for "create" instructions
if (isDataEqual(data, Buffer.alloc(CREATE_ASSOCIATED_TOKEN_DISCRIMINATOR))) {
instructionData = discriminatorToBuffer(CREATE_ASSOCIATED_TOKEN_DISCRIMINATOR);
if (equals(data, alloc(CREATE_ASSOCIATED_TOKEN_DISCRIMINATOR))) {
instructionData = toBuffer(discriminatorToBytes(CREATE_ASSOCIATED_TOKEN_DISCRIMINATOR));
instruction.data = instructionData; // overwrite original data with the modified one
}

Expand Down
4 changes: 3 additions & 1 deletion app/components/inspector/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
VersionedMessage,
} from '@solana/web3.js';

import { toBuffer } from '@/app/shared/lib/bytes';

type LookupsForAccountKeyIndex = { lookupTableIndex: number; lookupTableKey: PublicKey };

function findLookupAddressByIndex(
Expand Down Expand Up @@ -111,7 +113,7 @@ export function intoTransactionInstructionFromVersionedMessage(
const accountMetas = fillAccountMetas(accountKeyIndexes, originalMessage, lookupAccounts);

const transactionInstruction: TransactionInstruction = new TransactionInstruction({
data: Buffer.from(data),
data: toBuffer(data),
keys: accountMetas,
programId: programId,
});
Expand Down
7 changes: 5 additions & 2 deletions app/components/instruction/AnchorDetailsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { extractEventsFromLogs } from '@utils/program-logs';
import { useMemo, useState } from 'react';
import { ChevronDown, ChevronUp, CornerDownRight } from 'react-feather';

import { toBase64 } from '@/app/shared/lib/bytes';

import { InstructionCard } from './InstructionCard';
import { ProgramEventsCard } from './ProgramEventsCard';

Expand Down Expand Up @@ -74,14 +76,15 @@ function AnchorDetails({ ix, anchorProgram }: { ix: TransactionInstruction; anch
let ixDef: IdlInstruction | undefined;
if (anchorProgram) {
let coder: BorshInstructionCoder | BorshEventCoder;
const encodedInstructionData = toBase64(ix.data.slice(8));
if (instructionIsSelfCPI(ix.data)) {
// Try custom discriminator decoder first (handles variable-length discriminators)
decodedIxData = decodeEventWithCustomDiscriminator(ix.data.slice(8).toString('base64'), anchorProgram);
decodedIxData = decodeEventWithCustomDiscriminator(encodedInstructionData, anchorProgram);

// Fallback to standard Anchor event decoder
if (!decodedIxData) {
coder = new BorshEventCoder(anchorProgram.idl);
decodedIxData = coder.decode(ix.data.slice(8).toString('base64'));
decodedIxData = coder.decode(encodedInstructionData);
}
const ixEventDef = anchorProgram.idl.events?.find(
ixDef => ixDef.name === decodedIxData?.name,
Expand Down
4 changes: 2 additions & 2 deletions app/components/instruction/ProgramEventsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { IdlField, IdlTypeDefTyStruct } from '@coral-xyz/anchor/dist/cjs/idl';
import { cn } from '@shared/utils';
import { decodeEventFromLog, mapIxArgsToRows } from '@utils/anchor';
import { camelToTitleCase } from '@utils/index';
import { Buffer } from 'buffer';
import React, { useState } from 'react';
import { Code } from 'react-feather';

import { fromBase64 } from '@/app/shared/lib/bytes';
import { Logger } from '@/app/shared/lib/logger';

export function ProgramEventsCard({
Expand Down Expand Up @@ -96,7 +96,7 @@ function EventCard({
Event Data <span className="text-muted">(Hex)</span>
</td>
<td className="text-lg-end">
<HexData raw={Buffer.from(rawEventData, 'base64')} />
<HexData raw={fromBase64(rawEventData)} />
</td>
</tr>
</>
Expand Down
Loading
Loading