Solana payment method — verifies native SOL and SPL token charge payments.
Three credential types are supported, matching draft-solana-charge-00:
type="transaction"(pull, default) — the client sends signed legacy transaction bytes; the server optionally co-signs as fee payer, simulates, broadcasts, and waits for confirmation.type="signature"(push) — the client broadcasts the transaction and sends the confirmed signature; the server fetches it via RPC and matches the transfer against the charge.type="bundle"(confidential) — the client sends ordered proof setup, Token-2022 confidential transfer, and proof close transactions. The server confirms the encrypted amount with its recipient ElGamal key.
Configuration
Pass Solana-specific config via :method_config in MPP.Plug opts:
plug MPP.Plug,
secret_key: "hmac-secret",
realm: "api.example.com",
method: MPP.Methods.Solana,
amount: "10000000",
currency: "sol",
recipient: "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
method_config: %{
"rpc_url" => "https://api.devnet.solana.com",
"network" => "devnet"
}Config Keys
"rpc_url"— (required) Solana JSON-RPC endpoint URL"network"— (optional)"mainnet","devnet", or"localnet"; advertised in challenge details, defaults to"mainnet""decimals"— (required for SPL) token decimal count advertised to clients"token_program"— (optional) base58 Token or Token-2022 program id"fee_payer"— (optional) server fee sponsorship, defaults tofalse"fee_payer_private_key"— (required whenfee_payer: true) Ed25519 seed as hex, base58, or a Solana CLI JSON keypair"fee_payer_key"— (optional) base58 fee-payer pubkey; derived from the private key when omitted"splits"— (optional) at most 8 extra payment legs (recipient,amount, optionalmemo, optionalataCreationRequired)"store"— (optional) replay-dedup store, on by default (seeMPP.Methods.EVMfor the same contract). Passstore: falseto opt out"req_options"— (optional) merged intoCartouche.Solana.RPCcalls (e.g.[plug: {Req.Test, MyMod}]) for testing stubs"wait_for_confirmation"— (optional) whenfalse, pull mode broadcasts without waiting for confirmation. Defaulttrue"max_compute_unit_limit"/"max_compute_unit_price"— (optional) fee-payer ceilings for compute-budget instructions"confidential"— (optional) enables the Token-2022 confidential profile"recipient_elgamal_secret_key"— (required for confidential) base64 canonical scalar for the recipient confidential token account"max_bundle_transactions"— (optional) confidential bundle bound; defaults to 8
Credential Payload
"type" => "transaction","transaction" => "<base64>"— signed legacy transaction bytes (max 1232 decoded)"type" => "signature","signature" => "<base58>"— confirmed transaction signature"type" => "bundle","transactions" => ["<base64>", ...]— ordered confidential transaction bundle
Currency
- Native SOL:
"sol" - SPL tokens: the base58 mint address
Dependencies
Uses Cartouche.Solana (RPC, legacy transaction codec, System/Token/ATA
programs) already in the on-chain stack.
API Functions
| Function | Arity | Description | Param Kinds |
|---|---|---|---|
challenge_method_details | 1 | Return Solana-specific fields (network, credentialTypes, feePayer, optional decimals/tokenProgram/splits) for the 402 challenge. | charge: value |
verify | 2 | Verify a Solana credential by checking on-chain settlement. | payload: value, charge: value |
validate_config! | 1 | Validate Solana method_config at init time. Raises on missing rpc_url or invalid fee-payer / splits config. | config: value |
credential_types | 0 | Return the Solana charge payload types: transaction, signature, and bundle. | - |
method_name | 0 | Return the payment method identifier for Solana. | - |
Summary
Functions
Return Solana-specific fields (network, credentialTypes, feePayer, optional decimals/tokenProgram/splits) for the 402 challenge.
Return the Solana charge payload types: transaction, signature, and bundle.
Return the payment method identifier for Solana.
Validate Solana method_config at init time. Raises on missing rpc_url or invalid fee-payer / splits config.
Verify a Solana credential by checking on-chain settlement.
Functions
@spec challenge_method_details(MPP.Method.intent()) :: map() | nil
@spec challenge_method_details(MPP.Intents.Charge.t()) :: map()
Return Solana-specific fields (network, credentialTypes, feePayer, optional decimals/tokenProgram/splits) for the 402 challenge.
Parameters
charge- Charge struct with method_details containing Solana method_config keys (value)
Returns
Public challenge methodDetails; never includes rpc_url, keys, or store (map)
# descripex:contract
%{
params: %{
charge: %{
description: "Charge struct with method_details containing Solana method_config keys",
kind: :value
}
},
returns: %{
type: :map,
description: "Public challenge methodDetails; never includes rpc_url, keys, or store"
}
}
Return the Solana charge payload types: transaction, signature, and bundle.
# descripex:contract
%{}
@spec method_name() :: String.t()
Return the payment method identifier for Solana.
# descripex:contract
%{}
Validate Solana method_config at init time. Raises on missing rpc_url or invalid fee-payer / splits config.
Parameters
config- method_config map to validate (value)
Returns
:ok on success, raises ArgumentError on missing keys (atom)
# descripex:contract
%{
params: %{
config: %{description: "method_config map to validate", kind: :value}
},
returns: %{
type: :atom,
description: "`:ok` on success, raises `ArgumentError` on missing keys"
}
}
@spec verify(map(), MPP.Intents.Charge.t()) :: {:ok, MPP.Receipt.t()} | {:error, MPP.Errors.t()}
Verify a Solana credential by checking on-chain settlement.
Parameters
payload- Credential payload map with"type"("transaction","signature", or"bundle") and the corresponding proof field (value)charge- Charge intent struct with amount, currency, recipient, and method_details (includingrpc_url) (value)
Returns
{:ok, receipt} on success, {:error, error} on failure (tagged_tuple)
Errors
:invalid_payload:verification_failed
# descripex:contract
%{
params: %{
payload: %{
description: "Credential payload map with `\"type\"` (`\"transaction\"`, `\"signature\"`, or `\"bundle\"`) and the corresponding proof field",
kind: :value
},
charge: %{
description: "Charge intent struct with amount, currency, recipient, and method_details (including `rpc_url`)",
kind: :value
}
},
errors: [:invalid_payload, :verification_failed],
returns: %{
type: :tagged_tuple,
description: "`{:ok, receipt}` on success, `{:error, error}` on failure"
}
}