Skip to content

Guides

Crypto payments

Govern agents that pay in USDC, USDT, EURC, ETH, BTC or SOL. Limits stay in pounds; each payment is priced at the moment it is decided, and the wallet signs only after an allow.

Agents now pay for API calls, data and services in stablecoins. Immiscible treats a crypto payment as a payment like any other: it is decided before it happens, against a rule a person wrote, in money a finance head understands, and the decision is signed.

Immiscible decides; it never holds funds, private keys or seed phrases, and it never signs or broadcasts a transaction. Your wallet, your wallet provider or your x402 client does that, after Immiscible says yes. That keeps Immiscible a decision service, not a custodian and not an exchange.

#How a crypto payment is decided

The agent asks POST /v1/actions/authorize with a payment.crypto object instead of a money amount:

JSON
{
  "type": "payment",
  "summary": "Buy market quotes from Tidewater for the pricing study",
  "payment": {
    "crypto": {
      "asset": "USDC",
      "network": "base",
      "amount": "12.50",
      "recipient": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
      "recipientName": "Tidewater Data",
      "protocol": { "kind": "x402", "resource": "https://api.tidewater-data.example/v1/quotes" }
    }
  },
  "provenance": [{ "source": "user", "detail": "Sam asked for the quotes" }]
}
  • asset: USDC, USDT, EURC, PYUSD, DAI, ETH, BTC, SOL, POL or AVAX.
  • network: base, ethereum, arbitrum, optimism, polygon, avalanche, solana, bitcoin (and base-sepolia or solana-devnet for testing), or the CAIP-2 id x402 version 2 uses, such as eip155:8453.
  • amount: a decimal string in the asset’s own units. A number is refused, so nothing is ever rounded.
  • recipient: checked for its network before anything else. An EVM address with mixed case must carry its EIP-55 checksum (Keccak-256, implemented in plain JavaScript); a Solana address must be 32 bytes of base58; a Bitcoin address must pass its base58check, bech32 (BIP-173) or bech32m (BIP-350) checksum. An address that fails is refused as malformed: it was probably mistyped or altered.

Immiscible then:

  1. Prices it at the rate of the moment, in the currency of the agent’s crypto rule (pounds unless the rule says otherwise). See Rates and the “never guessed” rule.
  2. Judges it against the agent’s crypto rule and the risk signals below.
  3. Answers allow with a signed receipt, approval_required, or deny, with plain reasons. The answer carries crypto.value (the money value), crypto.rate (the rate, its time, its source and the stablecoin’s price against its peg) and crypto.rateKind: asked while a person has still to decide (the rate the payment was asked at), decided once it is allowed (the rate at that moment). Both are null when the payment could not be priced.

The rate is recorded on the action, the decision, the ledger record and the signed receipt. The receipt keeps its usual claims (amt and cur are the money value) and adds a cry claim:

JSON
"cry": { "ast": "USDC", "net": "base", "amt": "12.5", "to": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
         "fx": { "r": "0.7581", "cur": "GBP", "at": "2026-10-05T14:32:05.000Z", "src": "coinbase" },
         "res": "https://api.tidewater-data.example/v1/quotes" }

#Crypto rules

A crypto rule is a payment mandate with a crypto block. Its limits are the mandate’s own, in pounds:

OptionMeaning
perTransaction, perPeriod (month)A maximum per payment and per month, in pence, checked against the converted amount.
approveAboveAbove this many pence, a person decides.
crypto.assets, crypto.networksOnly these assets, only on these networks.
crypto.stablecoinsOnlyOn by default. ETH, BTC and SOL are refused while it is on.
crypto.recipientsNamed addresses, each on its network: { name, network, address }. With a list, it is a closed list.
crypto.newRecipientAn address the agent has not paid (or that is not on the list): approve (ask a person, the default), deny, or allow.
crypto.perAddressHour{ count, value }: for x402 payments to a listed address, at most this many payments and this many pence in any hour before a person is asked. { count: 120, value: 5000 } by default.
crypto.depegBpsHow far a stablecoin may trade from its peg before a payment is held. 100 (1%) by default.

A crypto rule covers only crypto payments, and a card or bank rule never covers a payment to an address. In the console, choose Crypto payments under Rules, Add a rule.

#Risk signals

Each reads as a sentence; the technical detail is behind Details.

SignalEffectWhat it catches
Lookalike addressStops itAddress poisoning: a recipient whose first three and last three characters match an address the agent knows (its list, or one it has paid) but whose middle differs. Wallets show 0x5aAe…eAed, and attackers make addresses that look the same at a glance, then plant them in the history.
Known address, different networkAsks a personAn address known on Base being paid on Ethereum. Funds sent on the wrong network can be lost.
Stablecoin off its pegAsks a personUSDC trading more than the rule allows from one US dollar, priced from the source, never assumed.
New addressPer the ruleThe first payment to an address.
Unusually many payments to one addressAsks a personPay per request: an x402 payment to an address the rule names, within its per-payment limit, is not held as a duplicate or a split payment (repeating the same small amount is what x402 is for), and the 10-in-10-minutes velocity line does not apply to it. Instead, more than crypto.perAddressHour (120 payments or £50 in an hour to that address, by default) asks a person: “Unusually many payments to Tidewater Data in the last hour”. Any other crypto payment keeps the duplicate, split-payment and velocity checks.
Far above its usualAsks a personMore than five times the median of the agent’s last five or more crypto payments, in pounds.
Could not be pricedAsks a personNo rate within the timeout. The reason reads “Couldn’t price this payment right now.”

