---
name: conto
description: Enforce fine-grained spending policies before executing any payment, transfer, swap, or bridge. Checks Conto policy engine for approval before money leaves your wallet. Use /conto to manage policies or check payments.
version: 1.8.2
user-invocable: true
metadata:
  {
    'openclaw':
      {
        'emoji': '🛡️',
        'primaryEnv': 'CONTO_SDK_KEY',
        'homepage': 'https://conto.finance',
        'requires': { 'env': ['CONTO_SDK_KEY'], 'bins': ['curl', 'jq', 'python3'] },
      },
  }
---

# Conto Policy Enforcement

You are a spending policy enforcement layer. **Before executing ANY payment, transfer, swap, or bridge**, you MUST check Conto's policy engine for approval. Never send money without policy clearance.

## Prerequisites

Before using this skill, you need:

1. **A Conto account**: Sign up at [conto.finance](https://conto.finance)
2. **A `CONTO_SDK_KEY`** (required): Generated from the Conto dashboard (format: `conto_agent_...`). See [Getting Started](#getting-started) below.
3. **`curl`, `jq`, and `python3`**: Command-line tools. `python3` handles the temporary browser callback used by `bash {baseDir}/conto-check.sh setup`.
4. **`CONTO_API_URL`** (optional): API base URL (default: `https://conto.finance`). Use the hosted HTTPS endpoint unless Conto support provides another URL.

## When This Skill Activates

This skill applies whenever you are about to:

- Transfer tokens (USDC, ETH, SOL, etc.) to any address
- Swap tokens on any DEX
- Bridge tokens between chains
- Pay for an API call (x402, paid endpoints)
- Make any onchain transaction that moves value

## Getting Started

### Step 1: Install the skill

```bash
npx clawhub install conto
```

### Step 2: Set up your agent

Run the setup command with your agent name and wallet address:

```bash
bash {baseDir}/conto-check.sh setup "my-agent" "0xYourWalletAddress" EVM 42431
```

This opens your browser to sign in to Conto. After you approve, the agent is automatically provisioned with:

- An agent record linked to your organization
- Your wallet registered as EXTERNAL custody
- Default spend limits ($100/tx, $500/day)
- An SDK key saved to `~/.openclaw/openclaw.json`

To find your wallet address, ask the agent: "What is my wallet address?" If you don't have a wallet yet, ask "Show me my wallet balances", and one will be provisioned automatically.

**Arguments:**

- `agent_name`: Name for your agent (e.g., "my-openclaw-agent")
- `wallet_address`: Your wallet address (0x... for EVM, base58 for Solana)
- `chain_type`: EVM or SOLANA (default: EVM)
- `chain_id`: Chain ID (default: 42431 for Tempo Testnet). Common values: 8453 (Base), 42431 (Tempo Testnet), 1 (Ethereum)

### Step 3: Verify it works

Test that the skill is connected:

```bash
bash {baseDir}/conto-check.sh budget
```

Or check your policies:

```
/conto list my policies
```

If you get a response, Conto is working.

### Manual Setup (Alternative)

If the browser setup doesn't work, you can configure manually:

1. Sign in at [conto.finance](https://conto.finance)
2. **Connect your agent**: Agents > Connect Agent > name it, set type to CUSTOM
3. **Register your wallet**: Wallets > Add Wallet > paste address, set EXTERNAL custody
4. **Link wallet to agent**: Agents > your agent > Wallets > link with spend limits
5. **Generate SDK key**: Agents > your agent > SDK Keys > Generate New Key (copy immediately, shown once)
6. Add to `~/.openclaw/openclaw.json`:

```json
{
  "skills": {
    "entries": {
      "conto": {
        "env": {
          "CONTO_SDK_KEY": "conto_agent_your_key_here",
          "CONTO_API_URL": "https://conto.finance"
        }
      }
    }
  }
}
```

## Which Wallet Mode Should I Use?

Conto supports two modes depending on who manages the wallet keys:

| Question                        | Mode A                                                 | Mode B                                            |
| ------------------------------- | ------------------------------------------------------ | ------------------------------------------------- |
| Who holds the wallet keys?      | Conto's managed wallet service                         | You (via your wallet tools)                       |
| How many API calls per payment? | 1 (single call, auto-executes)                         | 3 (approve → transfer → confirm)                  |
| When to use?                    | Wallet `custodyMode` is MANAGED in the Conto dashboard | Wallet `custodyMode` is EXTERNAL in the dashboard |

**Most OpenClaw setups use Mode B.** Your agent controls the wallet through its existing wallet tools, and Conto acts as the policy gate before each transaction.

> **Prefer `bash {baseDir}/conto-check.sh`** for Mode B commands (`approve`, `confirm`, `x402`, `budget`, `services`, and all policy commands). The shell helper includes input validation, HTTPS enforcement, timeouts, retry logic, and safe credential handling. Use raw `curl` for Mode A endpoints (`/request`, `/execute`) and the x402 `/record` endpoint.

---

## Quick Start: Your First Policy-Checked Payment (Mode B)

Here's a complete end-to-end example of sending 10 USDC with policy enforcement:

**1. Request approval from Conto:**

```bash
bash {baseDir}/conto-check.sh approve 10 0xRecipientAddress 0xYourWalletAddress 8453 "API credits" "API_PROVIDER"
```

**2. If approved, execute the transfer:**

Use your configured wallet integration to send the approved amount to the approved recipient on the
approved chain. Save the returned transaction hash.

**3. Confirm the transaction with Conto:**

```bash
bash {baseDir}/conto-check.sh confirm <approval_id> <tx_hash> <approval_token>
```

That's it. Conto checked the policy, you sent the payment, and the confirmation keeps spend tracking accurate. The sections below cover each step in detail.

---

## Mode A: Managed Wallets

For wallets managed by Conto, use a single API call. Conto evaluates policies and executes the transfer.

```bash
curl -sS -X POST "${CONTO_API_URL:-https://conto.finance}/api/sdk/payments/request" \
  -H "Authorization: Bearer $CONTO_SDK_KEY" \
  -H "Content-Type: application/json" \
  --connect-timeout 10 --max-time 30 \
  -d '{
    "amount": <AMOUNT>,
    "recipientAddress": "<RECIPIENT_ADDRESS>",
    "recipientName": "<OPTIONAL_NAME>",
    "purpose": "<WHY_THIS_PAYMENT>",
    "category": "<CATEGORY>",
    "autoExecute": true
  }'
```

**If execution starts**, the response uses a customer-facing state and may include a public receipt:

```json
{
  "requestId": "cmm59z...",
  "status": "PROCESSING",
  "amount": 25,
  "currency": "USDC",
  "recipient": "0xRecipientAddress",
  "actionUrl": "/api/sdk/payments/cmm59z...",
  "receipt": {
    "txHash": "0xdef...",
    "explorerUrl": "https://explore.testnet.tempo.xyz/?q=0xdef..."
  }
}
```

Do not call `/execute` again for a `PROCESSING` response. Report the receipt when present and check
the original request status until it completes.

**If the response remains `APPROVED`**, call `/execute` manually:

```bash
curl -sS -X POST "${CONTO_API_URL:-https://conto.finance}/api/sdk/payments/<REQUEST_ID>/execute" \
  -H "Authorization: Bearer $CONTO_SDK_KEY" \
  -H "Content-Type: application/json" \
  --connect-timeout 10 --max-time 30
```

**If denied**, report the customer-facing `reasons` and do not execute the payment.

---

## Mode B: External Wallets (EXTERNAL custody)

For wallets where you hold the keys, use the three-step flow: approve → transfer → confirm.

### Step 1: Request Policy Approval

```bash
# Prefer: bash {baseDir}/conto-check.sh approve <amount> <recipient> <sender> <chain_id> [purpose] [category]
curl -sS -X POST "${CONTO_API_URL:-https://conto.finance}/api/sdk/payments/approve" \
  -H "Authorization: Bearer $CONTO_SDK_KEY" \
  -H "Content-Type: application/json" \
  --connect-timeout 10 --max-time 30 \
  -d '{
    "amount": <AMOUNT_IN_USDC>,
    "recipientAddress": "<RECIPIENT_ADDRESS>",
    "senderAddress": "<YOUR_WALLET_ADDRESS>",
    "recipientName": "<OPTIONAL_NAME>",
    "purpose": "<WHY_THIS_PAYMENT>",
    "category": "<CATEGORY>",
    "chainId": <CHAIN_ID>
  }'
```

**Required fields:**

- `amount`: Positive number, the USDC value of the transaction
- `recipientAddress`: The destination address (0x... for EVM, base58 for Solana)
- `senderAddress`: Your wallet address that will send the funds
- `chainId`: Chain ID number. **Required.** Common values: `8453` (Base mainnet), `42431` (Tempo Testnet), `84532` (Base Sepolia), `1` (Ethereum). For Solana, use a base58 `senderAddress` with any chainId, and the chain type is detected automatically from the address format.

**Optional fields:**

- `recipientName`: Human-readable name (e.g., "OpenAI API", "Uniswap Router")
- `purpose`: Why this payment is needed (e.g., "Swap 0.5 ETH for USDC on Uniswap")
- `category`: One of: `API_PROVIDER`, `CLOUD`, `SAAS`, `INFRASTRUCTURE`, `MARKETING`, `PAYROLL`, `TRAVEL`, `LODGING`, `TRANSPORT`, `SUPPLIES`, `DATABASE`, `MONITORING`, `PAYMENTS`, `OTHER`
- `context`: Optional invoice details in `context.invoice`

**For x402 API payments** (paid HTTP endpoints), use the dedicated pre-authorize endpoint instead:

```bash
# Prefer: bash {baseDir}/conto-check.sh x402 <amount> <recipient> <resource_url>
curl -sS -X POST "${CONTO_API_URL:-https://conto.finance}/api/sdk/x402/pre-authorize" \
  -H "Authorization: Bearer $CONTO_SDK_KEY" \
  -H "Content-Type: application/json" \
  --connect-timeout 10 --max-time 30 \
  -d '{
    "amount": <AMOUNT>,
    "recipientAddress": "<PAYEE_ADDRESS>",
    "resourceUrl": "<THE_API_URL>"
  }'
```

### Step 2: Handle the Approval Response

**If approved** (`"approved": true`):

```json
{
  "approved": true,
  "decision": "approved",
  "status": "ready_to_send",
  "approvalId": "cmm59z...",
  "approvalToken": "a1b2c3d4...",
  "expiresAt": "2025-03-19T12:10:00.000Z",
  "confirmUrl": "/api/sdk/payments/<approvalId>/confirm",
  "statusUrl": "/api/sdk/payments/<approvalId>",
  "network": { "type": "evm", "id": "8453" },
  "reasons": [],
  "reasonCodes": [],
  "requiresHumanApproval": false,
  "limits": {
    "dailyUsed": 150.0,
    "dailyLimit": 1000.0,
    "dailyRemaining": 850.0
  },
  "nextAction": {
    "type": "submit_transaction",
    "confirmUrl": "/api/sdk/payments/<approvalId>/confirm",
    "expiresAt": "2025-03-19T12:10:00.000Z"
  }
}
```

Save the `approvalId` and `approvalToken`, you need them for Step 3.
The approval expires in 10 minutes. Execute and confirm before then.

**Security note:** The `approvalToken` authorizes confirmation of the saved payment request. Before calling `/confirm`, verify that the transfer you sent matches the approved amount, recipient, and chain.

### Step 2a: Execute the Transfer

**Now execute the payment using your wallet.** Conto approved the policy, now YOU must send the actual onchain transaction. Do NOT ask the user to execute it.

Use your configured wallet integration for the approved chain and currency. Verify the amount and
recipient against the approval response before signing.

The transfer will return a transaction hash. Save it for Step 3.
If the transfer fails, report the error to the user. Do NOT call confirm.

### Handling Denials

**If denied** (`"approved": false`):

```json
{
  "approved": false,
  "decision": "declined",
  "status": "declined",
  "approvalId": "cmm59z...",
  "reasons": ["This payment exceeds a configured spending limit."],
  "reasonCodes": ["SPENDING_LIMIT"],
  "requiresHumanApproval": false,
  "statusUrl": "/api/sdk/payments/<approvalId>",
  "nextAction": null
}
```

**DO NOT execute the payment.** Report the denial to the user:

- Show the customer-facing `reasons` array
- Use `reasonCodes` for programmatic handling
- If `requiresHumanApproval` is true, save `approvalRequestId`, follow `nextAction`, and tell the user a Conto dashboard admin must approve it

**If the request fails** (HTTP errors):

- 401: SDK key invalid. Ask user to check `CONTO_SDK_KEY`.
- 403: Missing scope. Needs `payments:approve`.
- 429: Rate limited. Wait for the `Retry-After` header value and retry.
- 500/502/503: Retry once after a 2-second delay. If it fails again, inform the user. The `conto-check.sh` helper handles this automatically.

### Step 3: Confirm After Execution

After the transfer succeeds, report the transaction hash back to Conto:

```bash
# Prefer: bash {baseDir}/conto-check.sh confirm <approval_id> <tx_hash> <approval_token>
curl -sS -X POST "${CONTO_API_URL:-https://conto.finance}/api/sdk/payments/<APPROVAL_ID>/confirm" \
  -H "Authorization: Bearer $CONTO_SDK_KEY" \
  -H "Content-Type: application/json" \
  --connect-timeout 10 --max-time 30 \
  -d '{
    "txHash": "<ON_CHAIN_TX_HASH>",
    "approvalToken": "<TOKEN_FROM_STEP_2>"
  }'
```

**Required fields:**

- `txHash`: The onchain transaction hash (0x + 64 hex chars for EVM, or base58 for Solana)
- `approvalToken`: The exact token string from the approval response

**Success response:**

```json
{
  "confirmed": true,
  "requestId": "cmm59z...",
  "transactionId": "tx_...",
  "status": "processing",
  "txHash": "0xdef...",
  "network": { "type": "evm", "id": "8453" },
  "explorerUrl": "https://basescan.org/tx/0xdef...",
  "statusUrl": "/api/sdk/transactions/tx_..."
}
```

Confirmation records the transaction hash and starts onchain confirmation tracking. Do not skip it after sending the transfer.

If confirmation fails with `EXPIRED`, the 10-minute window passed. Inform the user.

### x402 API Payments

For paid HTTP endpoints, use `/api/sdk/x402/pre-authorize` instead of `/approve` (see Step 1 above). If authorized (`"authorized": true`), proceed with the x402 payment flow normally. No separate confirm step is needed. Use the x402 record endpoint instead:

```bash
curl -sS -X POST "${CONTO_API_URL:-https://conto.finance}/api/sdk/x402/record" \
  -H "Authorization: Bearer $CONTO_SDK_KEY" \
  -H "Content-Type: application/json" \
  --connect-timeout 10 --max-time 30 \
  -d '{
    "amount": <AMOUNT>,
    "recipientAddress": "<PAYEE>",
    "resourceUrl": "<API_URL>",
    "paymentId": "<X402_PAYMENT_ID>",
    "sessionId": "<CUSTOMER_SESSION_ID>",
    "txHash": "<TX_HASH>"
  }'
```

For multiple micropayments in one call, use
`"batchItems": [{ "amount": ..., "resourceUrl": ..., "paymentId": ... }, ...]`.

`sessionId` is optional. When grouping related calls, send the same customer-defined value during
pre-authorization and recording, then query
`GET /api/sdk/x402/budget?sessionId=<CUSTOMER_SESSION_ID>` to reconcile that session's spend.

---

---

## Managing Policies from OpenClaw

If your SDK key is an **admin key** (`keyType: "admin"`), you can create, update, and delete policies directly from the CLI. Standard keys can only read policies and check payments.

### List all policies

```bash
bash {baseDir}/conto-check.sh policies
```

### Create a policy

```bash
bash {baseDir}/conto-check.sh create-policy '{
  "name": "Max $200 Per Transaction",
  "policyType": "SPEND_LIMIT",
  "priority": 10,
  "isActive": true,
  "rules": [
    {"ruleType": "MAX_AMOUNT", "operator": "LTE", "value": "200", "action": "ALLOW"}
  ]
}'
```

The response includes the new policy's `id`. Save it for assigning to agents.

### Common policy recipes

**Daily spending cap of $1,000:**

```json
{
  "name": "Daily $1K Cap",
  "policyType": "SPEND_LIMIT",
  "priority": 10,
  "isActive": true,
  "rules": [{ "ruleType": "DAILY_LIMIT", "operator": "LTE", "value": "1000", "action": "ALLOW" }]
}
```

**Only allow API and Cloud payments:**

```json
{
  "name": "API+Cloud Only",
  "policyType": "CATEGORY",
  "priority": 5,
  "isActive": true,
  "rules": [
    {
      "ruleType": "ALLOWED_CATEGORIES",
      "operator": "IN_LIST",
      "value": "[\"API_PROVIDER\",\"CLOUD\",\"SAAS\"]",
      "action": "ALLOW"
    }
  ]
}
```

**Block a scam address:**

```json
{
  "name": "Block Scammer",
  "policyType": "COUNTERPARTY",
  "priority": 50,
  "isActive": true,
  "rules": [
    {
      "ruleType": "BLOCKED_COUNTERPARTIES",
      "operator": "IN_LIST",
      "value": "[\"0xbadaddress...\"]",
      "action": "DENY"
    }
  ]
}
```

**Require human approval above $500:**

```json
{
  "name": "High Value Review",
  "policyType": "APPROVAL_THRESHOLD",
  "priority": 8,
  "isActive": true,
  "rules": [
    {
      "ruleType": "REQUIRE_APPROVAL_ABOVE",
      "operator": "GT",
      "value": "500",
      "action": "REQUIRE_APPROVAL"
    }
  ]
}
```

**Business hours only (Mon-Fri 9am-6pm):**

```json
{
  "name": "Business Hours",
  "policyType": "TIME_WINDOW",
  "priority": 5,
  "isActive": true,
  "rules": [
    {
      "ruleType": "TIME_WINDOW",
      "operator": "BETWEEN",
      "value": "{\"start\":\"09:00\",\"end\":\"18:00\"}",
      "action": "ALLOW"
    },
    {
      "ruleType": "DAY_OF_WEEK",
      "operator": "IN_LIST",
      "value": "[\"Mon\",\"Tue\",\"Wed\",\"Thu\",\"Fri\"]",
      "action": "ALLOW"
    }
  ]
}
```

**Cap x402 API spend at $1/request, $50/day per service:**

```json
{
  "name": "x402 Controls",
  "policyType": "SPEND_LIMIT",
  "priority": 10,
  "isActive": true,
  "rules": [
    { "ruleType": "X402_PRICE_CEILING", "operator": "LTE", "value": "1", "action": "ALLOW" },
    {
      "ruleType": "X402_MAX_PER_SERVICE",
      "operator": "LTE",
      "value": "{\"amount\":50,\"period\":\"DAILY\"}",
      "action": "ALLOW"
    }
  ]
}
```

### Delete a policy

```bash
bash {baseDir}/conto-check.sh delete-policy <policy_id>
```

### Available rule types

| Rule Type                      | Operator  | Value Format                                | Use Case                        |
| ------------------------------ | --------- | ------------------------------------------- | ------------------------------- |
| `MAX_AMOUNT`                   | `LTE`     | `"200"`                                     | Per-transaction cap             |
| `DAILY_LIMIT`                  | `LTE`     | `"1000"`                                    | Daily spending cap              |
| `WEEKLY_LIMIT`                 | `LTE`     | `"5000"`                                    | Weekly spending cap             |
| `MONTHLY_LIMIT`                | `LTE`     | `"20000"`                                   | Monthly spending cap            |
| `BUDGET_CAP`                   | `LTE`     | `{"amount":10000,"period":"MONTHLY"}`       | Budget allocation               |
| `ALLOWED_CATEGORIES`           | `IN_LIST` | `["API_PROVIDER","CLOUD"]`                  | Category whitelist              |
| `BLOCKED_CATEGORIES`           | `IN_LIST` | `["GAMBLING"]`                              | Category blocklist              |
| `ALLOWED_COUNTERPARTIES`       | `IN_LIST` | `["0xabc..."]`                              | Address whitelist               |
| `BLOCKED_COUNTERPARTIES`       | `IN_LIST` | `["0xbad..."]`                              | Address blocklist               |
| `TIME_WINDOW`                  | `BETWEEN` | `{"start":"09:00","end":"18:00"}`           | Allowed hours                   |
| `DAY_OF_WEEK`                  | `IN_LIST` | `["Mon","Tue","Wed","Thu","Fri"]`           | Allowed days                    |
| `VELOCITY_LIMIT`               | `LTE`     | `{"maxCount":10,"period":"HOUR"}`           | Rate limiting                   |
| `REQUIRE_APPROVAL_ABOVE`       | `GT`      | `"500"`                                     | Human approval threshold        |
| `AGENT_ENVIRONMENT`            | `IN_LIST` | `["PRODUCTION","STAGING"]`                  | Restrict by agent environment   |
| `COUNTERPARTY_APPROVAL_STATUS` | `IN_LIST` | `["APPROVED"]`                              | Require approved counterparties |
| `GEOGRAPHIC_RESTRICTION`       | `IN_LIST` | `["US","CA","GB"]`                          | Country whitelist               |
| `TRUST_SCORE`                  | `GTE`     | `"0.5"`                                     | Min counterparty trust score    |
| `COUNTERPARTY_STATUS`          | `IN_LIST` | `["TRUSTED","VERIFIED"]`                    | Required counterparty status    |
| `CONTRACT_ALLOWLIST`           | `IN_LIST` | `["0xcontract..."]`                         | Smart contract whitelist        |
| `BLACKOUT_PERIOD`              | `BETWEEN` | `{"start":"2026-04-01","end":"2026-04-02"}` | Block during maintenance        |
| `DATE_RANGE`                   | `BETWEEN` | `{"start":"2026-01-01","end":"2026-12-31"}` | Allowed date range              |
| `X402_PRICE_CEILING`           | `LTE`     | `"1"`                                       | Max per x402 API call           |
| `X402_ALLOWED_SERVICES`        | `IN_LIST` | `["api.openai.com"]`                        | x402 service whitelist          |
| `X402_BLOCKED_SERVICES`        | `IN_LIST` | `["untrusted.api"]`                         | x402 service blocklist          |
| `X402_MAX_PER_ENDPOINT`        | `LTE`     | `"10"`                                      | Max spend per x402 endpoint     |
| `X402_VELOCITY_PER_ENDPOINT`   | `LTE`     | `{"maxCount":5,"period":"MINUTE"}`          | x402 endpoint rate limit        |
| `X402_SESSION_BUDGET`          | `LTE`     | `"100"`                                     | x402 session spend cap          |
| `MPP_ALLOWED_SERVICES`         | `IN_LIST` | `["api.example.com"]`                       | MPP service whitelist           |
| `MPP_BLOCKED_SERVICES`         | `IN_LIST` | `["untrusted.api"]`                         | MPP service blocklist           |
| `MPP_MAX_PER_SERVICE`          | `LTE`     | `"50"`                                      | Max spend per MPP service       |
| `MPP_MAX_PER_ENDPOINT`         | `LTE`     | `"10"`                                      | Max spend per MPP endpoint      |
| `MPP_VELOCITY_PER_ENDPOINT`    | `LTE`     | `{"maxCount":5,"period":"MINUTE"}`          | MPP endpoint rate limit         |
| `MPP_SESSION_BUDGET`           | `LTE`     | `"100"`                                     | MPP session spend cap           |
| `MPP_MAX_SESSION_DEPOSIT`      | `LTE`     | `"50"`                                      | Max MPP session deposit         |
| `MPP_MAX_CONCURRENT_SESSIONS`  | `LTE`     | `"3"`                                       | Max active MPP sessions         |
| `MPP_MAX_SESSION_DURATION`     | `LTE`     | `"3600"`                                    | Max MPP session seconds         |
| `MPP_BLOCK_SESSION_INTENT`     | `IN_LIST` | `["streaming"]`                             | Block specific MPP intents      |

## Critical Rules

1. **Use the right mode for the wallet type.** Mode A (`/request` + `autoExecute`) for `MANAGED` wallets. Mode B (`/approve` + transfer + `/confirm`) for `EXTERNAL` wallets.
2. **NEVER skip the policy check.** Every payment must go through Conto first.
3. **NEVER execute a denied payment.** If `approved` is `false` or `status` is `DENIED`, stop.
4. **For Mode B: ALWAYS confirm after execution.** Call `/confirm` with the tx hash to keep spend tracking accurate. Mode A handles this automatically.
5. **Approvals expire quickly.** Mode A (`/request`) approvals expire in **5 minutes**. Mode B (`/approve`) approvals expire in **10 minutes**. Execute promptly after approval.
6. **On API errors, fail closed.** If the Conto API is unreachable after one retry, do NOT proceed with the payment.
7. **Convert to USDC equivalent.** The `amount` field is always in USDC. If you're swapping ETH or another token, convert to the USDC equivalent value for the policy check.
