Capxul Docs
Guides

Send money

Pay a typed recipient, manage Commitments, and read the payment ledger.

This guide covers paying a typed recipient, managing a Commitment, and reading payments.

Before you start

  • Auth and onboarding are done (Build the auth flow) and useCapxulIdentity() is authenticated with account.at === "claimed".
  • The account holds test funds (see test keys and the faucet).
  • Skim Money & accounts for the model: Money, Account, Payment, Commitment.
  • Check Capability status. Each payments claim uses the repository's fixed evidence state and names whether proof stops at a release-trunk contract, persistence, integration, or live execution boundary.

Amounts everywhere are a PaymentMoney: currency, decimal string value, and decimals — for example { currency: "USD", value: "25.00", decimals: 6 }. Never minor units, never a token amount. The current alpha validates payment amounts at six-decimal precision — pass decimals: 6; other values are rejected with INVALID_INPUT.

Pay a typed recipient

useCapxulPay sends money to a typed recipient reference — a handle, an email, an organization handle, or a saved payee id:

"use client";

import { useCapxulPay } from "@capxul/sdk-react";

export function PayButton() {
  const pay = useCapxulPay();

  const handlePay = async () => {
    const payment = await pay.mutateAsync({
      to: { kind: "email", email: "dana@example.com" },
      amount: { currency: "USD", value: "25.00", decimals: 6 },
    });
    // Check payment.status: an instant send to a validated recipient
    // settles; an unvalidated recipient escrows as "pending_claim".
  };

  return (
    <button type="button" onClick={() => void handlePay()} disabled={pay.isPending}>
      {pay.isPending ? "Sending…" : "Pay $25"}
    </button>
  );
}

The to reference is one of:

kindFieldsPays
"handle"handlea Capxul user by handle
"email"emailanyone by email — including people not on Capxul yet
"organization"handlean org by its handle
"payee"ida saved payee

Two constraints to design around (both by design, both graded on Capability status):

  • Bare addresses are rejected. pay never accepts a raw EVM address. That path fails with INVALID_RECIPIENT.
  • pay is personal-actor only today. Passing an organization actor fails with NOT_IMPLEMENTED — org treasury spending goes through the org surface, not payments.pay.

Optionally attach a paymentType ("invoice", "payroll", "reimbursement"), a payment document, or a timing clause. Omitting timing means an instant send; a scheduled or stream clause always creates a Commitment (below).

Unvalidated recipients become Commitments

One rule explains most "why is this payment not settled?" moments: an instant, irreversible send is only permitted to a validated recipient. Paying anyone not yet validated — a fresh email, an unclaimed handle — does not fail. It escrows the money in a recoverable Commitment instead:

  • The Payment comes back with status: "pending_claim".
  • The recipient claims it once they are on Capxul; availableToClaim on the payment tracks what they can take.
  • Until claimed, the sender can cancel (reclaiming the unvested remainder) or redirect it to another validated recipient.

The SDK enforces this; it is not a bug to work around. The full model is in Money & accounts.

List payments and read one

useCapxulPayments lists the payment ledger (most recent first); useCapxulPayment reads one by id with its live release state:

"use client";

import { useCapxulPayment, useCapxulPayments } from "@capxul/sdk-react";

function PaymentsList() {
  const payments = useCapxulPayments();

  if (payments.isLoading) return <p>Loading payments…</p>;
  if (payments.isError) return <p>{payments.error.message}</p>;

  return (
    <ul>
      {payments.data?.map((payment) => (
        <li key={payment.id}>
          {payment.recipient.label} · {payment.amount.value} {payment.amount.currency} ·{" "}
          {payment.status}
        </li>
      ))}
    </ul>
  );
}

function PaymentDetail({ paymentId }: { paymentId: string }) {
  const payment = useCapxulPayment(paymentId);
  if (payment.data == null) return null;
  return (
    <p>
      Released {payment.data.released.value} / {payment.data.amount.value} — claimable:{" "}
      {payment.data.availableToClaim.value}
    </p>
  );
}

A Payment is leak-safe by construction: recipient kind + label, amount, status (pending, settling, pending_claim, scheduled, streaming, settled, cancelled, failed), timing, released / availableToClaim, and document references. There is no transaction hash and no address on it.

Money mutations invalidate the payments list and detail automatically — see TanStack Query integration.

Cancel a payment

Commitments are cancellable by their creator before they are fully claimed: cancelling returns the unvested remainder to you, while anything already vested stays claimable by the recipient.

Use client.payments.cancel(paymentId) in the Core SDK. React clients can use useCapxulCancelPayment().

What you end up with

  • A pay flow that sends to handles, emails, orgs, and payees — and degrades safely to escrowed Commitments for recipients who are not validated yet.
  • A payments screen listing the ledger and showing per-payment release state.
  • Claim, cancel, and redirect controls for recoverable Commitments.

On this page