> For the complete documentation index, see [llms.txt](https://docs.greenowl.money/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.greenowl.money/agent-and-tools/agent/bundler_relay.md).

# bundler-relay

## Overview

The **Bundler Relay** is a secure proxy service that protects the ERC-4337 bundler API key from public exposure while enabling open-source AI agents to submit UserOperations. It acts as an authentication layer between public clients (like the OpenClaw agent) and the private Alchemy bundler infrastructure.

In the Owl Smart Wallet architecture, AI agents cannot directly access the bundler because doing so would require embedding the API key in open-source code. The relay solves this by:

1. Accepting authenticated requests from verified OpenClaw clients
2. Injecting the bundler API key from GCP Secret Manager
3. Forwarding the request to the private bundler endpoint
4. Returning the response to the client

This architecture enables **autonomous agent operation** without compromising bundler credentials.

***

## Architecture

```
┌─────────────────┐      ┌──────────────────┐      ┌─────────────────┐
│  OpenClaw Agent │─────▶│  Bundler Relay   │─────▶│ Alchemy Bundler │
│  (Public Code)  │      │ (API Key Shield) │      │ (Private)       │
└─────────────────┘      └──────────────────┘      └─────────────────┘
                               │
                               ▼
                         GCP Secret Manager
                         (owl-bundler-api-key)
```

**Components:**

* **Public Agent** — OpenClaw agent or owl-wallet MCP server with no embedded secrets
* **Bundler Relay** — This service, deployed on Google Cloud Run
* **Private Bundler** — Alchemy ERC-4337 bundler with API key authentication
* **Secret Manager** — GCP service storing the bundler API key

***

## Security Features

The relay implements multiple layers of protection:

| Feature                   | Description                                                                                            |
| ------------------------- | ------------------------------------------------------------------------------------------------------ |
| **API Key Protection**    | Bundler API key stored in GCP Secret Manager, never in code or environment                             |
| **Client Authentication** | Requires `x-openclaw-client` header with valid client identifier                                       |
| **Method Whitelisting**   | Only ERC-4337 JSON-RPC methods allowed (`eth_sendUserOperation`, `eth_estimateUserOperationGas`, etc.) |
| **Rate Limiting**         | 60 requests/minute per IP address to prevent abuse                                                     |
| **Request Validation**    | Full JSON-RPC 2.0 structure validation before forwarding                                               |
| **Timeout & Retry**       | 30-second timeout with automatic retry on 5xx errors                                                   |
| **HTTPS Only**            | TLS termination at Cloud Run ingress                                                                   |

**Threat Model:**

* **Prevents:** API key exposure in public repositories, unauthorized bundler access, method injection attacks
* **Assumes:** GCP infrastructure security, Cloud Run service account permissions properly scoped
* **Does not protect against:** DoS via rate limit exhaustion (mitigated by per-IP limiting)

***

## Endpoints

### Health Check

```
GET /health
```

Returns service health status. Used by Cloud Run for readiness probes.

**Response:**

```json
{
  "status": "healthy",
  "timestamp": "2024-01-15T10:30:00.000Z"
}
```

***

### Version

```
GET /version
```

Returns service version and build information.

**Response:**

```json
{
  "service": "owl-bundler-relay",
  "version": "1.0.0",
  "commit": "a1b2c3d"
}
```

***

### JSON-RPC Relay

```
POST /rpc
```

Forwards ERC-4337 JSON-RPC requests to the bundler with API key injection.

**Required Headers:**

| Header               | Required | Description                                                  |
| -------------------- | -------- | ------------------------------------------------------------ |
| `x-openclaw-client`  | Yes      | Client identifier: `owl-agent`, `owl-wallet`, `openclaw-sdk` |
| `x-openclaw-version` | No       | Client version in semver format (e.g., `1.0.0`)              |
| `Content-Type`       | Yes      | Must be `application/json`                                   |

**Request Body (JSON-RPC 2.0):**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_sendUserOperation",
  "params": [
    {
      "sender": "0x...",
      "nonce": "0x0",
      "initCode": "0x",
      "callData": "0x...",
      "callGasLimit": "0x...",
      "verificationGasLimit": "0x...",
      "preVerificationGas": "0x...",
      "maxFeePerGas": "0x...",
      "maxPriorityFeePerGas": "0x...",
      "paymasterAndData": "0x",
      "signature": "0x..."
    },
    "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"
  ]
}
```

**Allowed Methods:**

* `eth_sendUserOperation`
* `eth_estimateUserOperationGas`
* `eth_getUserOperationByHash`
* `eth_getUserOperationReceipt`
* `eth_supportedEntryPoints`

**Response:**

Proxied response from the bundler, unchanged.

**Error Responses:**

| Status | Condition                          |
| ------ | ---------------------------------- |
| `400`  | Missing `x-openclaw-client` header |
| `400`  | Invalid JSON-RPC structure         |
| `403`  | Method not whitelisted             |
| `429`  | Rate limit exceeded (60 req/min)   |
| `500`  | Bundler communication error        |
| `503`  | Secret Manager unavailable         |

***

## Local Development

### Prerequisites

* Node.js 18+
* Bundler endpoint (Alchemy or local bundler)
* Bundler API key

### Setup

```bash
# Install dependencies
npm install