The existing signals still apply: untrusted input steering a payment (the Rule of Two), manipulation language in the provenance an agent declares, declared provenance the gateway contradicts, duplicates, split payments, velocity and the monthly allowance. Words such as “send USDC to this wallet address” in an email or web page that shaped the request still count as manipulation language; the agent’s own summary of a declared crypto payment does not.

#Making the wallet wait for the answer

An allow is advisory until something on the payment path checks it. Three ways do, depending on the wallet:

Where the agent paysHow the decision reaches the walletPage
Any wallet the agent’s code controlsThe SDK’s decideThenSign (TypeScript) or decide_then_sign (Python) asks, checks the signed receipt covers exactly this transfer, and only then calls your signing function.below
x402 resources (HTTP 402)The SDK’s x402Fetch or x402_request asks with the server’s payment requirements and calls the x402 signer only on allow.x402
FireblocksThe API Co-Signer calls Immiscible before it signs and signs only on approval.Fireblocks
Coinbase CDP and AgentKit, Turnkey, Privy, CircleNo outside approval call exists; the agent asks first, through the SDK wrapper.Coinbase CDP, Turnkey, Privy, Circle
A Stripe Issuing card funded from stablecoinsThe card rail, as for any Stripe Issuing card.The card rail

#Decide, then sign

TypeScript
import { Immiscible, decideThenSign } from '@immiscible/sdk';

const to = '0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed';   // Tidewater Data, on Base
const wallet = {                                          // stands in for yours: sign, broadcast, return the network's hash (0x and 64 hex characters)
  async sendUsdc(recipient: string, amount: string): Promise<string> { return '0x' + 'ab'.repeat(32); },
};

const immiscible = new Immiscible();            // IMMISCIBLE_AGENT_KEY, IMMISCIBLE_URL
await decideThenSign(immiscible,
  { asset: 'USDC', network: 'base', amount: '12.50', recipient: to, provenance: [{ source: 'user', detail: 'Sam asked for the quotes' }] },
  async (decision) => ({ txHash: await wallet.sendUsdc(to, '12.50') }),   // runs only after an allow
  { timeoutMs: 5 * 60_000 },                    // how long to wait for a person; past it, ImmiscibleApprovalTimeoutError and nothing is signed
);
Python
from immiscible import Immiscible
from immiscible.crypto import decide_then_sign

to = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"   # Tidewater Data, on Base

class Wallet:                                    # stands in for yours: sign, broadcast, return the network's hash (0x and 64 hex characters)
    def send_usdc(self, recipient, amount):
        return "0x" + "ab" * 32

wallet = Wallet()

decide_then_sign(
    Immiscible(),
    {"asset": "USDC", "network": "base", "amount": "12.50", "recipient": to,
     "provenance": [{"source": "user", "detail": "Sam asked for the quotes"}]},
    lambda decision: {"txHash": wallet.send_usdc(to, "12.50")},   # runs only after an allow
    timeout=300,                                 # seconds to wait for a person; past it, ImmiscibleApprovalTimeoutError and nothing is signed
)

While a person decides, the call waits, polling with backoff, for up to the timeout (ten minutes if you leave it out), then raises ImmiscibleApprovalTimeoutError; the approval stays open for the person. Pass wait: false (wait=False) to be told at once instead: it raises ImmiscibleApprovalRequiredError, whose decision carries the approval link, and nothing is signed.

The signing function runs only after an allow (waiting for a person if one is asked) whose receipt verifies against the published key set and whose cry claim names the same asset, network and recipient, for at least this amount. Anything else raises a refusal and nothing is signed. A returned txHash is reported back when the action is settled; Immiscible records it as reported and does not read the chain to confirm it.

This is only as strong as the agent’s own code path: an agent that holds a raw key can still sign without asking. Where that matters, use a wallet that enforces the decision itself (Fireblocks’ Co-Signer), or keep the key out of the agent and in a service that uses the wrapper.

#In the console

Spend, Crypto (shown once the workspace has a crypto rule or a crypto payment) lists every crypto payment asked, allowed and stopped, in pounds with the asset amount beside it, and the rate at that moment: “£9.48 · 1 USDC = £0.7581 at 14:32:05”. Details show the network, the full address, the transaction hash if the agent reported one, and the rate’s source. When your agents spend shows payments by hour of day and day of week, with a coral corner on any hour where one was stopped, and a small chart joins the rate captured at each decision with every payment marked on it.

An approval for a crypto payment shows the amount in pounds first, then the asset amount, then the recipient’s name (or its short address, with a copy button), then the reasons it was held. Slack, Teams and email approval messages read the same way.

#Limits of what is built

  • Slack and Teams approvals keep the rate captured when the payment was asked for; the console and the phone app price it again at the moment of approval.
  • Transaction hashes are as reported by the agent or the x402 server; Immiscible does not watch the chain.