svm

package
v0.0.34 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 13, 2026 License: MIT Imports: 29 Imported by: 0

Documentation

Overview

Package svm implements the Solana (SVM) transaction builder for Push Chain's cross-chain outbound transaction system.

How Cross-Chain Outbound Works (High-Level)

When a user on Push Chain wants to send funds/execute something on Solana:

  1. Push Chain emits an OutboundCreatedEvent with details (amount, recipient, etc.)
  2. A coordinator node picks up the event
  3. This TxBuilder constructs the message that needs to be signed (GetOutboundSigningRequest)
  4. Push Chain validators collectively sign the message using TSS (Threshold Signature Scheme) - TSS uses secp256k1 (same curve as Ethereum) — the TSS group has an ETH-style address
  5. This TxBuilder assembles the full Solana transaction with the TSS signature and broadcasts it (BroadcastOutboundSigningRequest)
  6. The Solana gateway contract verifies the TSS signature on-chain using secp256k1_recover

Two-Signature Architecture

Every Solana transaction requires TWO different signatures:

  • TSS Signature (secp256k1/ECDSA): Signs the message hash. Verified by the gateway contract on-chain via secp256k1_recover. This proves the Push Chain validators approved the operation. The TSS group's ETH address is stored in the TSS PDA on Solana.

  • Relayer Signature (Ed25519): Signs the Solana transaction itself. This is a standard Solana transaction signature from the relayer's keypair. The relayer pays for gas (SOL).

Gateway Contract (Anchor/Rust on Solana)

The gateway is an Anchor program deployed on Solana with these main entry points:

  • finalize_universal_tx (instruction_id=1 for withdraw, 2 for execute): Unified function that handles both simple fund transfers and arbitrary program execution. For withdraw: transfers SOL/SPL from the vault to a recipient. For execute: calls an arbitrary Solana program via CPI with provided accounts and data.

  • revert_universal_tx (instruction_id=3): Reverts a failed cross-chain tx, returns native SOL.

  • revert_universal_tx_token (instruction_id=4): Same but for SPL tokens.

Key Concepts

  • PDA (Program Derived Address): Deterministic addresses derived from seeds + program ID. Like CREATE2 in EVM. The gateway uses PDAs for config, vault, TSS state, etc.

  • Anchor Discriminator: First 8 bytes of sha256("global:<method_name>"). Tells the Anchor framework which function to call. Similar to EVM function selectors (4 bytes of keccak256).

  • Borsh Serialization: Solana's standard binary format. Little-endian integers, Vec<T> = 4-byte LE length prefix + elements. Used for instruction data.

  • TSS PDA: Stores the TSS group's 20-byte ETH address and chain ID. Replay protection uses per-tx ExecutedTx PDAs.

  • CEA (Cross-chain Execution Account): Per-sender identity PDA derived from the EVM sender address.

  • ATA (Associated Token Account): Deterministic token account for a wallet + mint pair. Like mapping(address => mapping(token => balance)) in EVM, but accounts are explicit on Solana.

Index

Constants

View Source
const (
	EventTypeSendFunds = "send_funds"
	// Outbound observation events (emitted by gateway on SVM since there's no vault)
	EventTypeFinalizeUniversalTx = "finalize_universal_tx"
	EventTypeRevertUniversalTx   = "revert_universal_tx"
	EventTypeFundsRescued        = "funds_rescued"
)

Event type constants

Variables

This section is empty.

Functions

func ParseEvent added in v0.0.13

func ParseEvent(log string, signature string, slot uint64, logIndex uint, eventType string, chainID string, logger zerolog.Logger) *store.Event

ParseEvent parses a log into a store.Event based on the event type. eventType should be one of: "send_funds", "executeUniversalTx", "revertUniversalTx"

Types

type ChainMetaOracle added in v0.0.19

type ChainMetaOracle struct {
	// contains filtered or unexported fields
}

