> For the complete index of the 402pay docs, see [llms.txt](https://developer.402pay.co/llms.txt).

# Solana checkout addresses

How each Solana checkout gets a fresh address from a public key, the derivation to reproduce it, and moving the funds with only the recovery phrase.

Every Solana checkout pays to a fresh address that no other payment uses. 402pay derives these addresses from a public key your browser shares, as EVM, Tron and Bitcoin checkouts do from an account's extended public key, so it never holds a key that can move your funds. This page publishes the derivation, so anyone can reproduce it.

## What they are

When you create or import a wallet, your browser derives a Solana checkout key from the recovery phrase and shares only its public half: a public key and a chain code. 402pay derives checkout address `n` from it for checkout `n`. Only the recovery phrase makes the private keys behind these addresses, so only you can move what they hold. The checkout key can't spend anything, but it links every checkout address to your wallet, as any extended public key does.

## Where funds show

- The main address, `m/44'/501'/0'/0'`, is the one Solana wallet apps show when you restore your recovery phrase, and the one Receive shows in the dashboard.
- Checkout addresses sit on a branch of their own, `m/402'/501'/0'`, whose children use public (soft) derivation. Wallet apps don't scan it, so they don't show these funds.
- Move them with Send in the dashboard, which signs for checkout addresses in your browser, or with nothing but your recovery phrase on the [Solana recovery page](https://dash.402pay.co/recover/solana).

> Locked out of the dashboard, or 402pay unreachable? The [recovery page](https://dash.402pay.co/recover/solana) finds your main and checkout addresses from the phrase, reads their balances from Solana's public RPC and moves everything to an address you choose. The phrase stays in your browser, and the page makes no request to 402pay.

## The derivation

402pay Solana checkout keys, version 1, is BIP32-Ed25519 (Khovratovich and Law, the V2 scheme of Cardano's ed25519-bip32) from a SLIP-0010 root. Below, `B` is the Ed25519 base point, `l` its group order, `2^252 + 27742317777372353535851937790883648493`, numbers are read and written little-endian, `x[a..b]` is bytes `a` up to `b`, and `||` joins bytes.

1. `seed` is the BIP39 seed of the recovery phrase, its words in lowercase and one space apart, with no passphrase.
1. SLIP-0010 derivation for Ed25519 of `seed` along `m/402'/501'/0'`, all three steps hardened, gives a 32-byte key `k` and a 32-byte chain code `c`. Purpose 402' keeps the branch apart from the `m/44'/501'` accounts wallet apps show.
1. `h = SHA-512(k)`, `kL = h[0..32]` and `kR = h[32..64]`. Clamp `kL`: `kL[0] &= 0xF8`, `kL[31] &= 0x1F`, `kL[31] |= 0x40`. The root public key is `A = kL * B`.
1. The checkout key your browser shares is `base58(A || c)`: 64 bytes in Bitcoin's base58 alphabet, with no checksum. A valid one decodes to exactly 64 bytes, is at most 88 characters with no whitespace, and its `A` is a canonical encoding of a point in the prime-order subgroup that isn't of small order, what libsodium's `crypto_core_ed25519_is_valid_point` accepts.
1. Child `i`, for `0 <= i < 2^31`: `Z = HMAC-SHA512(key = c, data = 0x02 || A || le32(i))`, `ZL = Z[0..28]` and `ZR = Z[32..64]`. Its public key is `A_i = A + (8 * ZL) * B`, and checkout address `i` is `base58(A_i)`. Its private key, which only the phrase can make, is `kL_i = kL + 8 * ZL` (a 256-bit addition, not reduced) and `kR_i = (kR + ZR) mod 2^256`, and `kL_i * B` must equal `A_i`.
1. To sign message `M` with child `i`: `r = SHA-512(kR_i || M) mod l`, `R = r * B`, `x = SHA-512(R || A_i || M) mod l` and `S = (r + x * (kL_i mod l)) mod l`. The signature is `R || S`, and it verifies under standard Ed25519 verification (RFC 8032), strict checks included.

## Indexes

- Solana index 0 is the main address's slot, so child 0 of the checkout key is never handed out.
- Checkout `n`, from 1 up, pays to child `n`. Indexes count up per wallet and never go back, so an address never serves two payments. Card payments that settle on Solana take one too.
- To find funds, scan from child 0. [`address_counts.solana`](https://developer.402pay.co/api/wallet/retrieve.md) on `GET /wallet` is one more than the highest Solana index handed out, so it says how far to look.
- A child whose public key would be the identity point is skipped and never handed out. The chance of one is about 2^-250.

## Test vectors

From the published BIP39 test phrase, which is never a wallet to send funds to. Independent implementations give the same values. The message to sign is hex, the bytes of "402pay".

Test vectors, JSON:

```json
{
  "phrase": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
  "main_address": "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk",
  "checkout_key": "sHz8kwCJhANstUZXuSv3SDDFbcVmqpH2ojjvY1VNvCPSTcqm9rx2r65ECEryDo3oxWpZCv9mMmi4twijuvbf7F7",
  "children": [
    { "index": 0, "address": "FFWTFNVcavC2RVTDsjNHfJwPAZuSaKhvABNk7goBWkTX" },
    { "index": 1, "address": "EgsxatR8QNRxCBkf6ZBf3Z9uoaeN23yBhFWn2sk91h3E" },
    { "index": 2, "address": "ERRm1NNCfSnBaf9YzrSJwKVDYR5LfREkaRarxGWbyJpf" },
    { "index": 2147483647, "address": "7FMbTxB3Ke1NFknB1SCZJ2crt4pwmSTz3WNPSmzhtqB9" }
  ],
  "signature": {
    "index": 1000,
    "address": "HP3xenGuaNHgbeLhrZ4W7th5nqxCxwscYNHLi2KtSU5p",
    "message": "343032706179",
    "signature": "1095be7d64ba627ddd82081fc35a7f76e00a64a289656949ea2e0db101905f6f40cc8fb2ea410ae779bf8a90e67673bb5e81b18ecab3f277b876da13ba796d06"
  }
}
```

## Recover with your own code

You don't need 402pay's code to move these funds. Anything that follows the steps above finds the same addresses and makes the same keys: any Ed25519 library that signs with an expanded key (`kL`, `kR`) can sign for a checkout address, and BIP32-Ed25519 libraries, such as Cardano's ed25519-bip32, derive the same children from the root key (`kL`, `kR`, `c`). The signature is a standard Ed25519 signature, which Solana validators accept like any other, so a transfer from a checkout address is an ordinary Solana transaction.

1. Derive the main address and the checkout key from your recovery phrase.
1. Derive checkout addresses from child 0 up, and read each one's SOL and its USDC and USDT associated token accounts from any Solana RPC.
1. For each funded address, sign a transaction with its child key that moves its tokens and SOL where you choose, with an address that holds SOL, such as the main address, paying the fee.

## Next steps

- [Wallet and deposits](https://developer.402pay.co/guides/settlement.md): The three kinds of wallet, how each payment lands in yours, and who holds the keys.
- [Security best practices](https://developer.402pay.co/guides/security.md): Narrow keys, rotations without downtime, a locked-down webhook endpoint and a secure account.
- [Supported networks](https://developer.402pay.co/guides/networks.md): Every coin and network checkout accepts, and the confirmations each one needs.
