Skip to content

fix: pass idempotencyKey on CDP value-moving paths - #1530

Open
Baophan00 wants to merge 1 commit into
coinbase:mainfrom
Baophan00:fix/cdp-idempotency-key
Open

Baophan00 wants to merge 1 commit into
coinbase:mainfrom
Baophan00:fix/cdp-idempotency-key

Conversation

@Baophan00

Copy link
Copy Markdown

Summary

CdpEvmWalletProvider.sendTransaction and CdpSmartWalletProvider.sendTransaction call the CDP SDK without an idempotency key, although SendTransactionOptions and SendUserOperationOptions both declare one. nativeTransfer delegates into those methods on both providers, so every value-moving path on the CDP providers reaches the SDK with no key.

An agent harness retries after an ambiguous outcome. With no key, a retry after a lost response is a second, independently valid transfer of the same value, and no replay guard fires because the second submission genuinely is new.

This forwards an optional caller-supplied key on both TypeScript providers and on the Python CdpEvmWalletProvider send / native-transfer paths.

Closes #1483.

Why the key is a parameter and not generated inside the provider

This is the part that decides whether the change does anything. A key minted inside sendTransaction is a new key on every call, including a retry — so it deduplicates nothing. The key has to be a property of the caller's logical intent: same intent, same key; new transfer, new key. Only the caller knows which of those it is holding, so the key is a parameter and the provider's default leaves it unset, which keeps existing behaviour byte-identical.

Files changed

  • typescript/agentkit/src/wallet-providers/cdpEvmWalletProvider.ts — sendTransaction and nativeTransfer accept an optional idempotencyKey and forward it to cdp.evm.sendTransaction.
  • typescript/agentkit/src/wallet-providers/cdpSmartWalletProvider.ts — same, forwarding to cdp.evm.sendUserOperation.
  • python/coinbase-agentkit/coinbase_agentkit/wallet_providers/cdp_evm_wallet_provider.py — send_transaction and native_transfer accept an optional idempotency_key.
  • Tests on all three paths, plus a changeset and a changelog.d entry.

Test plan

TypeScript (typescript/agentkit):

  • node node_modules/jest/bin/jest.js --no-cache --testMatch='**/*.test.ts' — 908 passed, 62 suites.
  • tsc --noEmit — clean.

Python (python/coinbase-agentkit):

  • uv run pytest -m "not (e2e or integration)" — 673 passed, 35 deselected.
  • ruff check and ruff format --check — clean.

The new tests were confirmed to fail on the unmodified code and pass with it, so they actually pin the behaviour rather than passing vacuously. Reverting the provider changes only:

Suite Without the fix With the fix
cdpEvmWalletProvider.test.ts 3 failed, 20 passed 23 passed
cdpSmartWalletProvider.test.ts 2 failed, 12 passed 14 passed
test_transactions.py (CDP EVM server wallet) 4 failed, 11 passed 15 passed

Limits, stated plainly

This is a code read plus unit tests against the mocked SDK. I have not induced a real timeout mid-transfer against CDP and counted two landing transfers — that would establish CDP's server-side dedup behaviour, which I cannot determine from outside. What this PR fixes is the local omission: the parameter the SDK exposes is now reachable from the provider that moves the value. If the CDP backend already deduplicates by account and nonce independently of the key, then this is a documentation-level gap and the tests here still describe the intended contract.

@Baophan00
Baophan00 requested a review from murrlincoln as a code owner October 1, 2026 12:05
@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 wallet provider New wallet provider python typescript labels Oct 1, 2026
CdpEvmWalletProvider.sendTransaction and CdpSmartWalletProvider.sendTransaction
call the CDP SDK without an idempotency key, although SendTransactionOptions and
SendUserOperationOptions both declare one. nativeTransfer delegates into those
methods on both providers, so every value-moving path reaches the SDK with no key.

An agent harness retries after an ambiguous outcome. With no key, a retry after a
lost response is a second, independently valid transfer of the same value and no
replay guard fires, because the second submission genuinely is new.

Forward an optional caller-supplied key on both TypeScript providers and on the
Python CdpEvmWalletProvider send/native-transfer paths. The key is not generated
inside the provider: a key minted per call would differ on the retry and
deduplicate nothing. It is a property of the caller's logical intent, so callers
pass it and the default keeps existing behaviour unchanged.

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

documentation Improvements or additions to documentation python typescript wallet provider New wallet provider

2 participants