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 withaccount.at === "claimed". - The account holds test funds (see test keys and the faucet).
- Skim Money & accounts for the model: AssetAmount, 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.
Payment amounts are AssetAmount values. They contain an exact asset and a
decimal value. They do not contain raw token units or fiat currency identity.
The asset registry supplies precision. It supports six-decimal USDC and USDT
as separate assets, and 18-decimal WETH.
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: {
asset: "eip155:84532/erc20:0x06ba8b863ca77490b0e7008427562bdbfcff730e",
value: "25.00",
},
});
// 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:
kind | Fields | Pays |
|---|---|---|
"handle" | handle | a Capxul user by handle |
"email" | email | anyone by email — including people not on Capxul yet |
"organization" | handle | an org by its handle |
"payee" | id | a saved payee |
Two constraints to design around (both by design, both graded on Capability status):
- Bare addresses are rejected.
paynever accepts a raw EVM address. That path fails withINVALID_RECIPIENT. payis personal-actor only today. Passing an organizationactorfails withNOT_IMPLEMENTED— org treasury spending goes through the org surface, notpayments.pay.
Optionally attach a paymentType ("invoice", "payroll",
"reimbursement") or a payment document. A payment pays at once: held
timing (a scheduled or stream clause) returns INVALID_INPUT on timing.
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
Paymentcomes back withstatus: "pending_claim". - The recipient claims it once they are on Capxul;
availableToClaimon the payment tracks what they can take.
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>Could not load payments ({payments.error.code}).</p>;
return (
<ul>
{payments.data?.map((payment) => (
<li key={payment.id}>
{payment.recipient.label} · {payment.amount.value} · {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.
Payment mutations invalidate the payments list and detail automatically. See TanStack Query integration.
Held payments
A payment pays at once. Held payments (scheduled releases, vesting and claims)
retired with the move to the V3 payment router, so the Core SDK has no
createCommitment, claim, cancel or redirect.
What you end up with
- A pay flow that sends to handles, emails, orgs, and payees.
- A payments screen listing the ledger and showing each payment's state.
Work with organizations
List the orgs a user belongs to, create one, switch the acting entity, read treasury/members/roles, and invite a member with a role.
TanStack Query integration
How the SDK rides TanStack Query — the provider-owned QueryClient, the key namespace, automatic invalidation, manual invalidation, and what clears on sign-out and re-bootstrap.