# Copy environment template
cp .env.example .env
```

**Edit `.env`:**

```env
BUNDLER_URL=https://your-bundler.awsapprunner.com
BUNDLER_API_KEY=your-api-key-here
PORT=8080
```

**Note:** In local development, the API key is read from `.env`. In production, it's fetched from GCP Secret Manager.

### Run

```bash
# Development mode with auto-reload
npm run dev

# Production mode
npm start
```

### Test

```bash
# Health check
curl http://localhost:8080/health

# Version check
curl http://localhost:8080/version

# RPC call (example: supported entry points)
curl -X POST http://localhost:8080/rpc \
  -H "Content-Type: application/json" \
  -H "x-openclaw-client: owl-agent" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "eth_supportedEntryPoints",
    "params": []
  }'
```

***

## Production Deployment

### Prerequisites

* GCP project with billing enabled
* Cloud Run and Secret Manager APIs enabled
* Bundler API key stored in Secret Manager

### Create Secret

```bash
# Store bundler API key in Secret Manager
echo -n "your-bundler-api-key" | gcloud secrets create owl-bundler-api-key \
  --replication-policy="user-managed" \
  --locations="us-west2" \
  --data-file=-

# Grant Cloud Run service account access
gcloud secrets add-iam-policy-binding owl-bundler-api-key \
  --member="serviceAccount:YOUR-PROJECT-NUMBER-compute@developer.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor"
```

### Deploy to Cloud Run

```bash
# Deploy from source
gcloud run deploy owl-bundler-relay \
  --source . \
  --region us-west2 \
  --allow-unauthenticated \
  --memory 512Mi \
  --cpu 1 \
  --min-instances 1 \
  --max-instances 10 \
  --timeout 30s \
  --set-env-vars "BUNDLER_URL=https://eth-mainnet.g.alchemy.com/v2,GOOGLE_CLOUD_PROJECT=your-project-id"
```

**Environment Variables:**

| Variable               | Description                       | Example                                |
| ---------------------- | --------------------------------- | -------------------------------------- |
| `BUNDLER_URL`          | Base URL of the bundler endpoint  | `https://eth-mainnet.g.alchemy.com/v2` |
| `GOOGLE_CLOUD_PROJECT` | GCP project ID for Secret Manager | `owl-production`                       |
| `PORT`                 | HTTP port (auto-set by Cloud Run) | `8080`                                 |

### Verify Deployment

