Add a minimal Solana wallet API and external-signer implementation #31

Closed
opened 2026-07-18 22:14:07 +02:00 by minimons · 1 comment
Owner

Background

The Solana API can represent unsigned and signed transactions, but signing must remain separate from transaction construction and blockchain submission.

Issue #29 constructs an unsigned Jupiter Perps transaction. Before it can be submitted through SolanaBlockChain.sendTransaction(...), a wallet must authorize it by adding the required signature.

The wallet implementation must be able to sign any valid serialized Solana transaction for which the configured wallet is a required signer. It must not contain Jupiter-specific transaction logic.

The initial wallet implementation should be deliberately small. Its primary responsibility is converting:

SolanaUnsignedTransaction

into:

SolanaSignedTransaction

The implementation may use an external command, temporary files, standard input/output, or another local mechanism. These details must remain hidden behind the wallet API.

Goal

Introduce a minimal public Solana wallet interface.

Suggested package:

com.r35157.libs.solana.wallet

Suggested API:

public interface SolanaWallet {

    /**
     * Returns the public address controlled by this wallet.
     *
     * @return the wallet's public Solana address
     */
    @NotNull
    ΩSolanaWalletIdΩ getAddress();

    /**
     * Returns the native SOL balance of this wallet.
     *
     * @return the wallet's native SOL balance
     * @throws IOException if the balance cannot be fetched
     * @throws InterruptedException if the calling thread is interrupted
     */
    @NotNull
    ΩSolanaAmountΩ getSolanaBalance()
            throws IOException, InterruptedException;

    /**
     * Signs a complete unsigned Solana transaction.
     *
     * <p>This method does not submit the transaction to the blockchain.</p>
     *
     * @param transaction the unsigned transaction to sign
     * @return the complete signed transaction
     * @throws IOException if the transaction cannot be signed or decoded
     * @throws InterruptedException if the signing process is interrupted
     */
    @NotNull
    SolanaSignedTransaction signTransaction(
            @NotNull SolanaUnsignedTransaction transaction
    ) throws IOException, InterruptedException;
}

Initial implementation

Add a minimal reference implementation backed by the locally installed Jupiter CLI signer.

Suggested package:

com.r35157.libs.solana.wallet.impl.jupcli

Suggested class name:

JupiterCliSolanaWalletImpl

The implementation may internally invoke a command equivalent to:

jup sign -f json --key <key-name> --tx <unsigned-base64>

The public Java API must not expose this implementation detail.

Requirements

Signing

  • Accept a SolanaUnsignedTransaction.
  • Sign its serialized Base64 transaction using the configured local wallet.
  • Parse the machine-readable signer output.
  • Return a SolanaSignedTransaction.
  • Validate that the signer address returned by the external signer matches the wallet address configured for the implementation.
  • Reject missing or blank unsigned transaction data.
  • Report process failures and malformed signer output as meaningful Java exceptions.
  • Do not submit the transaction.
  • Do not modify or rebuild the transaction.

Address

  • getAddress() returns the public address controlled by the configured signing key.
  • The public address must be available without exposing private-key material.
  • The address should use the existing ΩSolanaWalletIdΩ ValueTag.

Balance

  • getSolanaBalance() delegates to:
SolanaBlockChain.getBalanceInSolana(getAddress())
  • The wallet implementation must not duplicate Solana RPC balance logic.

External process isolation

  • Private-key bytes must never be passed into or returned from the public Java API.
  • Private-key contents must never be logged.
  • The implementation may refer to an external signer key by a local alias such as:
evelyn-prod
  • Whether the unsigned transaction is passed through command arguments, standard input, or temporary files is an implementation detail.
  • Temporary files must be removed after use when temporary files are used.
  • Standard output used for machine parsing must not contain decorative human-readable formatting.
  • Standard error should be preserved or included in exceptions when the signer process fails.

Architecture

  • SolanaWallet must not contain Jupiter Perps domain logic.
  • The implementation may depend on SolanaBlockChain for read operations.
  • The implementation must not call SolanaBlockChain.sendTransaction(...) from signTransaction(...).
  • Signing and submission must remain two separate explicit operations.
  • The complete repository must continue to compile.

Verification

