Payments
The value-transfer primitive. Send instant payments via the GraphQL gateway, poll the REST message-status endpoint until settled, query the unified activity feed.
The value-transfer primitive. A payment is what an activated
deal — or a one-off instant mutation — executes
when value needs to move between parties. The payment itself is
idempotency-keyed, on-chain, and confidential.
In production flows you rarely create a raw payment by hand — the DMS does it when a signed agreement compiles down to executable actions. The mutations below are exposed for the cases where you need them directly: one-off transfers, test flows, or applications that don't need the deal envelope around every movement.
One GraphQL gateway, one JWT. Post payment mutations and joined reads to
$YF_GATEWAY/graphql(api.yieldfabric.com/graphql). Status polling for async on-chain work uses$YF_PAYMENTS/api/users/.../messages/...over REST — that one's not GraphQL.
Send an instant payment
Transfer value to another party. The payment locks in the sender's account until the recipient accepts.
curl -X POST $YF_GATEWAY/graphql \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation($input: InstantSendInput!) { instant(input: $input) { success messageId paymentId } }",
"variables": { "input": {
"destinationWalletId": "550e8400-e29b-41d4-a716-446655440000",
"assetId": "aud-token-asset",
"amount": "10",
"idempotencyKey": "<fresh UUIDv4>"
}}
}'
Input shape
destinationWalletId/protected/jwt returns default_wallet_id for the bearer).destinationIdentitiesByName. ✗ never pass a raw 0x… here — that's an address, not a name.assetIdaud-token-asset).amount"10", not 10 and not "10.00". No decimal points.idempotencyKeywalletIddefault_wallet_id).requireManualSignaturetrue, the message stalls in ManualSignature state until the user signs from their wallet UI. Auto-true if the bearer's is_external_signer flag is set.contractIdIdempotency rules
400 IDEMPOTENCY_CONFLICT.Pitfall — never deterministic. If you compute idempotencyKey
as sha256(user_id || action), every retry after a stuck message
returns the stuck messageId again without re-enqueueing —
the executor never gets a fresh message to run. Either omit the field
or use a UUIDv4 per call.
Watch the payment settle
The mutation returns as soon as the GraphQL gateway accepts the message; actual on-chain settlement is asynchronous. The canonical pattern for backend callers (Python SDK, shell scripts, operator) is REST polling at 2-second intervals against the message-status endpoint.
USER_ID=$(curl -s $YF_AUTH/protected/jwt -H "Authorization: Bearer $TOKEN" | jq -r .user_id)
until curl -s -H "Authorization: Bearer $TOKEN" \
"$YF_PAYMENTS/api/users/$USER_ID/messages/$MESSAGE_ID" \
| jq -e '.executed' > /dev/null; do
sleep 2
done
curl -s -H "Authorization: Bearer $TOKEN" \
"$YF_PAYMENTS/api/users/$USER_ID/messages/$MESSAGE_ID" | jq
Status progression: Pending → Validating → Executing →
Completed (or Failed / ManualSignature). Terminal when
executed is non-null.
Browser clients can subscribe to the SSE stream
(/api/events/stream) or WebSocket (/ws/messages) for live UX
instead. Backend services use polling — simpler, no missed-event race,
no streaming state to manage.
Query a wallet's activity (history feed)
The modern read pattern. Unified, paginated, merges payments +
messages, newest first. Stateless — same (walletId, limit, cursor)
returns the same page on any replica.
curl -X POST $YF_GATEWAY/graphql \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "query Activity($walletId: String!, $limit: Int, $cursor: String) {
walletFlow {
activity(walletId: $walletId, limit: $limit, cursor: $cursor) {
nextCursor
items {
__typename
... on PaymentActivity { timestamp payment { id status amount assetId } }
... on MessageActivity { timestamp message { id messageType status executed } }
}
}
}
}",
"variables": { "walletId": "'$WALLET_ID'", "limit": 25 }
}'
Pagination contract:
cursoris opaque. Don't parse it. PassnextCursorfrom the previous page back as the nextcursor.nextCursor: nullmeans the feed is exhausted.limitdefaults to 25, clamped to 200.
Sorted timestamp DESC across both streams; ties break by id
lexicographically — stable but arbitrary across __typename.
Query a single wallet's payments
curl -X POST $YF_GATEWAY/graphql \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "query GetPayments($currentEntityId: ID) { paymentsByEntity(currentEntityId: $currentEntityId) { id amount assetId paymentType status dueDate description } }",
"variables": { "currentEntityId": "your-entity-id" }
}'
Payment lifecycle
instant mutation. A message is queued and messageId is returned immediately.ManualSignature for external-wallet users.locked_in.accept explicitly — the next outflow reconciles automatically.)executed field on the message becomes non-null; bitemporal audit row written.Common pitfalls
success: true but on-chain never settles"No entity found with name '0x…'"destinationWalletId with a UUID.403 Insufficient permissions: CryptoOperationsCompletedidempotencyKey retried with different body400 IDEMPOTENCY_CONFLICT. Generate a fresh UUIDv4 per logical submission./api/mq/submit from app code403 Insufficient permissions: CryptoOperations otherwise). Use the corresponding GraphQL gateway mutation instead.See also
- Building with YieldFabric §Send a payment — the same recipe with extra wire-level notes.
- Balances — query the result of payment activity.
- Authentication & signing — how the sender authorises the transfer.
- Contracts & Obligations — when payments are scheduled inside an obligation rather than one-off.
- Deal Management System (DMS) — when payments are one step in a larger signed agreement, the DMS owns the lifecycle around them.