Skip to content

Conversation

@lorisleiva
Copy link

@lorisleiva lorisleiva commented Dec 4, 2025

This PR updates the NodeJS package to make it compatible with @solana/kit instead of @solana/web3.js. Currently, it replaces the existing package but we could envisage a separate directory/package for this — although CI would become more complex as a result.

Aside from making the LiteSVM class Kit compatible, it also takes that opportunity to make its API closer to the Kit consumers. Namely it makes the following changes:

  • It leverages the EncodedAccount and MaybeEncodedAccount types from Kit. The previous API was exposing types like [PublicKey, Account] to provide both the address of the content of an account. Kit already contains an Account<T> type for that purpose where T is the data type and type EncodedAccount = Account<Uint8Array>. A MaybeEncodedAccount represents a fetched account that may or may not exists but always contains the address.
    • SimulatedTransactionInfo.postAccounts now returns an array of EncodedAccount instead of [PublicKey, Account].
    • LiteSVM.getAccount now returns a MaybeEncodedAccount instead of AccountInfoBytes | null.
    • LiteSVM.setAccount now accepts an account: EncodedAccount instead of the following two arguments: address: PublicKey, account: AccountInfoBytes.
  • It accepts Signature instead of SignatureBytes. The Kit library usually returns Signature strings instead of their byte equivalent. Using the former instead of the latter avoids Kit consumers having to do the encoding themselves.
    • LiteSVM.getTransaction now accepts a Signature string instead of a Uint8Array.
  • It adds a new latestBlockhashLifetime method to the LiteSVM class. This method returns a blockhash with a lastValidBlockHeight attribute. This is mainly because the setTransactionMessageWithBlockhashLifetime function of Kit expect this exact value, making it easier for consumer to set their blockhash. Note that since LiteSVM doesn't concern itself with blocks (and because tests are passing that way) I assumed it would be okay to always set lastValidBlockHeight to 0n. Please let me know if this assumption is wrong. ⚠️
  • It returns the LiteSVM instance whenever a return value is not expected. This allows users to chain their setters.
    • The following functions now return this on the LiteSVM class: setAccount, addProgramFromFile, addProgram, expireBlockhash, warpToSlot, setClock, setEpochRewards, setEpochSchedule, setLastRestartSlot, setRent, setSlotHashes, setSlotHistory and setStakeHistory.
  • It adds a new tap method to the LiteSVM class. This function accepts a function that may mutate the LiteSVM instance in order to chain helpers more conveniently. For instance, in the tests, I was able to create several helpers that I could use in the following way:
      const svm = new LiteSVM()
        .tap(setComputeUnitLimit(10n))
        .tap((svm) => svm.airdrop(payer.address, lamports(LAMPORTS_PER_SOL)))
        .tap(setHelloWorldProgram(programAddress))
        .tap(setHelloWorldAccount(greetedAddress, programAddress));
  • It replaces core types.
    • PublicKey is now Address.
    • bigint is now Lamports when appropriate.
    • string is now Blockhash when appropriate.

In my fork, I managed to publish a version of this library under @loris-sandbox/litesvm-kit (version 0.5.0) so you can play with it and let me know if you're happy with it. Here's a repo with a simple test.

P.S.: I am aware that PR #237 aims to do the same but the changes were not leveraging an API that would be more idiomatic to the Kit library.

Fixes #233

@kevinheavey
Copy link
Collaborator

It returns the LiteSVM instance whenever a return value is not expected

Doesn't belong in this PR, please remove

It adds a new tap method to the LiteSVM class

Also doesn't belong in this PR, please remove

It adds a new latestBlockhashLifetime method to the LiteSVM class

This is a confusing thing to have in the API given that it doesn't even get included in the serialized transaction. Is there no way to build transactions without the lifetime?

Also, is there no way to build transactions synchronously? I don't like how the tests now require async

@kevinheavey
Copy link
Collaborator

It leverages the EncodedAccount and MaybeEncodedAccount types from Kit

This is a divergence from the Rust API that I would prefer to avoid. Does kit have a type that's like solana_account::Account? As in one that doesn't have a field for the account address:

pub struct Account {
    /// lamports in the account
    pub lamports: u64,
    /// data held in this account
    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
    pub data: Vec<u8>,
    /// the program that owns this account. If executable, the program that loads this account.
    pub owner: Pubkey,
    /// this account's data contains a loaded program (and is now read-only)
    pub executable: bool,
    /// the epoch at which this account will next owe rent
    pub rent_epoch: Epoch,
}

@lorisleiva
Copy link
Author

Hey @kevinheavey, thanks for the review!

Doesn't belong in this PR, please remove
Also doesn't belong in this PR, please remove

It would be nice to make the return this part consistent because currently, some of the methods are fluent when other are not which is confusing.

But sure, would you like me to PR them separately afterwards? Is the plan to merge this and replace the @solana/web3.js version first? Alternatively, would it not make sense to only publish the "internal.js" part which would make it easier to spawn wrappers around it without bothering this repo?

This is a confusing thing to have in the API given that it doesn't even get included in the serialized transaction. Is there no way to build transactions without the lifetime?

The TransactionMessageWithBlockhashLifetime type requires a last valid block height in Kit. Without this function, consumers would have to manually set this to whatever. Alternatively, we could offer a LiteSVM.setTransactionMessageWithLatestBlockhashLifetime function that runs setTransactionMessageWithBlockhashLifetime under the hood.

Also, is there no way to build transactions synchronously? I don't like how the tests now require async

Kit uses the native Crypto API from JavaScript instead of relying on third party libraries for cryptographic operations which means generating keypairs, signing, verifying, etc. are all asynchronous operations.


Let me know how you want to proceed and I'll update/close accordingly.

@lorisleiva
Copy link
Author

This is a divergence from the Rust API that I would prefer to avoid. Does kit have a type that's like solana_account::Account?

Perhaps this makes the move to publishing internal.js directly more pertinent as it'll keep framework opinions out-of-scope for this repo.

@lorisleiva
Copy link
Author

I have updated the PR with your comments. I only held back on not using the Account types from Kit since otherwise consumers will have to convert them back and forth but I'm happy to make the change if you still think it's for the best.

I also swapped the latestBlockhashLifetime method with a new setTransactionMessageLifetimeUsingLatestBlockhash method that accepts a transaction message and returns one with the appropriate lifetime. Let me know if you'd like me to remove it.

My offer to open a new PR that only exports the internal.js still stands if you want to take framework opinions out of this repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Node Litesvm incompatible with @solana/kit

2 participants