Auto-accept incoming payments

Make incoming payments spendable without a manual accept. Arm a wallet so a background worker sweeps its incoming (one asset, a set, or all — present and future), or call acceptAll on demand. Covers the mint-key → setAcceptAutomation flow, status/revoke, group-wallet delegation, and the default-on UX pattern.

Guides/Integration

In YieldFabric's confidential model, money sent to a wallet doesn't become spendable on arrival — it lands as a pending (locked-in) payment that the recipient must accept. Until accepted it can't fund a send, settle a swap, or be disbursed.

Auto-accept removes that manual step: you arm a wallet once, and a background worker sweeps its incoming payments into spendable balance on a schedule — for one asset, a set of assets, or all assets (present and future). If you'd rather not run the background worker, you can accept on demand with a single call.

This guide is for developers building their own UX on top of the YieldFabric APIs. It covers both paths and the exact operations to call.

  • Auth + GraphQL setup: Building with YieldFabric
  • Conceptual background on locked-in vs spendable: Balances

The two paths

What it does
Auto-accept (background)
Arms a wallet so a worker accepts its incoming on a ~20s cadence
acceptAll (on demand)
Accepts a wallet's pending incoming right now, once
You call
Auto-accept (background)
setAcceptAutomation once
acceptAll (on demand)
acceptAll whenever you want
Best for
Auto-accept (background)
"Money should just land" UX; rent / payroll / any recurring inflow
acceptAll (on demand)
A button the user clicks; a server job you already run
Idempotent
Auto-accept (background)
Yes (nothing pending ⇒ no-op)
acceptAll (on demand)
Yes (nothing pending ⇒ no-op)

Both ultimately call the same accept logic. Auto-accept is just acceptAll on a timer, with a sealed credential so the worker can act as the wallet unattended.


Path A — accept on demand (acceptAll)

One mutation. Accepts every pending payable for a wallet + denomination that's unlocked (past its release time) and addressed to that wallet.

# POST pay.test.yieldfabric.com/graphql   (Authorization: Bearer <user JWT>)
mutation AcceptAll($input: AcceptAllInput!) {
  acceptAll(input: $input) {
    success
    message
    totalPayments
    acceptedCount
    failedCount
  }
}
// variables — accept ALL assets for the wallet
{ "input": { "walletId": "WLT-0x…", "denomination": "" } }

// variables — accept just one asset
{ "input": { "walletId": "WLT-0x…", "denomination": "aud-token-asset" } }

AcceptAllInput fields:

field
denomination
type
String!
notes
The asset id to accept. Empty string "" = all assets (wildcard).
field
walletId
type
String
notes
Scope acceptance to this wallet and sign as it. Omit to use the JWT's default wallet.
field
obligor
type
String
notes
Optional payer filter. Empty/omitted = any payer.
field
idempotencyKey
type
String
notes
Optional dedupe key.

