CdpEvmWalletProvider.sendTransaction omits idempotencyKey, which the CDP SDK accepts — an agent retry after a lost response sends the transfer twice (cdpEvmWalletProvider.ts:195)
Maintainers usually reply within 1 day
@Baophan00 is already working on this.
Since Oct 1, 2026.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 35/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- typescript
- Domain
- backend-api-design, payments
Research direction
Read typescript/agentkit/src/wallet-providers/cdpEvmWalletProvider.ts at sendTransaction and nativeTransfer, then compare the delegation in cdpSmartWalletProvider.ts and the SDK’s SendTransactionOptions. The open question is where a stable key for the same transaction intent should come from; done means retries reuse that key while distinct transfers do not collide. The issue does not name tests, so locate the relevant provider tests before validating the behavior.
Written by the indexing model from the issue text.
Description
Summary
CdpEvmWalletProvider.sendTransaction does not pass an idempotency key, although the CDP SDK it calls accepts one and Coinbase's own docs use idempotency keys elsewhere in this same repository.
// typescript/agentkit/src/wallet-providers/cdpEvmWalletProvider.ts:195-206
async sendTransaction(transaction: TransactionRequest): Promise<Hex> {
const result = await this.#cdp.evm.sendTransaction({
address: this.#serverAccount.address,
transaction: { to, value, data },
network: this.getCdpSdkNetwork(),
// no idempotencyKey
});
return result.transactionHash;
}
The SDK signature (@coinbase/cdp-sdk, _types/actions/evm/sendTransaction.d.ts) is:
export interface SendTransactionOptions {
address: Address;
transaction: Hex | TransactionRequestEIP1559;
network: SendEvmTransactionBodyNetwork;
/** The idempotency key. */
idempotencyKey?: string;
}
So the field exists, is documented, and is not used on the one path that moves value. nativeTransfer (:296) routes through this method, as does every ERC-20 transfer and contract write that reaches this provider. CdpSmartWalletProvider.nativeTransfer (:374) has the same shape.
Why this matters here more than in most SDKs
An agent framework's defining property is that the caller is a model inside a harness that retries. A dropped connection or a timeout after sendTransaction leaves the caller unable to distinguish "the transaction was never submitted" from "it was submitted and the response was lost". With no idempotency key, a retry is a second, independently valid transaction — a second transfer of real value. Nothing in either response marks it as a duplicate, and no replay guard fires, because the second submission genuinely is new.
This is also the case Coinbase already solved. The idempotency-key flow appears in this repo's own examples for wallet creation:
# python/examples/langchain-cdp-chatbot/chatbot.py:51-52
address=config.address, # Optional, will trigger idempotency flow if not provided
idempotency_key=config.idempotency_key,
Creating a wallet twice is an inconvenience. Sending value twice is not. The protection is applied to the cheaper failure and omitted from the expensive one.
What I checked
cdpEvmWalletProvider.ts:195-206—sendTransaction, noidempotencyKeyin the options object.cdpEvmWalletProvider.ts:296-302—nativeTransferdelegates to it unchanged.cdpSmartWalletProvider.ts:374-380— same delegation.@coinbase/[email protected]—SendTransactionOptions.idempotencyKey?: string, present and commented as "The idempotency key."- A repository-wide search for
idempotin TypeScript and Python sources: matches occur only in wallet-creation examples, never in a wallet provider or an action provider.
Limits, stated plainly
This is a code read. I have not run an agent against CDP, induced a timeout mid-transfer and observed two transfers — that experiment is what turns a mechanism into a measured rate, and I would want it before quoting one.
If the CDP backend deduplicates sendTransaction server-side by account and nonce independently of the key, then the omission is harmless and this reduces to a documentation gap — in which case I will publish that correction as prominently as the claim. I could not determine that from outside, and the SDK exposing the parameter suggests the backend does rely on it.
I have also not reviewed the Solana provider's equivalent path in the same detail, so I am not making a claim about it here.
On the fix
The fix is small and local to these providers, and there is a design choice inside it — where a stable key should come from so that a retry of the same intent reuses it while distinct transfers do not collide. That choice is the part that actually matters; the plumbing is trivial. I am not posting the patch.
I would rather explain that than be coy. I have filed seven of these over three weeks, each with the complete remedy and a failing test attached, free. Seven teams shipped fixes, the fastest in 4.3 hours. It built a real public record and it has not been a business, so the finding is free and the fix is the work now.
If it is useful: I read one money path end to end and return every finding tied to your own file and line numbers, each with its patch and a failing test in your own harness. Five working days, written only, no call. $1,200, and no invoice if the path is clean. The deliverable is specified up front so you can check the report against it: https://github.com/aurumflux20/seal/blob/main/docs/REVIEW-DELIVERABLE.md
And if you would rather simply have this one, say so on this thread and I will post it here for nothing. I am not going to withhold a payment-safety fix on a public repository, and I would think less of anyone who did. But it is what I sell, and asking costs you nothing.
Context on the seam: of ten agent-payment money paths read in three days, seven could charge a payer twice on an ambiguous outcome. The public record, with the evidence for each row and nobody named while their finding is open, is at https://aurumflux.co/retry-safety/
- Dominant language
- TypeScript
- Stars
- 1.3k
- Forks
- 842
- PR merge metrics
- No merged PRs in 30d
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from coinbase/agentkit
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
coinbase-agentkit 0.7.x fails on fresh install: imports solana.rpc.api, which solana>=0.37 removedPossibly taken @JulienKervarrec claimed this 17 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
x402 action provider: optional pre-pay spot-check hookPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 4/5 3-5 days Newbie friendliness 8/100
coinbase/agentkit#1548 · 3 comments ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 Half a day Newbie friendliness 45/100
coinbase/agentkit#1547 · 3 comments ·
Maintainers usually reply within 1 day
-
Community Action Provider: Agent Safe paid_fetch, x402 payments where the model only picks the URLOpen
Difficulty 4/5 1-2 days Newbie friendliness 15/100
coinbase/agentkit#1544 · 22 comments ·
Maintainers usually reply within 1 day
All issues in coinbase/agentkit
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
supadata-ai/mcp#27 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
capricorn86/happy-dom#2485 ·
Maintainers usually reply within 2 days
-
优化导入 OCR 模型选择文件的按钮样式Open
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
siyuan-note/siyuan#20430 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Albert-Weasker/niubigeo#194 ·
Maintainers usually reply within 1 day