Skip to content
38 changes: 38 additions & 0 deletions python/coinbase-agentkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,44 @@ This section provides a detailed list of all available action providers and thei
</table>
</details>

<details>
<summary><strong>Spraay</strong></summary>
<table width="100%">
<tr>
<td width="200"><code>spraay_eth</code></td>
<td width="768">Sends equal amounts of ETH to up to 200 recipients in a single atomic transaction via the Spraay batch contract on Base.</td>
</tr>
<tr>
<td width="200"><code>spraay_token</code></td>
<td width="768">Sends equal amounts of an ERC-20 token to up to 200 recipients atomically, with EIP-2612 permit-optimized approvals (approve fallback).</td>
</tr>
<tr>
<td width="200"><code>spraay_eth_variable</code></td>
<td width="768">Sends different ETH amounts to each recipient in a single atomic batch transaction.</td>
</tr>
<tr>
<td width="200"><code>spraay_token_variable</code></td>
<td width="768">Sends different ERC-20 amounts to each recipient in a single atomic batch transaction.</td>
</tr>
<tr>
<td width="200"><code>spraay_validate_batch</code></td>
<td width="768">Validates a batch against the free Spraay gateway pre-flight endpoint before signing anything.</td>
</tr>
<tr>
<td width="200"><code>spraay_estimate_batch</code></td>
<td width="768">Estimates batch execution cost via the free Spraay gateway endpoint.</td>
</tr>
<tr>
<td width="200"><code>spraay_execute_batch_gateway</code></td>
<td width="768">Executes a batch through the x402-metered Spraay gateway (POST /api/v1/batch/execute; pricing via 402 challenge).</td>
</tr>
<tr>
<td width="200"><code>spraay_create_escrow</code></td>
<td width="768">Creates an escrow through the x402-metered Spraay gateway (POST /api/v1/escrow/create; pricing via 402 challenge).</td>
</tr>
</table>
</details>

