Solana address primitives: program IDs, PDA and ATA derivation.
Implements the address arithmetic the exact scheme on solana:*
networks needs without any RPC or native dependencies:
- Base58 address validation/decoding (32-byte Ed25519 public keys)
- The Ed25519 on-curve check used to reject PDA candidates (RFC 8032 point decompression over integer arithmetic)
create_program_address/2/find_program_address/2— SHA-256 program derived addresses, as specified by the Solana runtimeassociated_token_address/3— the Associated Token Account PDA for an (owner, mint, token program) triple, which is where theexactscheme says the payment must land (scheme spec §1.1)
All hashing uses OTP's :crypto; no new dependencies.
Summary
Functions
Derives the Associated Token Account address for an owner and mint.
The Associated Token Account program address.
The Compute Budget program address.
Derives a program address from seeds and a program ID (no bump search).
Decodes a Base58 address into its raw 32-byte public key.
Finds the first off-curve program address, searching bump seeds 255 down
to 0 — the canonical find_program_address from the Solana SDKs.
The Lighthouse assertion program address (wallet-injected guard instructions; allowed as optional instructions by the scheme spec §3.1).
The SPL Memo program address.
Whether 32 bytes decompress to a point on the Ed25519 curve.
The Token-2022 program address.
The SPL Token program address.
Whether a term is a Base58 string decoding to 32 bytes.
Types
@type address() :: String.t()
A Base58-encoded Solana address.
@type pubkey() :: <<_::256>>
A raw 32-byte Ed25519 public key or PDA.
Functions
@spec associated_token_address(address(), address(), address()) :: {:ok, address()} | {:error, :invalid_address | :not_found}
Derives the Associated Token Account address for an owner and mint.
This is the destination the exact SVM scheme requires: the ATA derived
from payTo and asset under the relevant token program (scheme spec
§1.1–1.2). Seeds are [owner, token_program, mint] under the ATA
program.
Examples
iex> X402.Solana.associated_token_address(
...> "GyGKxMyg1p9SsHfm15MkNUu1u9TN2JtTspcdmrtGUdse",
...> "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
...> X402.Solana.token_program()
...> )
{:ok, "DNDTCnZkNk358qDFZd9unHtnrc73SsXcpVWtwJJMrR4B"}
iex> X402.Solana.associated_token_address("bogus", "also-bogus", "nope")
{:error, :invalid_address}
@spec ata_program() :: address()
The Associated Token Account program address.
Examples
iex> X402.Solana.ata_program()
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
@spec compute_budget_program() :: address()
The Compute Budget program address.
Examples
iex> X402.Solana.compute_budget_program()
"ComputeBudget111111111111111111111111111111"
@spec create_program_address([binary()], address()) :: {:ok, pubkey()} | {:error, :on_curve | :invalid_seeds | :invalid_address}
Derives a program address from seeds and a program ID (no bump search).
Seeds are binaries of at most 32 bytes each, at most 16 seeds. Returns
{:error, :on_curve} when the SHA-256 candidate lands on the Ed25519
curve (callers should try another bump seed) and
{:error, :invalid_seeds} for out-of-range seeds.
Decodes a Base58 address into its raw 32-byte public key.
Examples
iex> X402.Solana.decode_address("11111111111111111111111111111111")
{:ok, <<0::256>>}
iex> X402.Solana.decode_address("tooshort")
{:error, :invalid_address}
iex> X402.Solana.decode_address(nil)
{:error, :invalid_address}
@spec find_program_address([binary()], address()) :: {:ok, {pubkey(), byte()}} | {:error, :invalid_seeds | :invalid_address | :not_found}
Finds the first off-curve program address, searching bump seeds 255 down
to 0 — the canonical find_program_address from the Solana SDKs.
Returns the raw 32-byte address and the bump seed that produced it.
Examples
iex> {:ok, {address, bump}} =
...> X402.Solana.find_program_address(
...> ["hello", "world"],
...> "11111111111111111111111111111111"
...> )
iex> {X402.Base58.encode(address), bump}
{"JDC4d5bNdpBPNLHfugxDcuknk6e9cp2xBis5V5v67PGh", 253}
@spec lighthouse_program() :: address()
The Lighthouse assertion program address (wallet-injected guard instructions; allowed as optional instructions by the scheme spec §3.1).
Examples
iex> X402.Solana.lighthouse_program()
"L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95"
@spec memo_program() :: address()
The SPL Memo program address.
Examples
iex> X402.Solana.memo_program()
"MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
Whether 32 bytes decompress to a point on the Ed25519 curve.
Program derived addresses must not be on the curve (so no private key
can exist for them); create_program_address/2 uses this check. The
algorithm mirrors the reference SDKs' vendored Ed25519 decompression:
mask the sign bit, solve x^2 = (y^2 - 1) / (d*y^2 + 1), and require a
square root to exist (with x = 0 requiring a zero sign bit).
Examples
iex> {:ok, system_program} = X402.Solana.decode_address("11111111111111111111111111111111")
iex> X402.Solana.on_curve?(system_program)
true
iex> {:ok, ata} = X402.Solana.decode_address("DNDTCnZkNk358qDFZd9unHtnrc73SsXcpVWtwJJMrR4B")
iex> X402.Solana.on_curve?(ata)
false
@spec token_2022_program() :: address()
The Token-2022 program address.
Examples
iex> X402.Solana.token_2022_program()
"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
@spec token_program() :: address()
The SPL Token program address.
Examples
iex> X402.Solana.token_program()
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
Whether a term is a Base58 string decoding to 32 bytes.
Examples
iex> X402.Solana.valid_address?("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
true
iex> X402.Solana.valid_address?("0x036CbD53842c5426634e7929541eC2318f3dCF7e")
false