sol

package
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package sol implements the chain-agnostic adapter interfaces (see pkg/core) against Solana, over the custody Anchor program. The program bindings in ./custody are generated; the depositor/withdrawal-finalizer adapters and the Ed25519-precompile / digest helpers are hand-written on top of them.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildEd25519Instruction

func BuildEd25519Instruction(pubkeys, sigs [][]byte, message []byte) (solana.Instruction, error)

BuildEd25519Instruction frames the quorum's signatures for the native Ed25519SigVerify precompile, all offsets self-referencing (instruction index 0xFFFF) so the verified data cannot be smuggled from another instruction. pubkeys/sigs are parallel (32-byte / 64-byte); message is the 32-byte digest.

func ConfigPDA

func ConfigPDA(programID solana.PublicKey) solana.PublicKey

ConfigPDA / VaultPDA / WithdrawalPDA / eventAuthorityPDA derive the program's deterministic accounts.

func RotateDigest

func RotateDigest(chainID uint64, programID, config solana.PublicKey, commitment [32]byte, signerNonce uint64) [32]byte

RotateDigest computes the 32-byte digest the providers sign for a signer rotation, matching the program's `rotate_digest`:

sha256(ROTATE_DOMAIN ‖ chainID(BE) ‖ programID ‖ config
       ‖ signersCommitment ‖ signerNonce(BE))

signerNonce is the on-chain Config.SignerNonce — the rotation replay token.

func SignersCommitment

func SignersCommitment(newSigners []solana.PublicKey, newThreshold uint8) [32]byte

SignersCommitment computes sha256(newSigners ‖ newThreshold), matching the program's `signers_commitment`. It is the payload the rotation digest binds.

func VaultLookupAddresses

func VaultLookupAddresses(programID, mint solana.PublicKey) []solana.PublicKey