The caller must be able to act for walletId (it's one of your own wallets, or a group you control — see Group wallets). acceptAll is safe to call repeatedly: once a payment is accepted it's no longer pending, so a re-run accepts nothing.


Path B — arm auto-accept (background)

Arming is a two-step flow: mint a revocable API key, then register it against the wallet. The worker uses that key to act as the wallet unattended.

Step 1 — mint a key

# POST auth.yieldfabric.com/auth/api-key/generate   (Authorization: Bearer <user JWT>)
curl -X POST https://auth.yieldfabric.com/auth/api-key/generate \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{ "service_name": "auto-accept-WLT-0x…", "description": "Auto-accept incoming" }'

# → { "id": "<key-uuid>", "api_key": "yf_api_…", "service_name": "…", "created_at": "…" }

The plaintext api_key is returned once — you pass it straight into Step 2, which seals it server-side. Keep the id: it's what later revokes the key.

Step 2 — register it against the wallet

# POST pay.test.yieldfabric.com/graphql   (Authorization: Bearer <user JWT>)
mutation SetAcceptAutomation($input: SetAcceptAutomationInput!) {
  setAcceptAutomation(input: $input) {
    active
    walletId
    entityId
    groupId
    keyLabel
    denominations
    createdAt
  }
}
// variables — auto-accept ALL assets (recommended default)
{
  "input": {
    "walletId": "WLT-0x…",
    "apiKey": "yf_api_…",     // from Step 1
    "keyId": "<key-uuid>",    // from Step 1 — enables full revoke
    "keyLabel": "…last4",     // optional, for your UI
    "denominations": []        // [] = all assets, present and future
  }
}

// variables — auto-accept specific assets only
{
  "input": {
    "walletId": "WLT-0x…",
    "apiKey": "yf_api_…",
    "keyId": "<key-uuid>",
    "denominations": ["aud-token-asset", "usd-token-asset"]
  }
}

SetAcceptAutomationInput fields:

field
walletId
type
String!
notes
The wallet to auto-accept for. One credential per wallet (re-arming replaces it).
field
apiKey
type
String!
notes
The yf_api_… from Step 1. Stored sealed; never returned.
field
keyId
type
String
notes
The key's id from Step 1. Pass it so stopping also revokes the key at auth.
field
keyLabel
type
String
notes
Non-secret label (e.g. last 4 chars) for your UI.
field
groupId
type
String
notes
Set when walletId is a group account (see below).
field
denominations
type
[String!]!
notes
[] = all (present + future). A non-empty list limits to those asset ids.

From this point the worker accepts the wallet's incoming automatically. There's nothing else to call.

Check status

# POST pay.test.yieldfabric.com/graphql
query AcceptAutomationStatus($walletId: String!) {
  dealFlow {
    acceptAutomationStatus(walletId: $walletId) {
      active          # true when armed
      walletId
      groupId
      keyLabel
      denominations   # [] means "all"
      createdAt
    }
  }
}

Returns null when the wallet has never been configured, an object with active: true when armed, and active: false when it was armed then stopped. (The mutations above are also under the dealFlow namespace — shown without it for brevity; nest them the same way if your client requires it.)

Stop

# POST pay.test.yieldfabric.com/graphql
mutation RevokeAcceptAutomation($input: RevokeAcceptAutomationInput!) {
  revokeAcceptAutomation(input: $input) {
    active            # false after revoke
    walletId
  }
}
{ "input": { "walletId": "WLT-0x…" } }

Stopping revokes the credential and kills the underlying yf_api_… key at auth (using the keyId you stored at arm time) — so a leaked key is dead, not merely unreferenced.


Denominations: all vs specific

  • denominations: [] (all) is the recommended default. It's a true wildcard — the worker accepts every incoming asset, including assets that don't exist yet at arm time. Use this when you want "any money sent here just lands."
  • denominations: ["asset-a", "asset-b"] limits auto-accept to those assets. Anything else stays pending until accepted some other way.

Internally, the "all" mode issues a single acceptAll with an empty denomination per tick; a specific list issues one acceptAll per asset.


Group wallets

A wallet owned by a group (e.g. a shared property or treasury account) needs delegation: the worker has to sign as the group, not as the arming user. Pass groupId when arming:

{
  "input": {
    "walletId": "WLT-0x…group-account…",
    "groupId":  "<group-entity-id>",
    "apiKey": "yf_api_…",
    "keyId": "<key-uuid>",
    "denominations": []
  }
}

The arming user must be the group's owner — the worker mints a group delegation token each tick, and that mint is the authoritative ownership check. A non-owner's auto-accept simply never fires (and is logged), so there's no silent wrong-account behavior.

For a directly-owned (personal) wallet, omit groupId.


Making it default-on in your UX

Auto-accept is off until armed — there's no global flag, because each wallet needs its own sealed key minted in the user's session. To make it feel "on by default," arm lazily when the user first opens a wallet:

  1. Query acceptAutomationStatus(walletId).
  2. If it returns null (never configured) → mint a key + setAcceptAutomation with denominations: [].
  3. If it returns an object → do nothing. An active: false row means the user explicitly stopped it; don't re-arm it (don't fight an explicit choice).

The null check is the durable, server-side guard: once a wallet is configured the row exists, so re-opening never re-arms. For group wallets, wait until you know the wallet is a group (and have its groupId) before arming, so you arm with delegation rather than as a plain wallet. Guard the in-flight window with a per-wallet "already attempted this session" flag so a fast re-open can't double-mint.

Trade-off: arming mints one yf_api_… key per wallet the first time it's opened. Surface those keys somewhere the user can review and revoke them.


How the worker behaves

  • Cadence. It scans armed wallets roughly every 20 seconds. Acceptance is eventually-consistent, not instantaneous — a payment that lands between ticks is swept on the next one.
  • Idempotency. Each tick calls acceptAll, which only touches pending payments. Already-accepted money is skipped, so re-running is always a no-op. There's no double-accept.
  • Unlocked only. Payments with a future release time aren't accepted until they unlock — auto-accept respects the schedule, it doesn't bypass it.
  • Key lifecycle. The worker exchanges the sealed key for a short-lived token each cycle (cached briefly). The key is the user's own, revocable at any time; revoking it (via revokeAcceptAutomation, or directly at auth) stops the sweeps immediately.

Security notes

  • The credential is the user's own revocable API key, sealed at rest. The worker holds no standing secret of its own — only what the user armed.
  • Acceptance signs as the wallet (or, for a group wallet, as the group via delegation). The worker can't accept for a wallet the user can't act for.
  • Accepting incoming money has no downside — it's funds owed to the wallet — so auto-accept needs no approval step or amount bound. (Disbursing out of a wallet is a different, policy-gated flow.)
  • Stopping is a true kill: it revokes the local credential and the key at auth.