ChainMetaOracle handles fetching and reporting gas prices (prioritization fees)

func NewChainMetaOracle added in v0.0.19

func NewChainMetaOracle(
	rpcClient *RPCClient,
	pushSigner *pushsigner.Signer,
	chainID string,
	gasPriceIntervalSeconds int,
	gasPriceMarkupPercent int,
	logger zerolog.Logger,
) *ChainMetaOracle

NewChainMetaOracle creates a new gas oracle

func (*ChainMetaOracle) Start added in v0.0.19

func (g *ChainMetaOracle) Start(ctx context.Context) error

Start begins fetching and voting on gas prices

func (*ChainMetaOracle) Stop added in v0.0.19

func (g *ChainMetaOracle) Stop()

Stop stops the gas oracle

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client implements the ChainClient interface for Solana chains

func NewClient

func NewClient(
	config *uregistrytypes.ChainConfig,
	database *db.DB,
	chainConfig *config.ChainSpecificConfig,
	pushSigner *pushsigner.Signer,
	nodeHome string,
	logger zerolog.Logger,
) (*Client, error)

NewClient creates a new Solana chain client

func (*Client) ChainID added in v0.0.13

func (c *Client) ChainID() string

ChainID returns the chain ID string

func (*Client) GetConfig added in v0.0.13

func (c *Client) GetConfig() *uregistrytypes.ChainConfig

GetConfig returns the registry chain config

func (*Client) GetTxBuilder added in v0.0.13

func (c *Client) GetTxBuilder() (common.TxBuilder, error)

GetTxBuilder returns the TxBuilder for this chain

func (*Client) IsHealthy

func (c *Client) IsHealthy() bool

IsHealthy checks if the Solana chain RPC client is healthy

func (*Client) Start

func (c *Client) Start(ctx context.Context) error

Start initializes and starts the Solana chain client

func (*Client) Stop

func (c *Client) Stop() error

Stop gracefully shuts down the Solana chain client

type EventConfirmer added in v0.0.13

type EventConfirmer struct {
	// contains filtered or unexported fields
}

EventConfirmer periodically checks pending events and marks them as CONFIRMED once their transactions are confirmed on-chain.

func NewEventConfirmer added in v0.0.13

func NewEventConfirmer(
	rpcClient *RPCClient,
	database *db.DB,
	chainID string,
	pollIntervalSeconds int,
	fastConfirmations uint64,
	standardConfirmations uint64,
	logger zerolog.Logger,
) *EventConfirmer

NewEventConfirmer creates a new event confirmer

func (*EventConfirmer) Start added in v0.0.13

func (ec *EventConfirmer) Start(ctx context.Context) error

Start begins checking and confirming events

func (*EventConfirmer) Stop added in v0.0.13

func (ec *EventConfirmer) Stop()

Stop stops the event confirmer

type EventListener added in v0.0.13

type EventListener struct {
	// contains filtered or unexported fields
}

EventListener listens for gateway events on SVM chains and stores them in the database

func NewEventListener added in v0.0.13

func NewEventListener(
	rpcClient *RPCClient,
	gatewayAddress string,
	chainID string,
	gatewayMethods []*uregistrytypes.GatewayMethods,
	database *db.DB,
	eventPollingSeconds int,
	eventStartFrom *int64,
	logger zerolog.Logger,
) (*EventListener, error)

NewEventListener creates a new SVM event listener

func (*EventListener) IsRunning added in v0.0.13

func (el *EventListener) IsRunning() bool

IsRunning returns whether the listener is currently running

func (*EventListener) Start added in v0.0.13

func (el *EventListener) Start(ctx context.Context) error

Start begins listening for gateway events

func (*EventListener) Stop added in v0.0.13

func (el *EventListener) Stop() error

Stop gracefully stops the event listener

type GatewayAccountMeta added in v0.0.16

type GatewayAccountMeta struct {
	Pubkey     [32]byte // Solana public key (32 bytes, not base58-encoded)
	IsWritable bool     // Whether the target program needs to write to this account
}