<details>
<summary><strong>SSH</strong></summary>
<table width="100%">
Expand Down
1 change: 1 addition & 0 deletions python/coinbase-agentkit/changelog.d/944.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added Spraay action provider: atomic batch payments (ETH/ERC-20, up to 200 recipients) on Base with EIP-2612 permit-optimized approvals, free gateway pre-flight validation and cost estimation, x402-metered gateway batch execution, and escrow creation
4 changes: 4 additions & 0 deletions python/coinbase-agentkit/coinbase_agentkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .action_providers import (
Action,
ActionProvider,
SpraayConfig,
X402Config,
aave_action_provider,
basename_action_provider,
Expand All @@ -19,6 +20,7 @@
nillion_action_provider,
onramp_action_provider,
pyth_action_provider,
spraay_action_provider,
ssh_action_provider,
superfluid_action_provider,
twitter_action_provider,
Expand Down Expand Up @@ -55,6 +57,7 @@
"EthAccountWalletProvider",
"EthAccountWalletProviderConfig",
"EvmWalletProvider",
"SpraayConfig",
"WalletProvider",
"X402Config",
"__version__",
Expand All @@ -72,6 +75,7 @@
"nillion_action_provider",
"onramp_action_provider",
"pyth_action_provider",
"spraay_action_provider",
"ssh_action_provider",
"superfluid_action_provider",
"twitter_action_provider",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
from .nillion.nillion_action_provider import NillionActionProvider, nillion_action_provider
from .onramp.onramp_action_provider import OnrampActionProvider, onramp_action_provider
from .pyth.pyth_action_provider import PythActionProvider, pyth_action_provider
from .spraay.schemas import SpraayConfig
from .spraay.spraay_action_provider import SpraayActionProvider, spraay_action_provider
from .ssh.ssh_action_provider import SshActionProvider, ssh_action_provider
from .superfluid.superfluid_action_provider import (
SuperfluidActionProvider,
Expand Down Expand Up @@ -55,6 +57,8 @@
"NillionActionProvider",
"OnrampActionProvider",
"PythActionProvider",
"SpraayActionProvider",
"SpraayConfig",
"SshActionProvider",
"SuperfluidActionProvider",
"TwitterActionProvider",
Expand All @@ -76,6 +80,7 @@
"nillion_action_provider",
"onramp_action_provider",
"pyth_action_provider",
"spraay_action_provider",
"ssh_action_provider",
"superfluid_action_provider",
"twitter_action_provider",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Spraay Action Provider

This directory contains the **SpraayActionProvider**, payment coordination for AgentKit agents on Base. Batch payments are the core capability — send ETH or any ERC-20 token to up to 200 recipients with per-recipient amounts, atomically, in a single transaction — with escrow creation via the Spraay gateway as the complementary second pillar.

Contract: [`0x1646452F98E36A3c9Cfc3eDD8868221E207B5eEC`](https://basescan.org/address/0x1646452F98E36A3c9Cfc3eDD8868221E207B5eEC) (Base Mainnet) · Gateway: `https://gateway.spraay.app` · Website: [spraay.app](https://spraay.app)

## Directory Structure

```
spraay/
├── spraay_action_provider.py # Provider: on-chain batch, permit handling, gateway actions
├── schemas.py # Pydantic schemas and provider config
├── constants.py # Contract address/ABI, gateway endpoints
├── utils.py # Unit parsing/formatting, signature splitting
├── __init__.py # Public exports
└── README.md # This file
```

## Setup

```python
from coinbase_agentkit import AgentKit, AgentKitConfig, SpraayConfig, spraay_action_provider

agent_kit = AgentKit(AgentKitConfig(
wallet_provider=wallet_provider,
action_providers=[
spraay_action_provider(SpraayConfig(
# Max x402 payment per gateway request in whole USDC (default 1.0,
# or SPRAAY_MAX_GATEWAY_PAYMENT_USDC env var)
max_gateway_payment_usdc=0.5,
# Optional pre-funded x402 payment header, sent instead of signing
# a payment with the wallet provider
# x402_payment_header=os.getenv("SPRAAY_X402_PAYMENT_HEADER"),
)),
],
))
```

## Batch payment actions (direct on-chain)

The agent signs the batch transaction and pays gas itself on Base. All batches are atomic: every transfer succeeds or the whole transaction reverts. Up to 200 recipients per transaction; the protocol fee (default 0.3%, read live from the contract) is added on top. Recipient lists are validated for case-insensitive duplicates before anything is signed.

- `spraay_eth`: Equal ETH amounts to every recipient.
- `spraay_token`: Equal ERC-20 amounts to every recipient.
- `spraay_eth_variable`: Per-recipient ETH amounts.
- `spraay_token_variable`: Per-recipient ERC-20 amounts.

**EIP-2612 permit:** for ERC-20 batches, allowance handling prefers a signed permit when the token supports it (detected at runtime via `nonces()`/`version()` — no hardcoded token list; USDC on Base qualifies). The permit grants an exact, deadline-bounded allowance instead of a standing approve. Non-permit tokens fall back cleanly to `approve`.

**Pre-flight (`preflight=True`):** validates the batch against the free gateway endpoint before signing. An explicit "invalid" verdict aborts before any signature; gateway unavailability never blocks the on-chain path.

## Gateway pre-flight actions (free, no payment)

- `spraay_validate_batch`: `POST https://gateway.spraay.app/free/validate-batch` — validates a batch (BPA 1.0 body; the gateway expects a `recipients` array of `{to, amount}` entries, which the provider maps from its uniform `{recipient, amount}` input) and returns `valid`/`errors`/`warnings`/`summary`.
- `spraay_estimate_batch`: `GET https://gateway.spraay.app/free/estimate-batch?recipients=<count>&chain=<chain>&amount=<total>` — rough gas and protocol-fee estimate for a batch of a given size (the optional total `amount` enables the fee figures).

These make agents safer: validate and cost a batch before signing anything.

## Gateway execution and escrow (x402-metered, paid)

- `spraay_execute_batch_gateway`: `POST https://gateway.spraay.app/api/v1/batch/execute` — the gateway executes the batch (body: `token`, `recipients` as `{address, amount}` entries mapped from the provider's uniform input, and the wallet's address as `sender`); the agent pays a metered USDC fee via the [x402 protocol](https://x402.org) (pricing returned via 402 challenge; `POST /api/v1/batch/estimate` quotes the same way). Multi-chain capable, no gas management for the agent.
- `spraay_create_escrow`: `POST https://gateway.spraay.app/api/v1/escrow/create` — locks funds from a depositor (defaults to the connected wallet) for a beneficiary, with optional arbiter, release conditions, and expiry in hours. Creation only; the gateway's `POST /api/v1/escrow/fund`, `/release`, and `/cancel` endpoints handle the rest of the lifecycle.

Payments are settled either by signing with the wallet provider (`x402` package) or with a pre-funded `x402_payment_header` from config, and are capped by `max_gateway_payment_usdc`. Payment is never faked or stubbed.

## Network Support

Base mainnet (`base-mainnet`) only — that is where the batch contract is deployed. Gateway execution is multi-chain capable on the gateway side, but the provider itself registers on Base EVM wallets.

## Notes

- Amounts are whole units (e.g. `"0.01"` ETH, `"100"` USDC); decimals are read from the token contract.
- Works with any `EvmWalletProvider` (CDP EVM wallets, smart wallets, eth-account wallets). If a wallet cannot sign EIP-712 typed data, permit silently falls back to `approve`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Spraay Action Provider for Coinbase AgentKit."""

from .schemas import SpraayConfig
from .spraay_action_provider import SpraayActionProvider, spraay_action_provider

__all__ = ["SpraayActionProvider", "SpraayConfig", "spraay_action_provider"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
"""Constants for the Spraay action provider."""

# Spraay batch payment contract (SprayContract) address on Base Mainnet.
# Verified on BaseScan / Blockscout.
SPRAAY_CONTRACT_ADDRESS = "0x1646452F98E36A3c9Cfc3eDD8868221E207B5eEC"

# Default Spraay protocol fee in basis points (0.3% = 30 bps).
# The live value is read from the contract's `feeBps` view; this is the fallback.
SPRAAY_PROTOCOL_FEE_BPS = 30

# Maximum number of recipients per transaction (contract-enforced MAX_RECIPIENTS).
SPRAAY_MAX_RECIPIENTS = 200

# Zero address, used by `sprayEqual` to select native ETH transfers.
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"

# Spraay gateway base URL. Hosts free pre-flight endpoints (validate/estimate)
# and x402-metered execution endpoints (batch execute/estimate, escrow create).
SPRAAY_GATEWAY_BASE_URL = "https://gateway.spraay.app"

# Free (no payment required) gateway endpoint paths.
SPRAAY_FREE_VALIDATE_BATCH_PATH = "/free/validate-batch"
SPRAAY_FREE_ESTIMATE_BATCH_PATH = "/free/estimate-batch"

# x402-metered (paid) gateway endpoint paths. Pricing is returned via a
# 402 Payment Required challenge on first request.
SPRAAY_GATEWAY_BATCH_EXECUTE_PATH = "/api/v1/batch/execute"
SPRAAY_GATEWAY_BATCH_ESTIMATE_PATH = "/api/v1/batch/estimate"
SPRAAY_GATEWAY_ESCROW_CREATE_PATH = "/api/v1/escrow/create"

# Batch Payment Aggregate (BPA) schema version used by the Spraay gateway.
SPRAAY_BPA_VERSION = "1.0"

# Validity window for EIP-2612 permit signatures, in seconds.
PERMIT_DEADLINE_SECONDS = 1800

# SprayContract ABI — matches the verified contract deployed at
# SPRAAY_CONTRACT_ADDRESS. Variable-amount functions take an array of
# (recipient, amount) structs; `sprayEqual` covers the uniform-amount case
# for both ETH (token = zero address) and ERC-20 tokens.
SPRAAY_ABI = [
{
"name": "sprayETH",
"type": "function",
"stateMutability": "payable",
"inputs": [
{
"name": "recipients",
"type": "tuple[]",
"components": [
{"name": "recipient", "type": "address"},
{"name": "amount", "type": "uint256"},
],
},
],
"outputs": [],
},
{
"name": "sprayToken",
"type": "function",
"stateMutability": "nonpayable",
"inputs": [
{"name": "token", "type": "address"},
{
"name": "recipients",
"type": "tuple[]",
"components": [
{"name": "recipient", "type": "address"},
{"name": "amount", "type": "uint256"},
],
},
],
"outputs": [],
},
{
"name": "sprayEqual",
"type": "function",
"stateMutability": "payable",
"inputs": [
{"name": "token", "type": "address"},
{"name": "recipients", "type": "address[]"},
{"name": "amountPerRecipient", "type": "uint256"},
],
"outputs": [],
},
{
"name": "feeBps",
"type": "function",
"stateMutability": "view",
"inputs": [],
"outputs": [{"name": "", "type": "uint256"}],
},
{
"name": "calculateTotalCost",
"type": "function",
"stateMutability": "view",
"inputs": [{"name": "totalAmount", "type": "uint256"}],
"outputs": [{"name": "", "type": "uint256"}],
},
]

# ERC-20 ABI fragments needed for approvals, metadata lookups, and
# EIP-2612 permit support (nonces/version/permit).
ERC20_ABI = [
{
"name": "approve",
"type": "function",
"stateMutability": "nonpayable",
"inputs": [
{"name": "spender", "type": "address"},
{"name": "amount", "type": "uint256"},
],
"outputs": [{"name": "", "type": "bool"}],
},
{
"name": "allowance",
"type": "function",
"stateMutability": "view",
"inputs": [
{"name": "owner", "type": "address"},
{"name": "spender", "type": "address"},
],
"outputs": [{"name": "", "type": "uint256"}],
},
{
"name": "decimals",
"type": "function",
"stateMutability": "view",
"inputs": [],
"outputs": [{"name": "", "type": "uint8"}],
},
{
"name": "symbol",
"type": "function",
"stateMutability": "view",
"inputs": [],
"outputs": [{"name": "", "type": "string"}],
},
{
"name": "name",
"type": "function",
"stateMutability": "view",
"inputs": [],
"outputs": [{"name": "", "type": "string"}],
},
{
"name": "version",
"type": "function",
"stateMutability": "view",
"inputs": [],
"outputs": [{"name": "", "type": "string"}],
},
{
"name": "nonces",
"type": "function",
"stateMutability": "view",
"inputs": [{"name": "owner", "type": "address"}],
"outputs": [{"name": "", "type": "uint256"}],
},
{
"name": "permit",
"type": "function",
"stateMutability": "nonpayable",
"inputs": [
{"name": "owner", "type": "address"},
{"name": "spender", "type": "address"},
{"name": "value", "type": "uint256"},
{"name": "deadline", "type": "uint256"},
{"name": "v", "type": "uint8"},
{"name": "r", "type": "bytes32"},
{"name": "s", "type": "bytes32"},
],
"outputs": [],
},
]
Loading
Loading