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
5 changes: 5 additions & 0 deletions .changeset/true-wings-send.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solana/kit-plugin-litesvm': minor
---

Replace the temporary `@loris-sandbox/litesvm-kit` dependency with the official `litesvm` v1 package.
2 changes: 1 addition & 1 deletion packages/kit-plugin-litesvm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@solana/kit": "^6.5.0"
},
"dependencies": {
"@loris-sandbox/litesvm-kit": "^0.5.0"
"litesvm": "^1.0.0"
},
"devDependencies": {
"@solana-program/system": "^0.12.0"
Expand Down
2 changes: 1 addition & 1 deletion packages/kit-plugin-litesvm/src/index.browser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type { FailedTransactionMetadata, TransactionMetadata } from '@loris-sandbox/litesvm-kit';
export type { FailedTransactionMetadata, TransactionMetadata } from 'litesvm';
export type { LiteSVM } from './litesvm';
export type { LiteSvmRpcApi } from './litesvm-to-rpc';

Expand Down
2 changes: 1 addition & 1 deletion packages/kit-plugin-litesvm/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type { FailedTransactionMetadata, TransactionMetadata } from '@loris-sandbox/litesvm-kit';
export type { FailedTransactionMetadata, TransactionMetadata } from 'litesvm';

export * from './airdrop';
export * from './get-minimum-balance';
Expand Down
4 changes: 2 additions & 2 deletions packages/kit-plugin-litesvm/src/litesvm-to-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { LiteSVM } from '@loris-sandbox/litesvm-kit';
import {
AccountInfoBase,
AccountInfoWithBase64EncodedData,
Expand All @@ -25,6 +24,7 @@ import {
signature as toSignature,
SolanaRpcResponse,
} from '@solana/kit';
import type { LiteSVM } from 'litesvm';

import { getSolanaErrorFromLiteSvmFailure, isFailedTransaction } from './transaction-error';

Expand Down Expand Up @@ -70,7 +70,7 @@ type Encoding = 'base58' | 'base64' | 'base64+zstd' | 'jsonParsed';
*
* @example
* ```ts
* import { LiteSVM } from '@loris-sandbox/litesvm-kit';
* import { LiteSVM } from 'litesvm';
* import { createRpcFromSvm } from '@solana/kit-plugin-litesvm';
*
* const svm = new LiteSVM();
Expand Down
4 changes: 2 additions & 2 deletions packages/kit-plugin-litesvm/src/litesvm.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { LiteSVM } from '@loris-sandbox/litesvm-kit';
import { extendClient } from '@solana/kit';
import { LiteSVM } from 'litesvm';

import { createRpcFromSvm } from './litesvm-to-rpc';

// Re-export the LiteSVM type to make the `litesvm` plugin type-portable.
export type { LiteSVM } from '@loris-sandbox/litesvm-kit';
export type { LiteSVM } from 'litesvm';

/**
* A Kit plugin that adds LiteSVM functionality to your client.
Expand Down
6 changes: 3 additions & 3 deletions packages/kit-plugin-litesvm/src/transaction-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getSolanaErrorFromTransactionError } from '@solana/kit';

/**
* A duck-typed interface for `FailedTransactionMetadata` from
* `@loris-sandbox/litesvm-kit`. We avoid importing the class directly
* `litesvm`. We avoid importing the class directly
* because LiteSVM is a Node-only native module and this file must be
* resolvable in browser builds.
*/
Expand All @@ -12,7 +12,7 @@ export type FailedTransactionResult = {

/**
* The ordered names of the `TransactionErrorFieldless` enum variants
* from `@loris-sandbox/litesvm-kit`. Each index corresponds to the
* from `litesvm`. Each index corresponds to the
* enum's numeric value and maps to the string name expected by
* {@link getSolanaErrorFromTransactionError}.
*/
Expand Down Expand Up @@ -56,7 +56,7 @@ const TRANSACTION_ERROR_NAMES: readonly string[] = [

/**
* The ordered names of the `InstructionErrorFieldless` enum variants
* from `@loris-sandbox/litesvm-kit`. Each index corresponds to the
* from `litesvm`. Each index corresponds to the
* enum's numeric value and maps to the string name expected by
* {@link getSolanaErrorFromTransactionError}.
*/
Expand Down
13 changes: 3 additions & 10 deletions packages/kit-plugin-litesvm/src/transaction-plan-executor.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import type { FailedTransactionMetadata, LiteSVM, TransactionMetadata } from '@loris-sandbox/litesvm-kit';
import {
createTransactionPlanExecutor,
extendClient,
pipe,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners,
} from '@solana/kit';
import { createTransactionPlanExecutor, extendClient, pipe, signTransactionMessageWithSigners } from '@solana/kit';
import type { FailedTransactionMetadata, LiteSVM, TransactionMetadata } from 'litesvm';

import { getSolanaErrorFromLiteSvmFailure, isFailedTransaction } from './transaction-error';

Expand Down Expand Up @@ -51,10 +45,9 @@ export function litesvmTransactionPlanExecutor() {
transactionMetadata: FailedTransactionMetadata | TransactionMetadata;
}>({
executeTransactionMessage: async (context, transactionMessage, config) => {
const latestBlockhash = client.svm.latestBlockhashLifetime();
const signedTransaction = await pipe(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice — this is a cleaner API. The old two-step pattern (latestBlockhashLifetime()setTransactionMessageLifetimeUsingBlockhash()) was easy to misuse if the blockhash went stale between the two calls. Having the SVM instance handle it atomically is a good improvement.

transactionMessage,
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
tx => client.svm.setTransactionMessageLifetimeUsingLatestBlockhash(tx),
tx => (context.message = tx),
async tx => await signTransactionMessageWithSigners(tx, config),
);
Expand Down
2 changes: 1 addition & 1 deletion packages/kit-plugin-litesvm/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { LiteSVM } from '@loris-sandbox/litesvm-kit';
import {
createEmptyClient,
GetAccountInfoApi,
Expand All @@ -11,6 +10,7 @@ import {
RequestAirdropApi,
Rpc,
} from '@solana/kit';
import { LiteSVM } from 'litesvm';
import { describe, expect, expectTypeOf, it } from 'vitest';

import { litesvm as nodeLitesvm } from '../src/index';
Expand Down
33 changes: 15 additions & 18 deletions packages/kit-plugin-litesvm/test/transaction-plan-executor.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { FailedTransactionMetadata, LiteSVM, TransactionMetadata } from '@loris-sandbox/litesvm-kit';
import {
Address,
appendTransactionMessageInstruction,
Expand All @@ -19,14 +18,12 @@ import {
SolanaError,
} from '@solana/kit';
import { getTransferSolInstruction } from '@solana-program/system';
import type { FailedTransactionMetadata, LiteSVM, TransactionMetadata } from 'litesvm';
import { describe, expect, it, vi } from 'vitest';

import { litesvm, litesvmTransactionPlanExecutor, litesvmTransactionPlanner } from '../src';

const MOCK_BLOCKHASH = { blockhash: '11111111111111111111111111111111', lastValidBlockHeight: 0n };
const MOCK_INSTRUCTION = {
programAddress: '11111111111111111111111111111111' as Address,
};
const MOCK_INSTRUCTION = { programAddress: '11111111111111111111111111111111' as Address };

describe('litesvmTransactionPlanExecutor', () => {
describe('with mocks', () => {
Expand All @@ -40,10 +37,10 @@ describe('litesvmTransactionPlanExecutor', () => {

it('uses the SVM instance to send transactions', async () => {
const payer = await generateKeyPairSigner();
const latestBlockhashLifetime = vi.fn().mockReturnValueOnce(MOCK_BLOCKHASH);
const setTransactionMessageLifetimeUsingLatestBlockhash = vi.fn().mockImplementation(<T>(m: T) => m);
// Return a success result (no `.err` property).
const sendTransaction = vi.fn().mockReturnValue({ signature: () => new Uint8Array(64) });
const svm = { latestBlockhashLifetime, sendTransaction } as unknown as LiteSVM;
const svm = { sendTransaction, setTransactionMessageLifetimeUsingLatestBlockhash } as unknown as LiteSVM;
const client = createEmptyClient()
.use(() => ({ payer, svm }))
.use(litesvmTransactionPlanner())
Expand All @@ -55,16 +52,16 @@ describe('litesvmTransactionPlanExecutor', () => {
transactionPlan,
)) as SingleTransactionPlanResult;
expect(transactionPlanResult.kind).toBe('single');
expect(latestBlockhashLifetime).toHaveBeenCalledOnce();
expect(setTransactionMessageLifetimeUsingLatestBlockhash).toHaveBeenCalledOnce();
expect(sendTransaction).toHaveBeenCalledOnce();
});

it('includes transactionMetadata in the result context on success', async () => {
const payer = await generateKeyPairSigner();
const latestBlockhashLifetime = vi.fn().mockReturnValueOnce(MOCK_BLOCKHASH);
const setTransactionMessageLifetimeUsingLatestBlockhash = vi.fn().mockImplementation(<T>(m: T) => m);
const mockMetadata = { logs: () => ['log1'], signature: () => new Uint8Array(64) };
const sendTransaction = vi.fn().mockReturnValue(mockMetadata);
const svm = { latestBlockhashLifetime, sendTransaction } as unknown as LiteSVM;
const svm = { sendTransaction, setTransactionMessageLifetimeUsingLatestBlockhash } as unknown as LiteSVM;
const client = createEmptyClient()
.use(() => ({ payer, svm }))
.use(litesvmTransactionPlanner())
Expand All @@ -78,10 +75,10 @@ describe('litesvmTransactionPlanExecutor', () => {

it('includes transactionMetadata in the result context on failure', async () => {
const payer = await generateKeyPairSigner();
const latestBlockhashLifetime = vi.fn().mockReturnValueOnce(MOCK_BLOCKHASH);
const setTransactionMessageLifetimeUsingLatestBlockhash = vi.fn().mockImplementation(<T>(m: T) => m);
const mockMetadata = { err: () => 2 };
const sendTransaction = vi.fn().mockReturnValue(mockMetadata);
const svm = { latestBlockhashLifetime, sendTransaction } as unknown as LiteSVM;
const svm = { sendTransaction, setTransactionMessageLifetimeUsingLatestBlockhash } as unknown as LiteSVM;
const client = createEmptyClient()
.use(() => ({ payer, svm }))
.use(litesvmTransactionPlanExecutor());
Expand All @@ -98,10 +95,10 @@ describe('litesvmTransactionPlanExecutor', () => {

it('throws a SolanaError when the transaction fails', async () => {
const payer = await generateKeyPairSigner();
const latestBlockhashLifetime = vi.fn().mockReturnValueOnce(MOCK_BLOCKHASH);
const setTransactionMessageLifetimeUsingLatestBlockhash = vi.fn().mockImplementation(<T>(m: T) => m);
// Return a failed result with a fieldless error (AccountNotFound = 2).
const sendTransaction = vi.fn().mockReturnValue({ err: () => 2 });
const svm = { latestBlockhashLifetime, sendTransaction } as unknown as LiteSVM;
const svm = { sendTransaction, setTransactionMessageLifetimeUsingLatestBlockhash } as unknown as LiteSVM;
const client = createEmptyClient()
.use(() => ({ payer, svm }))
.use(litesvmTransactionPlanExecutor());
Expand All @@ -124,15 +121,15 @@ describe('litesvmTransactionPlanExecutor', () => {

it('throws a SolanaError for instruction errors', async () => {
const payer = await generateKeyPairSigner();
const latestBlockhashLifetime = vi.fn().mockReturnValueOnce(MOCK_BLOCKHASH);
const setTransactionMessageLifetimeUsingLatestBlockhash = vi.fn().mockImplementation(<T>(m: T) => m);
// Return a failed result with an instruction error.
const instructionError = {
constructor: { name: 'TransactionErrorInstructionError' },
err: () => 2, // InstructionErrorFieldless.InvalidInstructionData
index: 0,
};
const sendTransaction = vi.fn().mockReturnValue({ err: () => instructionError });
const svm = { latestBlockhashLifetime, sendTransaction } as unknown as LiteSVM;
const svm = { sendTransaction, setTransactionMessageLifetimeUsingLatestBlockhash } as unknown as LiteSVM;
const client = createEmptyClient()
.use(() => ({ payer, svm }))
.use(litesvmTransactionPlanExecutor());
Expand Down Expand Up @@ -194,10 +191,10 @@ describe('litesvmTransactionPlanExecutor', () => {
.use(client => extendClient(client, { payer }))
.use(litesvmTransactionPlanner())
.use(litesvmTransactionPlanExecutor());
client.svm.airdrop(payer.address, lamports(1_000_000_000n));
client.svm.airdrop(payer.address, lamports(1_000_000_000n)); // 1 SOL
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bump from 1,000 to 100,000,000 lamports — is this needed because the v1 runtime now enforces minimum rent-exempt balances or fee requirements that the old version didn't? Might be worth a brief comment explaining why this specific amount was chosen.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trevor-cortex That's exactly right. The test transferred 1,000 lamports to a fresh destination address but the minimum balance for a 0-data account is 890,880 lamports. The official litesvm v1 package enforces this whereas my previous temporary package was forked from an older version that was lenient enough to let the test pass.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks for the context! Good that the official v1 enforces rent-exemption properly — better to catch that in tests than have it silently pass.


const instruction = getTransferSolInstruction({
amount: lamports(1_000n),
amount: lamports(100_000_000n), // 0.1 SOL
destination: destination.address,
source: payer,
});
Expand Down
Loading
Loading