```bash
# Get service URL
SERVICE_URL=$(gcloud run services describe owl-bundler-relay \
  --region us-west2 \
  --format 'value(status.url)')

# Test health endpoint
curl $SERVICE_URL/health

# Test RPC endpoint
curl -X POST $SERVICE_URL/rpc \
  -H "Content-Type: application/json" \
  -H "x-openclaw-client: owl-agent" \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_supportedEntryPoints","params":[]}'
```

***

## Integration with OpenClaw Agent

The OpenClaw agent's `owl-wallet` MCP server uses the relay for all bundler interactions:

```typescript
// owl-wallet MCP server configuration
const bundlerClient = createBundlerClient({
  transport: http("https://owl-bundler-relay-xyz.run.app/rpc", {
    headers: {
      "x-openclaw-client": "owl-agent",
      "x-openclaw-version": "1.0.0"
    }
  })
});

// Send UserOperation through relay
const userOpHash = await bundlerClient.sendUserOperation({
  userOperation,
  entryPoint: ENTRYPOINT_ADDRESS_V07
});
```

**Key Points:**

* **No API key in agent code** — The relay injects it server-side
* **Automatic retry** — The relay handles transient bundler failures
* **Rate limiting awareness** — Agents should implement exponential backoff on 429 responses
* **Client header required** — All requests must identify themselves via `x-openclaw-client`

***

## Monitoring and Operations

### Cloud Run Metrics

Monitor these metrics in GCP Console:

* **Request count** — Track relay usage
* **Request latency** — P50/P95/P99 latencies to bundler
* **Error rate** — 4xx/5xx responses
* **Instance count** — Autoscaling behavior

### Secret Rotation

To rotate the bundler API key:

```bash
# Create new secret version
echo -n "new-api-key" | gcloud secrets versions add owl-bundler-api-key \
  --data-file=-

# Restart Cloud Run service to pick up new version
gcloud run services update owl-bundler-relay \
  --region us-west2
```

The relay fetches the latest secret version on startup and caches it in memory.

### Rate Limit Tuning

To adjust rate limits, modify the `RATE_LIMIT_PER_MINUTE` environment variable:

```bash
gcloud run services update owl-bundler-relay \
  --region us-west2 \
  --set-env-vars "RATE_LIMIT_PER_MINUTE=120"
```

Default is 60 requests/minute per IP.

***

## Troubleshooting

### 400: Missing x-openclaw-client header

**Cause:** Request missing required authentication header

**Solution:** Add header to all `/rpc` requests:

```bash
curl -H "x-openclaw-client: owl-agent" ...
```

***

### 403: Method not allowed

**Cause:** Attempting to call a non-whitelisted JSON-RPC method

**Solution:** Only use ERC-4337 bundler methods:

* `eth_sendUserOperation`
* `eth_estimateUserOperationGas`
* `eth_getUserOperationByHash`
* `eth_getUserOperationReceipt`
* `eth_supportedEntryPoints`

***

### 429: Rate limit exceeded

**Cause:** More than 60 requests/minute from the same IP

**Solution:** Implement exponential backoff in client code:

```typescript
async function sendWithRetry(userOp: UserOperation, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      return await bundlerClient.sendUserOperation(userOp);
    } catch (error) {
      if (error.status === 429 && i < retries - 1) {
        await sleep(2 ** i * 1000); // Exponential backoff
        continue;
      }
      throw error;
    }
  }
}
```

***

### 503: Secret unavailable

**Cause:** Cloud Run cannot access GCP Secret Manager

**Solution:** Verify service account permissions:

```bash
# Check IAM binding
gcloud secrets get-iam-policy owl-bundler-api-key

# Expected output should include:
# - member: serviceAccount:xxx-compute@developer.gserviceaccount.com
# - role: roles/secretmanager.secretAccessor
```

If missing, grant access:

```bash
gcloud secrets add-iam-policy-binding owl-bundler-api-key \
  --member="serviceAccount:YOUR-PROJECT-NUMBER-compute@developer.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor"
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.greenowl.money/agent-and-tools/agent/bundler_relay.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
