X402.Solana (X402 v0.6.0)

Copy Markdown View Source

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 runtime
  • associated_token_address/3 — the Associated Token Account PDA for an (owner, mint, token program) triple, which is where the exact scheme says the payment must land (scheme spec §1.1)

All hashing uses OTP's :crypto; no new dependencies.

Summary

Types

A Base58-encoded Solana address.

A raw 32-byte Ed25519 public key or PDA.

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

address()

@type address() :: String.t()

A Base58-encoded Solana address.

pubkey()

@type pubkey() :: <<_::256>>

A raw 32-byte Ed25519 public key or PDA.

Functions

associated_token_address(owner, mint, token_program \\ "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")

(since 0.6.0)
@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}

ata_program()

(since 0.6.0)
@spec ata_program() :: address()

The Associated Token Account program address.

Examples

iex> X402.Solana.ata_program()
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"

compute_budget_program()

(since 0.6.0)
@spec compute_budget_program() :: address()

The Compute Budget program address.

Examples

iex> X402.Solana.compute_budget_program()
"ComputeBudget111111111111111111111111111111"

create_program_address(seeds, program_id)

(since 0.6.0)
@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.

decode_address(address)

(since 0.6.0)
@spec decode_address(term()) :: {:ok, pubkey()} | {:error, :invalid_address}

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}

find_program_address(seeds, program_id)

(since 0.6.0)
@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}

lighthouse_program()

(since 0.6.0)
@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"

memo_program()

(since 0.6.0)
@spec memo_program() :: address()

The SPL Memo program address.

Examples

iex> X402.Solana.memo_program()
"MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"

on_curve?(pubkey)

(since 0.6.0)
@spec on_curve?(pubkey()) :: boolean()

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

token_2022_program()

(since 0.6.0)
@spec token_2022_program() :: address()

The Token-2022 program address.

Examples

iex> X402.Solana.token_2022_program()
"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"

token_program()

(since 0.6.0)
@spec token_program() :: address()

The SPL Token program address.

Examples

iex> X402.Solana.token_program()
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"

valid_address?(address)

(since 0.6.0)
@spec valid_address?(term()) :: boolean()

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