# Payouts

Payouts send money from a project's hot wallet: refunds, supplier payments, withdrawals to an exchange. They
work on the EVM networks and Solana. Bitcoin has no hot wallet in Coldtill, so Bitcoin payouts aren't supported.
Tron payouts aren't supported yet; the Tron hot wallet only pays for sweeps.

## Setup

1. **Hot wallet** (global admin): under **Hot wallets**, generate or import an EVM key and a Solana key, then
   assign them to the project. Assigning needs a two-factor code.
2. **Fund it:** send the hot wallet enough native coin for gas, plus the tokens you plan to pay out. Keep the
   balance small; it is the only money a server compromise can reach.
3. **Limits** under **Project settings → Payout limits** (project admin, with two-factor):

| Setting | Effect |
|---|---|
| Minimum payout (USD) | Smaller payouts are refused |
| Auto-approve up to (USD) | Larger payouts wait for an admin. `0` holds every payout |
| Hourly limit, daily limit (USD) | Payouts that would take the project's total over the limit wait for an admin. `0` means no limit |

Limits are counted per project, across every user and API key, including payouts still in flight.

4. **Address book:** add destinations under **Payouts → Address book** (project admin, with two-factor).
   API payouts can only go to address-book entries, and entries can only be added in the dashboard.

## Creating a payout

- **Dashboard:** **Payouts → New payout** (project admin, with two-factor). Any valid address is allowed, but
  limits still apply.
- **API:** `POST /api/v1/withdrawal/merchant` with an `Idempotency-Key`. See [Merchant API](https://coldtill.com/docs/api.md#payouts).

Coldtill validates the destination for the chain. EVM addresses with a wrong mixed-case checksum are refused.
On Solana, sending to a token account (rather than a wallet) is refused, as are SOL transfers that would
leave a new account below rent exemption.

## Approval

A held payout shows the reason (for example "above the auto-approve amount"). To approve:

1. A project admin clicks **Approve** and enters a two-factor code.
2. Coldtill emails a six-digit code to that admin. It is valid for 10 minutes and allows five attempts.
3. The same admin enters the emailed code. Only the admin who started the approval can finish it.

Either step can be abandoned. **Reject** ends the payout. A payout that hasn't been signed yet can be cancelled.

## Statuses

| Status | Meaning |
|---|---|
| `pending-approval` | Held by a limit or the auto-approve amount |
| `pending-otp-verification` | Approval started; waiting for the emailed code |
| `pending` | Approved, queued for signing |
| `signing` | Signed and stored; being broadcast |
| `initiated` | Broadcast; waiting for confirmations |
| `sent` | Confirmed on-chain |
| `failed` | Definitely did not happen (reverted, or refused and unknown to every node) |
| `rejected` | Rejected by an admin |
| `cancelled` | Cancelled before signing |
| `needs-review` | Coldtill can't tell whether it landed. See below |

## How sending works

1. The `payouts` process claims a pending payout and reserves a nonce (EVM) or a recent blockhash (Solana).
2. It signs the transaction and **stores it before broadcasting**, so a crash can never lead to a second,
   different transaction for the same payout.
3. It broadcasts and tracks the transaction:
   - **EVM:** a stuck transaction is re-signed with the same nonce, recipient and amount and doubled fees.
     Only one version can ever be mined.
   - **Solana:** if the blockhash expires before the transaction lands, Coldtill asks **every** node, with full
     history search, whether it ever landed. It re-signs only if none has seen it.
4. After the network's confirmations it becomes `sent` and a `payout.sent` webhook goes out.

If a node refuses a broadcast outright (for example "insufficient funds"), the payout isn't failed straight
away: with failover, an earlier node may have accepted it. Coldtill waits two minutes and fails it only if
the transaction is still unknown.

## Needs review

`needs-review` means the hot wallet's nonce was used, but Coldtill can't match the transaction that used it to
this payout, even after 30 minutes. That can happen if someone used the hot wallet outside Coldtill, or if
your nodes are badly out of sync. Coldtill won't guess, because a payout wrongly marked `failed` invites a retry
that pays twice.

To resolve one:

1. Open the hot wallet's address in a block explorer and look for a transfer of that amount to that address.
2. In **Payouts**, click **Resolve**, choose **It was sent** or **It was not sent**, add a note (for
   example the explorer link) and confirm with two-factor.

The decision is recorded in the audit log and sends a final `payout.sent` or `payout.failed` webhook.

## Good practice

- Keep the hot float to about a week of gas plus expected payouts. Top it up from your cold wallet.
- Use a low auto-approve amount and tight daily limits; raise them only as volume requires.
- Give `project_admin` only to people who should approve payouts. Admins can approve payouts they created
  themselves, which single-admin installs need. If you have several admins, agree that someone else approves.
- Don't use the hot wallet from other software. Coldtill expects to be the only thing spending from it.

---

Source: https://coldtill.com/docs/payouts/ · All Coldtill docs: https://coldtill.com/llms.txt
