Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions typescript/.changeset/keeperhub-action-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@coinbase/agentkit": patch
---

Added a KeeperHub action provider with `transfer` (simulate, then execute once under an idempotency key derived from the work) and `get_execution_status` (outcome with receipts re-read from chain)
1 change: 1 addition & 0 deletions typescript/agentkit/src/action-providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from "./erc721";
export * from "./erc8004";
export * from "./farcaster";
export * from "./jupiter";
export * from "./keeperhub";
export * from "./messari";
export * from "./pyth";
export * from "./moonwell";
Expand Down
52 changes: 52 additions & 0 deletions typescript/agentkit/src/action-providers/keeperhub/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# KeeperHub Action Provider

This directory contains the **KeeperHubActionProvider**, which routes transfers through [KeeperHub](https://keeperhub.com) instead of signing them with the local wallet, and lets the agent ask afterwards what actually happened.

## Directory Structure

```
keeperhub/
├── keeperHubActionProvider.ts # Provider with transfer and get_execution_status
├── keeperHubActionProvider.test.ts # Provider tests (fetch is faked, the real client runs)
├── keeperHubClient.ts # Thin REST client and idempotency key derivation
├── keeperHubClient.test.ts # Idempotency key tests
├── constants.ts # Base URL, supported chains
├── schemas.ts # Action schemas
├── index.ts # Main exports
└── README.md # This file
```

## Actions

- `transfer`: simulates the transfer through KeeperHub, aborts before broadcast if the simulation predicts a revert, then executes once under an idempotency key derived from `taskId` and the fields that decide the onchain effect. Returns an `executionId`.
- `get_execution_status`: answers "did the money move?" for an `executionId`, with receipts re-read from chain: succeeded, reverted, or not yet known (which is not the same as failed).

## Why

When a transfer is broadcast but the confirmation is lost (for example, receipt polling fails), `erc20.transfer` returns an error string with no identifier to ask about again. The agent cannot tell "never sent" from "sent, answer lost", so a retry can pay twice (see #1483). With this provider a retry of the same `taskId` is replayed, not resent, and the outcome can always be asked for.

Measured on Base Sepolia, 100 trials per arm with `eth_getTransactionReceipt` rejected on purpose: the outcome was determinable in 0 of 100 trials through `erc20.transfer` and in 99 of 100 through this provider; duplicate transfers 47 of 47 vs 0 of 100. Method and every transaction hash: https://github.com/scientivan/resi

## Configuration

```typescript
import { keeperHubActionProvider } from "@coinbase/agentkit";

const provider = keeperHubActionProvider({
apiKey: process.env.KEEPERHUB_API_KEY, // organisation key, prefixed kh_
});
```

`KEEPERHUB_API_KEY` is read from the environment when `apiKey` is omitted.

## Network Support

Any EVM network KeeperHub supports, including Base, Base Sepolia, Ethereum, Sepolia, Arbitrum, Optimism and Polygon. See `constants.ts` for the full list. Unsupported chains are refused locally rather than sent to the server.

## Notes

- The simulation flag is set by code, never by model input, so it cannot be switched off.
- `taskId` is the durable handle. Recovery through the same `taskId` lasts 24 hours; after that the same key executes again.
- `get_execution_status` retries on timeout, network error, 429 and 5xx, behind a deadline that holds even if fetch ignores its abort signal. If no answer is obtained it reports "not yet known", never "failed".
- A transfer answered with 409 "already being processed" is retried with the same idempotency key, so it cannot execute twice.
- Only transfers are supported. Contract calls and protocol actions are not.
49 changes: 49 additions & 0 deletions typescript/agentkit/src/action-providers/keeperhub/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/** KeeperHub service base URL. */
export const KEEPERHUB_BASE_URL = "https://app.keeperhub.com";

/**
* Chains KeeperHub supports as of 16 Sep 2026, read from
* `GET https://app.keeperhub.com/api/chains` (public endpoint, no auth).
*
* Gnosis (100) is deliberately absent: KeeperHub does not support it, and
* listing it would only make actions fail much later.
*/
export const SUPPORTED_CHAIN_IDS = [
1,
10,
56,
137,
4217,
4663,
8453,
9745,
16661,
42161,
43114, // mainnet
97,
9746,
16602,
42431,
43113,
46630,
80002,
84532,
421614,
11155111,
11155420, // testnet
] as const;

export const NETWORK_ID_TO_CHAIN_ID: Record<string, number> = {
"ethereum-mainnet": 1,
"optimism-mainnet": 10,
"bnb-mainnet": 56,
"polygon-mainnet": 137,
"base-mainnet": 8453,
"arbitrum-mainnet": 42161,
"avalanche-mainnet": 43114,
"base-sepolia": 84532,
"ethereum-sepolia": 11155111,
"arbitrum-sepolia": 421614,
"optimism-sepolia": 11155420,
"polygon-amoy": 80002,
};
2 changes: 2 additions & 0 deletions typescript/agentkit/src/action-providers/keeperhub/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./keeperHubActionProvider";
export * from "./schemas";
Loading
Loading