Core SDKHow-to guides

Create a payment

Create a personal-Account payment for a validated recipient and understand its current completion boundary.

client.payments.pay accepts an AssetAmount and a validated recipient. The personal Account path prepares one stored UserOperation, validates the sender, asks the configured wallet to sign one digest, and submits through the backend. A successful result confirms that the backend recorded the submitted hash. It does not claim final chain settlement.

Limited availability

Payment execution requires an environment with active CapxulPaymentsV2 writes. Use client.org(orgId).payments for Organization payments. Check capability status before treating a submitted Payment as settled.

Before you start

Create a client with the Account readiness your application requires, then authenticate the user. This guide assumes you already have an initialized client; see Choose Account readiness if you are unsure which requirement to use.

Create the payment

Select an admitted asset. Then parse form text against its registry metadata:

import { formatAssetAmount, parseAssetAmount, requireAsset, TOKENS } from "@capxul/sdk";

const selectedAsset = TOKENS.BASE_SEPOLIA.CAPXUL_TEST_USDC;
const amount = parseAssetAmount(formAmount, requireAsset(selectedAsset));
if ("kind" in amount) {
  showFieldError(copyFor(amount.reason)); // app-owned lookup (ADR-0023)
  return;
}

console.log(formatAssetAmount(amount));

parseAssetAmount uses the selected asset precision. It returns a validation error value for missing, malformed, zero, negative, oversized, or over-precise input.

formatAssetAmount resolves display metadata from the exact AssetId. An unknown AssetId fails with INVALID_INPUT.

TOKENS.BASE_SEPOLIA also contains CAPXUL_TEST_USDT and CAPXUL_TEST_WETH. The keys select exact assets. They do not select by symbol or fiat peg.

PAYMENT_STATUSES and PAYMENT_DIRECTIONS are the closed code vocabularies themselves. Render a dropdown from them and keep the wording in the application; the SDK ships no label for a code.

Pass the parsed AssetAmount and a validated recipient:

const paid = await client.payments.pay({
  to: { kind: "email", email: "vendor@example.com" },
  amount,
  paymentType: "invoice",
});

if (!paid.ok) {
  console.error(paid.error.code, paid.error.message);
  return;
}

console.log(paid.value.id, paid.value.status);

The personal path accepts handles, emails, Organizations, and saved payees—not a raw wallet address. The paymentType value records the business purpose of the payment.

Interpret the result

paid.ok means the SDK accepted the request and returned a payment record. Read the record's status; do not reinterpret the presence of an ID as final settlement.

Read the latest record when your UI needs to refresh its state:

const payment = await client.payments.get(paid.value.id);
if (!payment.ok) throw payment.error;

console.log(payment.value.status);

Know the unavailable paths

  • An Organization actor passed to payments.pay returns NOT_IMPLEMENTED.
  • A destination target passed to payments.pay returns NOT_IMPLEMENTED.
  • A payment pays at once: held timing returns INVALID_INPUT on timing, and there is no cancel, claim or redirect.

See capability status for the current availability summary and the method reference for exact evidence.

On this page