Using the local evelyn-prod signer:

  1. Construct an unsigned transaction through issue #29.
  2. Pass it to SolanaWallet.signTransaction(...).
  3. Verify that the returned SolanaSignedTransaction is non-empty.
  4. Verify that the signer address matches the expected wallet address.
  5. Submit the result separately through SolanaBlockChain.sendTransaction(...).
  6. Verify that the expected Jupiter Perps position was changed.

Also verify that:

wallet.getSolanaBalance()

returns the same value as:

solanaBlockChain.getBalanceInSolana(wallet.getAddress())

Out of scope

The following are not part of this issue:

  • Creating or importing private keys
  • Seed phrases
  • Key encryption
  • Password prompts
  • Hardware wallets
  • Multiple signatures
  • Transaction submission
  • Confirmation polling
  • Browser-wallet integration
  • A graphical wallet interface
  • General token-balance APIs
  • SOL transfers
  • SPL token transfers
  • A pure-Java cryptographic wallet implementation

These capabilities may be introduced later through other implementations or extensions.

Definition of done

  • The public SolanaWallet interface exists.
  • The interface contains getAddress(), getSolanaBalance(), and signTransaction(...).
  • A minimal Jupiter CLI-backed implementation exists.
  • The implementation signs an unsigned transaction using the configured local wallet.
  • A valid SolanaSignedTransaction is returned.
  • The returned signer address is validated against the configured wallet address.
  • getSolanaBalance() delegates to the existing Solana API.
  • Signing does not submit the transaction.
  • No private-key material is exposed through the Java API or logs.
  • The complete repository compiles.
