Raxol.Payments.Actions.SpendGate (Raxol Payments v0.2.0)

Copy Markdown View Source

Single spending choke point for payment Actions.

Every agent Action that releases a wallet signature for real funds calls authorize/3 first and proceeds only on :ok. This routes the request through the non-budget policy gate (Raxol.Payments.PolicyGate) and the atomic ledger reservation (Raxol.Payments.Ledger.try_spend/5) before any wallet.sign_* call.

Targets

  • {:domain, host} -- HTTP/host-routed payment (x402, Xochi intent endpoint). The host is matched against policy.approved_domains and the confirmation threshold is applied.
  • {:address, addr} -- a direct on-chain transfer to an address. There is no host to approve, so only the confirmation threshold applies.

Context keys

  • :policy -- a SpendingPolicy (nil means unrestricted: the gate is a no-op).
  • :ledger -- a Ledger server (nil disables budget reservation).
  • :agent_id -- ledger key (default :unknown).
  • :on_confirm -- (Decimal.t(), String.t() -> :approve | :deny) consulted when the amount exceeds policy.require_confirmation_above.

Failed downstream execution

authorize/3 reserves budget atomically. If the signing/execution step that follows fails, call release/2 (or release/3) to refund the reservation so a failed payment does not permanently consume the agent's budget.

Summary

Functions

Authorize a spend before signing. Returns :ok or {:error, reason}.

Drop the reservation tag for intent_id without releasing budget (the spend stands; the intent settled or failed with funds kept). No-op when no ledger is configured. See Ledger.forget_reservation/3.

Refund a previously authorized amount (e.g. when execution fails after the gate reserved budget). No-op when no ledger is configured.

Idempotently release the reservation tagged for intent_id (a refund reversed the spend). Returns :released, :noop, or :noop when no ledger is configured. Safe to call on every observation of a refund -- a second call for an already-released intent is a no-op. See Ledger.release_by_intent/3.

Tag the reservation just made for amount with the intent (or transfer) id it funded, so a later refund can release it by id. Call once execution dispatches. No-op when no ledger is configured. See Ledger.tag_reservation/4.

Types

error()

@type error() ::
  {:over_budget, atom()}
  | {:requires_confirmation, Decimal.t()}
  | {:invalid_amount, Decimal.t()}
  | {:policy_required, atom()}
  | {:deny, Raxol.Payments.PolicyGate.deny_reason()}

target()

@type target() :: {:domain, String.t()} | {:address, String.t()}

Functions

authorize(context, amount, opts \\ [])

@spec authorize(map(), Decimal.t(), keyword()) :: :ok | {:error, error()}

Authorize a spend before signing. Returns :ok or {:error, reason}.

Options

  • :target -- {:domain, host} or {:address, addr} (default {:address, ""}).
  • :metadata -- map recorded with the ledger entry on success.

forget_reservation(context, intent_id)

@spec forget_reservation(map(), String.t()) :: :ok

Drop the reservation tag for intent_id without releasing budget (the spend stands; the intent settled or failed with funds kept). No-op when no ledger is configured. See Ledger.forget_reservation/3.

release(context, amount, metadata \\ %{})

@spec release(map(), Decimal.t(), map()) :: :ok

Refund a previously authorized amount (e.g. when execution fails after the gate reserved budget). No-op when no ledger is configured.

release_by_intent(context, intent_id)

@spec release_by_intent(map(), String.t()) :: :released | :noop

Idempotently release the reservation tagged for intent_id (a refund reversed the spend). Returns :released, :noop, or :noop when no ledger is configured. Safe to call on every observation of a refund -- a second call for an already-released intent is a no-op. See Ledger.release_by_intent/3.

tag_reservation(context, intent_id, amount)

@spec tag_reservation(map(), String.t(), Decimal.t()) :: :ok

Tag the reservation just made for amount with the intent (or transfer) id it funded, so a later refund can release it by id. Call once execution dispatches. No-op when no ledger is configured. See Ledger.tag_reservation/4.