GatewayAccountMeta represents a single account that a target program needs when executing an arbitrary cross-chain call (instruction_id=2). The payload from Push Chain includes a list of these — each with the account's public key and whether it needs write access. This mirrors the Rust struct in the gateway contract (state.rs).

type RPCClient added in v0.0.13

type RPCClient struct {
	// contains filtered or unexported fields
}

RPCClient provides SVM-specific RPC operations

func NewRPCClient added in v0.0.13

func NewRPCClient(rpcURLs []string, expectedGenesisHash string, logger zerolog.Logger) (*RPCClient, error)

NewRPCClient creates a new SVM RPC client from RPC URLs and validates genesis hash

func (*RPCClient) BroadcastTransaction added in v0.0.13

func (rc *RPCClient) BroadcastTransaction(ctx context.Context, tx *solana.Transaction) (string, error)

BroadcastTransaction broadcasts a signed transaction and returns the transaction signature (hash)

func (*RPCClient) Close added in v0.0.13

func (rc *RPCClient) Close()

Close closes all RPC connections

func (*RPCClient) GetAccountData added in v0.0.13

func (rc *RPCClient) GetAccountData(ctx context.Context, pubkey solana.PublicKey) ([]byte, error)

GetAccountData fetches account data for a given public key

func (*RPCClient) GetGasPrice added in v0.0.13

func (rc *RPCClient) GetGasPrice(ctx context.Context) (*big.Int, error)

GetGasPrice fetches the current gas price (prioritization fee) from Solana

func (*RPCClient) GetLatestSlot added in v0.0.13

func (rc *RPCClient) GetLatestSlot(ctx context.Context) (uint64, error)

GetLatestSlot returns the latest slot number

func (*RPCClient) GetRecentBlockhash added in v0.0.13

func (rc *RPCClient) GetRecentBlockhash(ctx context.Context) (solana.Hash, error)

GetRecentBlockhash gets a recent blockhash for transaction building

func (*RPCClient) GetSignaturesForAddress added in v0.0.13

func (rc *RPCClient) GetSignaturesForAddress(ctx context.Context, address solana.PublicKey) ([]*rpc.TransactionSignature, error)

GetSignaturesForAddress gets transaction signatures for an address

func (*RPCClient) GetTransaction added in v0.0.13

func (rc *RPCClient) GetTransaction(ctx context.Context, signature solana.Signature) (*rpc.GetTransactionResult, error)

GetTransaction gets a transaction by signature

func (*RPCClient) IsHealthy added in v0.0.13

func (rc *RPCClient) IsHealthy(ctx context.Context) bool

IsHealthy checks if any RPC in the pool is healthy by pinging it

func (*RPCClient) SimulateTransaction added in v0.0.16

func (rc *RPCClient) SimulateTransaction(ctx context.Context, tx *solana.Transaction) (*rpc.SimulateTransactionResult, error)

SimulateTransaction runs a transaction against the current ledger state without broadcasting. Returns the simulation result (logs, error, compute units consumed). Skips signature verification so the TSS/relayer signatures don't need to be valid.

type TxBuilder added in v0.0.13

type TxBuilder struct {
	// contains filtered or unexported fields
}

TxBuilder constructs and broadcasts Solana transactions for cross-chain operations. It implements the common.TxBuilder interface shared with the EVM tx builder.

The builder needs:

  • rpcClient: to talk to a Solana RPC node (fetch account data, send transactions)
  • chainID: identifies the Solana cluster (e.g., "solana:EtWTRABZ..." for devnet)
  • gatewayAddress: the deployed gateway program's public key on Solana
  • nodeHome: filesystem path where the relayer's Solana keypair is stored

func NewTxBuilder added in v0.0.13

func NewTxBuilder(
	rpcClient *RPCClient,
	chainID string,
	gatewayAddress string,
	nodeHome string,
	logger zerolog.Logger,
	chainConfig *config.ChainSpecificConfig,
) (*TxBuilder, error)