## Background The Solana API can represent unsigned and signed transactions, but signing must remain separate from transaction construction and blockchain submission. Issue #29 constructs an unsigned Jupiter Perps transaction. Before it can be submitted through `SolanaBlockChain.sendTransaction(...)`, a wallet must authorize it by adding the required signature. The wallet implementation must be able to sign any valid serialized Solana transaction for which the configured wallet is a required signer. It must not contain Jupiter-specific transaction logic. The initial wallet implementation should be deliberately small. Its primary responsibility is converting: ```text SolanaUnsignedTransaction ``` into: ```text SolanaSignedTransaction ``` The implementation may use an external command, temporary files, standard input/output, or another local mechanism. These details must remain hidden behind the wallet API. ## Goal Introduce a minimal public Solana wallet interface. Suggested package: ```java com.r35157.libs.solana.wallet ``` Suggested API: ```java public interface SolanaWallet { /** * Returns the public address controlled by this wallet. * * @return the wallet's public Solana address */ @NotNull ΩSolanaWalletIdΩ getAddress(); /** * Returns the native SOL balance of this wallet. * * @return the wallet's native SOL balance * @throws IOException if the balance cannot be fetched * @throws InterruptedException if the calling thread is interrupted */ @NotNull ΩSolanaAmountΩ getSolanaBalance() throws IOException, InterruptedException; /** * Signs a complete unsigned Solana transaction. * * <p>This method does not submit the transaction to the blockchain.</p> * * @param transaction the unsigned transaction to sign * @return the complete signed transaction * @throws IOException if the transaction cannot be signed or decoded * @throws InterruptedException if the signing process is interrupted */ @NotNull SolanaSignedTransaction signTransaction( @NotNull SolanaUnsignedTransaction transaction ) throws IOException, InterruptedException; } ``` ## Initial implementation Add a minimal reference implementation backed by the locally installed Jupiter CLI signer. Suggested package: ```java com.r35157.libs.solana.wallet.impl.jupcli ``` Suggested class name: ```java JupiterCliSolanaWalletImpl ``` The implementation may internally invoke a command equivalent to: ```bash jup sign -f json --key <key-name> --tx <unsigned-base64> ``` The public Java API must not expose this implementation detail. ## Requirements ### Signing * Accept a `SolanaUnsignedTransaction`. * Sign its serialized Base64 transaction using the configured local wallet. * Parse the machine-readable signer output. * Return a `SolanaSignedTransaction`. * Validate that the signer address returned by the external signer matches the wallet address configured for the implementation. * Reject missing or blank unsigned transaction data. * Report process failures and malformed signer output as meaningful Java exceptions. * Do not submit the transaction. * Do not modify or rebuild the transaction. ### Address * `getAddress()` returns the public address controlled by the configured signing key. * The public address must be available without exposing private-key material. * The address should use the existing `ΩSolanaWalletIdΩ` ValueTag. ### Balance * `getSolanaBalance()` delegates to: ```java SolanaBlockChain.getBalanceInSolana(getAddress()) ``` * The wallet implementation must not duplicate Solana RPC balance logic. ### External process isolation * Private-key bytes must never be passed into or returned from the public Java API. * Private-key contents must never be logged. * The implementation may refer to an external signer key by a local alias such as: ```text evelyn-prod ``` * Whether the unsigned transaction is passed through command arguments, standard input, or temporary files is an implementation detail. * Temporary files must be removed after use when temporary files are used. * Standard output used for machine parsing must not contain decorative human-readable formatting. * Standard error should be preserved or included in exceptions when the signer process fails. ### Architecture * `SolanaWallet` must not contain Jupiter Perps domain logic. * The implementation may depend on `SolanaBlockChain` for read operations. * The implementation must not call `SolanaBlockChain.sendTransaction(...)` from `signTransaction(...)`. * Signing and submission must remain two separate explicit operations. * The complete repository must continue to compile. ## Verification Using the local `evelyn-prod` signer: 1. Construct an unsigned transaction through issue #29. 2. Pass it to `SolanaWallet.signTransaction(...)`. 3. Verify that the returned `SolanaSignedTransaction` is non-empty. 4. Verify that the signer address matches the expected wallet address. 5. Submit the result separately through `SolanaBlockChain.sendTransaction(...)`. 6. Verify that the expected Jupiter Perps position was changed. Also verify that: ```java wallet.getSolanaBalance() ``` returns the same value as: ```java solanaBlockChain.getBalanceInSolana(wallet.getAddress()) ``` ## Out of scope The following are not part of this issue: * Creating or importing private keys * Seed phrases * Key encryption * Password prompts * Hardware wallets * Multiple signatures * Transaction submission * Confirmation polling * Browser-wallet integration * A graphical wallet interface * General token-balance APIs * SOL transfers * SPL token transfers * A pure-Java cryptographic wallet implementation These capabilities may be introduced later through other implementations or extensions. ## Definition of done * The public `SolanaWallet` interface exists. * The interface contains `getAddress()`, `getSolanaBalance()`, and `signTransaction(...)`. * A minimal Jupiter CLI-backed implementation exists. * The implementation signs an unsigned transaction using the configured local wallet. * A valid `SolanaSignedTransaction` is returned. * The returned signer address is validated against the configured wallet address. * `getSolanaBalance()` delegates to the existing Solana API. * Signing does not submit the transaction. * No private-key material is exposed through the Java API or logs. * The complete repository compiles.
minimons added the enhancement label 2026-07-18 22:14:07 +02:00
minimons self-assigned this 2026-07-18 22:14:07 +02:00
minimons added this to the AssetAZ project 2026-07-18 22:14:07 +02:00
minimons moved this to Done in AssetAZ on 2026-07-19 01:35:28 +02:00
Author
Owner

Implemented the minimal Solana wallet API and reference implementation.

The wallet now:

  • exposes its public Solana address,
  • delegates native SOL balance lookup to SolanaBlockChain,
  • signs arbitrary serialized Solana transactions through the configured local Jupiter CLI key,
  • validates that the returned signer matches the configured wallet address,
  • returns a SolanaSignedTransaction without submitting it.

The implementation was verified using a real unsigned Jupiter Perps transaction. The transaction was signed successfully and later executed on-chain through Jupiter's transaction relay.

Implemented the minimal Solana wallet API and reference implementation. The wallet now: - exposes its public Solana address, - delegates native SOL balance lookup to SolanaBlockChain, - signs arbitrary serialized Solana transactions through the configured local Jupiter CLI key, - validates that the returned signer matches the configured wallet address, - returns a SolanaSignedTransaction without submitting it. The implementation was verified using a real unsigned Jupiter Perps transaction. The transaction was signed successfully and later executed on-chain through Jupiter's transaction relay.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: r35157/com_r35157_nenjim-hubd-impl_ref#31