VaultLookupAddresses returns the invariant accounts of the execute instruction — the ones that recur across every withdrawal and are therefore eligible to populate an Address Lookup Table, letting large quorums fit a v0 transaction (set the table via WithdrawalFinalizer's Config.AddressLookupTable). A zero mint returns the native-SOL set; a non-zero mint adds the token program and the vault's associated token account. Per-withdrawal accounts (the recipient, its token account, the Withdrawal PDA, the fee payer) vary each call and are intentionally excluded.

Build the lookup table from this set so it stays in lockstep with the instruction's account layout (both live here, in the SDK).

func VaultPDA

func VaultPDA(programID solana.PublicKey) solana.PublicKey

func WithdrawDigest

func WithdrawDigest(chainID uint64, programID, vault, to, mint solana.PublicKey, amount uint64, withdrawalID [32]byte, deadline int64) [32]byte

WithdrawDigest computes the 32-byte digest the providers sign for a withdrawal, matching the program's `withdraw_digest`:

sha256(WITHDRAW_DOMAIN ‖ chainID(BE) ‖ programID ‖ vault
       ‖ to ‖ mint ‖ amount(BE) ‖ withdrawalID ‖ deadline(BE))

mint == the zero pubkey denotes native SOL. deadline is the unix-second time bound, encoded as an int64 big-endian so it matches Solana's Clock::unix_timestamp (i64) and the block SealedAt it derives from.

func WithdrawalPDA

func WithdrawalPDA(programID solana.PublicKey, withdrawalID [32]byte) solana.PublicKey

Types

type AssetResolver added in v0.5.0

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

func NewAssetResolver added in v0.5.0

func NewAssetResolver(rpcURL string, commitment rpc.CommitmentType) *AssetResolver

func (*AssetResolver) AssetDecimals added in v0.5.0

func (r *AssetResolver) AssetDecimals(ctx context.Context, assetAddress string) (uint8, error)

func (*AssetResolver) ValidateAssetAddress added in v0.5.0

func (r *AssetResolver) ValidateAssetAddress(_ context.Context, assetAddress string) error

type Config

type Config struct {
	ChainID          uint64
	ComputeUnitLimit uint32 // 0 → 200k
	ComputeUnitPrice uint64 // micro-lamports per CU (priority fee)
	// Commitment is the level at which on-chain reads (Config, the Withdrawal
	// PDA) are observed. Empty → CommitmentFinalized.
	//
	// NOTE: production should use CommitmentFinalized — a withdrawal is only
	// truly settled once finalized (no rollback). CommitmentConfirmed is a
	// devnet/test speed tradeoff: it observes results in ~1-2 slots instead of
	// waiting ~32 for finality, cutting the local flow from ~16s to a few.
	Commitment rpc.CommitmentType
	// AddressLookupTable, when set, makes the submit emit a v0 transaction using
	// this ALT. Required for large quorums: the Ed25519 instruction grows ~112
	// bytes per signer, so beyond ~8-9 signers a legacy transaction exceeds the
	// 1232-byte packet limit. Zero → legacy transaction.
	AddressLookupTable solana.PublicKey
}

Config tunes the submit transaction.

type Depositor

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

Depositor moves funds into the custody vault on Solana, signed by the depositor's own ed25519 key. It implements core.VaultDepositor. Native SOL and SPL tokens are both supported. The deposit credits the 20-byte clearnet account encoded in `account` (hex).

func NewDepositor

func NewDepositor(rpcURL string, programID solana.PublicKey, signer sign.Signer, commitment rpc.CommitmentType, assets blockchain.AssetResolver) (*Depositor, error)

NewDepositor builds the Solana depositor over the JSON-RPC at rpcURL. signer is the depositor's ed25519 key (it pays + funds). commitment is the level the deposit tx's blockhash + preflight use; empty → CommitmentFinalized (see the NOTE on the withdrawal finalizer's Config.Commitment for the test tradeoff).

func (*Depositor) DepositorAddress

func (d *Depositor) DepositorAddress() string

DepositorAddress returns the depositor's Solana address.

func (*Depositor) SubmitDeposit

func (d *Depositor) SubmitDeposit(ctx context.Context, assetAddress string, amount decimal.Decimal, dest core.DepositDestination) (string, error)

SubmitDeposit transfers amount of assetAddress into the vault, crediting clearnet dest.Account (20-byte hex) with the optional ADR-015 dest.Ref sub-account reference. assetAddress is "" for native or a base58 mint.

func (*Depositor) VerifyDeposit

func (d *Depositor) VerifyDeposit(ctx context.Context, txID string, minConf uint64) (core.DepositStatus, error)

VerifyDeposit reports the on-chain status of the deposit txID (the base58 signature). minConf maps onto Solana's commitment ladder, which has no numeric depth: minConf 0 accepts the optimistic "confirmed" level (~1-2 slots), while minConf >= 1 requires "finalized" (irreversible). A failed tx reads as DepositAbsent (it credited nothing).

type RotationFinalizer

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

RotationFinalizer rotates the custody program's signer set via update_signers, authorized by the current (outgoing) ed25519 quorum and verified on-chain via the Ed25519 precompile — the rotation analogue of WithdrawalFinalizer. The node's signer contributes one share; a separate fee-payer pays + submits. It implements core.SignerRotationFinalizer.

func NewRotationFinalizer

func NewRotationFinalizer(rpcURL string, programID solana.PublicKey, signer, feePayer sign.Signer, cfg Config) (*RotationFinalizer, error)

NewRotationFinalizer builds the finalizer. signer is this node's ed25519 custody key (one quorum share); feePayer pays for and submits the update_signers transaction. cfg reuses the withdrawal Config (chain id, compute budget, commitment).

func (*RotationFinalizer) Pack

func (f *RotationFinalizer) Pack(ctx context.Context, _ [32]byte, newSigners []string, newThreshold int) ([]byte, error)

Pack reads the live signer nonce and returns the canonical JSON for rotating to newSigners / newThreshold. opID is ignored: Solana binds rotation replay to the on-chain program signer nonce, so the operation identity is not embedded in the payload.

func (*RotationFinalizer) Sign

func (f *RotationFinalizer) Sign(ctx context.Context, packed []byte) ([]byte, error)

Sign returns this node's share: nodePubkey(32) ‖ ed25519 signature(64) over the rotation digest.

func (*RotationFinalizer) Submit

func (f *RotationFinalizer) Submit(ctx context.Context, packed []byte, shares [][]byte) (string, error)

Submit filters the collected shares against the live (outgoing) signer set, assembles the Ed25519-precompile + update_signers transaction, and broadcasts it (fee-payer signed). Idempotent: if the rotation already applied it returns without re-submitting.

func (*RotationFinalizer) Validate

func (f *RotationFinalizer) Validate(ctx context.Context, _ [32]byte, packed []byte, newSigners []string, newThreshold int) error

Validate re-derives the rotation target from newSigners / newThreshold, asserts the packed payload matches it, and re-reads the live nonce to reject a packer that bound a stale or wrong signer nonce.

func (*RotationFinalizer) VerifyRotation

func (f *RotationFinalizer) VerifyRotation(ctx context.Context, newSigners []string, newThreshold int) (string, bool, error)

VerifyRotation reports whether the on-chain Config now holds exactly the requested signer set + threshold. Binary: no txID is recoverable from the Config account, so an empty txID is returned with done=true.

type WithdrawalFinalizer

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

WithdrawalFinalizer executes a withdrawal against the custody Anchor program: a digest signed by the ed25519 quorum, verified on-chain via the Ed25519 precompile. The node's signer contributes one share; a separate fee-payer signer pays + submits. It implements core.VaultWithdrawalFinalizer, for both native SOL and SPL tokens (the SPL path adds the recipient-ATA creation and the token remaining-accounts the program's execute expects).

func NewWithdrawalFinalizer

func NewWithdrawalFinalizer(rpcURL string, programID solana.PublicKey, signer, feePayer sign.Signer, cfg Config, assets blockchain.AssetResolver) (*WithdrawalFinalizer, error)

NewWithdrawalFinalizer builds the finalizer. signer is this node's ed25519 custody key (contributes a quorum share); feePayer is a distinct ed25519 key that pays for and submits the execute transaction.

func (*WithdrawalFinalizer) Pack

func (f *WithdrawalFinalizer) Pack(ctx context.Context, op *core.WithdrawalOp, withdrawalID [32]byte, deadline int64) ([]byte, error)

Pack resolves the withdrawal target and returns the canonical JSON.

func (*WithdrawalFinalizer) Sign

func (f *WithdrawalFinalizer) Sign(ctx context.Context, packed []byte) ([]byte, error)

Sign returns this node's share: nodePubkey(32) ‖ ed25519 signature(64) over the withdrawal digest.

func (*WithdrawalFinalizer) Submit

func (f *WithdrawalFinalizer) Submit(ctx context.Context, packed []byte, shares [][]byte) (string, error)

Submit filters + orders the collected shares against the live signer set, assembles the Ed25519-precompile + execute transaction, and broadcasts it (fee-payer signed), then waits for the Withdrawal PDA to appear.

func (*WithdrawalFinalizer) Validate

func (f *WithdrawalFinalizer) Validate(ctx context.Context, packed []byte, op *core.WithdrawalOp, withdrawalID [32]byte, deadline int64) error

Validate re-derives the canonical payload from the op and the caller-supplied deadline and asserts a match — a peer packing a different deadline than the quorum agreed is rejected here, before Sign.

func (*WithdrawalFinalizer) VerifyExecution

func (f *WithdrawalFinalizer) VerifyExecution(ctx context.Context, withdrawalID [32]byte) (string, bool, error)

VerifyExecution reports whether the Withdrawal PDA exists (the on-chain executed flag). The txID is not recoverable from the PDA alone, so an empty txID is returned with executed=true.

Directories

Path Synopsis
Command idl_refresher regenerates the Solana program bindings (the pkg/blockchain/sol/custody package) from the vendored Anchor IDL using anchor-go's generator library directly — the Solana analog of the EVM abi_refresher (which drives go-ethereum's abigen).
Command idl_refresher regenerates the Solana program bindings (the pkg/blockchain/sol/custody package) from the vendored Anchor IDL using anchor-go's generator library directly — the Solana analog of the EVM abi_refresher (which drives go-ethereum's abigen).

Jump to

Keyboard shortcuts

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