NewTxBuilder creates a new Solana transaction builder. gatewayAddress must be a valid base58-encoded Solana public key pointing to the deployed gateway program.

func (*TxBuilder) BroadcastFundMigrationTx added in v0.0.28

func (tb *TxBuilder) BroadcastFundMigrationTx(ctx context.Context, req *common.UnsignedSigningReq, data *common.FundMigrationData, signature []byte) (string, error)

BroadcastFundMigrationTx is not supported for SVM - funds are held by the program, not the TSS key.

func (*TxBuilder) BroadcastOutboundSigningRequest added in v0.0.13

func (tb *TxBuilder) BroadcastOutboundSigningRequest(
	ctx context.Context,
	req *common.UnsignedSigningReq,
	data *uetypes.OutboundCreatedEvent,
	signature []byte,
) (string, error)

BroadcastOutboundSigningRequest assembles a complete Solana transaction with the TSS signature and broadcasts it to the Solana network.

func (*TxBuilder) BuildOutboundTransaction added in v0.0.16

func (tb *TxBuilder) BuildOutboundTransaction(
	ctx context.Context,
	req *common.UnsignedSigningReq,
	data *uetypes.OutboundCreatedEvent,
	signature []byte,
) (*solana.Transaction, uint8, error)

BuildOutboundTransaction assembles a complete signed Solana transaction from the TSS signature and event data, without broadcasting. Returns the transaction and the instruction ID for logging. Use this for simulation or inspection.

func (*TxBuilder) GetFundMigrationSigningRequest added in v0.0.28

func (tb *TxBuilder) GetFundMigrationSigningRequest(ctx context.Context, data *common.FundMigrationData, nonce uint64) (*common.UnsignedSigningReq, error)

GetFundMigrationSigningRequest is not supported for SVM - funds are held by the program, not the TSS key.

func (*TxBuilder) GetGasFeeUsed added in v0.0.19

func (tb *TxBuilder) GetGasFeeUsed(ctx context.Context, txHash string) (string, error)

GetGasFeeUsed returns "0" for SVM. SVM gas accounting is handled via vault gasFee reimbursement — the actual gas cost is the base fee + PDA rent paid by the relayer, which is reimbursed from the gasFee baked into the signed message.

func (*TxBuilder) GetNextNonce added in v0.0.16

func (tb *TxBuilder) GetNextNonce(ctx context.Context, signerAddress string, useFinalized bool) (uint64, error)

GetNextNonce returns 0 for SVM. The contract no longer uses a global nonce; replay protection is handled by per-tx ExecutedTx PDAs.

func (*TxBuilder) GetOutboundSigningRequest added in v0.0.13

func (tb *TxBuilder) GetOutboundSigningRequest(
	ctx context.Context,
	data *uetypes.OutboundCreatedEvent,
	nonce uint64,
) (*common.UnsignedSigningReq, error)

GetOutboundSigningRequest creates a signing request from an outbound event. Returns a 32-byte keccak256 hash that the TSS nodes need to sign.

func (*TxBuilder) IsAlreadyExecuted added in v0.0.17

func (tb *TxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, error)

IsAlreadyExecuted checks if the ExecutedTx PDA for the given txID exists on-chain, indicating another relayer has already processed this transaction.

func (*TxBuilder) VerifyBroadcastedTx added in v0.0.16

func (tb *TxBuilder) VerifyBroadcastedTx(ctx context.Context, txHash string) (found bool, blockHeight uint64, confirmations uint64, status uint8, err error)

VerifyBroadcastedTx checks the status of a broadcasted transaction on Solana. Returns (found, confirmations, status, error): - found=false: tx not found or not yet confirmed - found=true: tx exists on-chain

  • confirmations: number of slots since the tx was included (0 = just confirmed)
  • status: 0 = failed, 1 = success

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL