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:
- Push Chain emits an OutboundCreatedEvent with details (amount, recipient, etc.)
- A coordinator node picks up the event
- This TxBuilder constructs the message that needs to be signed (GetOutboundSigningRequest)
- 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
- This TxBuilder assembles the full Solana transaction with the TSS signature and broadcasts it (BroadcastOutboundSigningRequest)
- 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
- func ParseEvent(log string, signature string, slot uint64, logIndex uint, eventType string, ...) *store.Event
- type ChainMetaOracle
- type Client
- type EventConfirmer
- type EventListener
- type GatewayAccountMeta
- type RPCClient
- func (rc *RPCClient) BroadcastTransaction(ctx context.Context, tx *solana.Transaction) (string, error)
- func (rc *RPCClient) Close()
- func (rc *RPCClient) GetAccountData(ctx context.Context, pubkey solana.PublicKey) ([]byte, error)
- func (rc *RPCClient) GetGasPrice(ctx context.Context) (*big.Int, error)
- func (rc *RPCClient) GetLatestSlot(ctx context.Context) (uint64, error)
- func (rc *RPCClient) GetRecentBlockhash(ctx context.Context) (solana.Hash, error)
- func (rc *RPCClient) GetSignaturesForAddress(ctx context.Context, address solana.PublicKey) ([]*rpc.TransactionSignature, error)
- func (rc *RPCClient) GetTransaction(ctx context.Context, signature solana.Signature) (*rpc.GetTransactionResult, error)
- func (rc *RPCClient) IsHealthy(ctx context.Context) bool
- func (rc *RPCClient) SimulateTransaction(ctx context.Context, tx *solana.Transaction) (*rpc.SimulateTransactionResult, error)
- type TxBuilder
- func (tb *TxBuilder) BroadcastFundMigrationTx(ctx context.Context, req *common.UnsignedSigningReq, ...) (string, error)
- func (tb *TxBuilder) BroadcastOutboundSigningRequest(ctx context.Context, req *common.UnsignedSigningReq, ...) (string, error)
- func (tb *TxBuilder) BuildOutboundTransaction(ctx context.Context, req *common.UnsignedSigningReq, ...) (*solana.Transaction, uint8, error)
- func (tb *TxBuilder) GetFundMigrationSigningRequest(ctx context.Context, data *common.FundMigrationData, nonce uint64) (*common.UnsignedSigningReq, error)
- func (tb *TxBuilder) GetGasFeeUsed(ctx context.Context, txHash string) (string, error)
- func (tb *TxBuilder) GetNextNonce(ctx context.Context, signerAddress string, useFinalized bool) (uint64, error)
- func (tb *TxBuilder) GetOutboundSigningRequest(ctx context.Context, data *uetypes.OutboundCreatedEvent, nonce uint64) (*common.UnsignedSigningReq, error)
- func (tb *TxBuilder) IsAlreadyExecuted(ctx context.Context, txID string) (bool, error)
- func (tb *TxBuilder) VerifyBroadcastedTx(ctx context.Context, txHash string) (found bool, blockHeight uint64, confirmations uint64, status uint8, err error)
Constants ¶
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) 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
GetTxBuilder returns the TxBuilder for this chain
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
GetAccountData fetches account data for a given public key
func (*RPCClient) GetGasPrice ¶ added in v0.0.13
GetGasPrice fetches the current gas price (prioritization fee) from Solana
func (*RPCClient) GetLatestSlot ¶ added in v0.0.13
GetLatestSlot returns the latest slot number
func (*RPCClient) GetRecentBlockhash ¶ added in v0.0.13
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
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
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
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