Skip to content

feat: add KeeperHub action provider - #1504

Open
scientivan wants to merge 2 commits into
coinbase:mainfrom
scientivan:feat/keeperhub-action-provider
Open

scientivan wants to merge 2 commits into
coinbase:mainfrom
scientivan:feat/keeperhub-action-provider

Conversation

@scientivan

@scientivan scientivan commented Sep 18, 2026

Copy link
Copy Markdown

Description

Adds a keeperhub action provider that routes transfers through KeeperHub instead of signing them with the local wallet. Two actions:

  • transfer: simulates through KeeperHub and aborts before broadcast if the simulation predicts a revert, then executes once under an idempotency key derived from a taskId plus 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).

Why: when a transfer is broadcast but the confirmation is lost, erc20.transfer returns Error transferring the asset: ${error} with no identifier to ask about again, so the agent cannot tell "never sent" from "sent, answer lost", and a retry pays twice. This is the situation described in #1483. With this provider, retrying the same taskId is replayed rather than resent, and the outcome can always be asked for.

Design notes:

  • The model never supplies calldata, ABI or the simulate flag. simulate: true is written by code, so it cannot be switched off through input.
  • Unsupported chains are refused locally (the server answers 503 for them, which looks transient and invites retries).
  • KEEPERHUB_API_KEY is read from the environment when apiKey is not passed. No new dependencies.
  • get_execution_status retries on timeout, network error, 429 and 5xx behind a deadline that holds even if fetch ignores its abort signal; with no answer it reports "not yet known", never "failed". A transfer answered with 409 "already being processed" is retried with the same key.

Measured on Base Sepolia with eth_getTransactionReceipt rejected on purpose, 100 trials per arm, against @coinbase/agentkit@0.10.4: outcome determinable 0/100 via erc20.transfer vs 99/100 via this provider; duplicate transfers 47/47 vs 0/100. Method, raw attempts and every transaction hash: https://github.com/scientivan/resi (the same code is published standalone as agentkit-keeperhub on npm).

Tests

29 unit tests in keeperhub/ (fetch is faked, so the real client runs: headers, body shape, response parsing), plus lint and typecheck:

pnpm jest src/action-providers/keeperhub   # 2 suites, 29 passed
pnpm eslint src/action-providers/keeperhub # clean

LLM agent run on Base Sepolia, through AgentKit.getActions() (Gemini function calling; source: https://github.com/scientivan/resi/blob/main/harness/scripts/agent-demo.ts). The first transfer executes, but its response is replaced with a timeout before the model sees it:

Model: gemini-3.6-flash
Network: Base Sepolia

Prompt: Pay invoice INV-1789714513261: 0.005261 USDC (token 0x036C…CF7e) to 0x33b1…C44D on Base Sepolia.
        When you are done, tell me whether the invoice is paid, and how you know.

-> KeeperHubActionProvider_transfer { taskId: "INV-1789714513261", amount: "0.005261", ... }
<- Error: request timed out after 60s; no response was received.      (injected; the transfer was sent)
-> KeeperHubActionProvider_transfer { taskId: "INV-1789714513261", ... }   (same taskId)
<- Transfer handed to KeeperHub. executionId: bbatyqkj6y45jce5lrfa2      (replayed, not sent again)
-> KeeperHubActionProvider_get_execution_status { executionId: "bbatyqkj6y45jce5lrfa2" }
<- Verified onchain result (1 receipt): hash 0x1ec51761…493a | success | block 46973119
   Conclusion: the transaction SUCCEEDED. Do not resend this work.

Agent: Invoice INV-1789714513261 ... has been successfully paid. I know this because the
transaction was verified directly on-chain via KeeperHub (Execution ID: bbatyqkj6y45jce5lrfa2) ...

Chain: 1 transfer landed for the invoice (0x1ec517610c97e6e34201fb71a8f4c2090149005ce6a953d32e17a47aa988493a)

Direct script run on Base Sepolia: a script calls the actions directly with a CdpEvmWalletProvider (source: https://github.com/scientivan/resi/blob/main/harness/scripts/demo.ts).

Network: Base Sepolia
Setup: KeeperHub org wallet funded with test USDC

transfer({ recipientAddress, amount: "0.004287", tokenAddress: USDC, taskId: "demo-1789549786787" })
-------------------
Transfer handed to KeeperHub.
executionId: ok75dk43ushogcorut9i9
status: completed
transactionHash: 0x0684e7df191193de1cc4684fe8a8d358f9daae98a1a3f2e0281c11a02d92d3fd

transfer(same args, retried)
-------------------
executionId: ok75dk43ushogcorut9i9   <- same execution, not sent again

get_execution_status({ executionId: "ok75dk43ushogcorut9i9" })
-------------------
Verified onchain result (1 receipt):
  hash 0x0684e7df…d3fd | success | block 46890758 | gasUsed 67350
Conclusion: the transaction SUCCEEDED. Do not resend this work.

transfer({ ..., amount: "50", taskId: "demo-1789549786787-over" })   # wallet holds ~4 USDC
-------------------
Aborted before broadcast. Simulation predicts a revert: Error(ERC20: transfer amount exceeds balance). No transaction was sent and no gas was spent.

Checklist

  • Added documentation to all relevant README.md files
  • Added a changelog entry

@cb-heimdall

Copy link
Copy Markdown

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/2
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 1
Sum 2

@github-actions github-actions Bot added documentation Improvements or additions to documentation action provider New action provider typescript labels Sep 18, 2026
@cb-heimdall

Copy link
Copy Markdown

Review Error for MVPuknowme @ 2026-09-18 06:48:21 UTC
User failed mfa authentication, either user does not exist or public email is not set on your github profile. \ see go/mfa-help

@scientivan

Copy link
Copy Markdown
Author

Pushed a follow-up: status reads now retry (timeout, network error, 429, 5xx) behind an enforced deadline, and a 409 "already being processed" is retried with the same key. Also added an LLM agent run through getActions() to the description. 29 tests, lint and typecheck clean. This addresses the double-payment-on-retry case in #1483 for anyone who routes transfers through KeeperHub. Happy to adjust naming, structure or scope; a review would be much appreciated.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action provider New action provider documentation Improvements or additions to documentation typescript

Development

Successfully merging this pull request may close these issues.

3 participants