How to Migrate CCTP V1 to V2 Before December 2026
Circle is phasing out CCTP V1 in favor of V2. This practical migration guide covers contract changes, fees, Fast Transfer, API updates, attestations, testing, and the 2026 deadline.
On this page
If your application still sends USDC through Circle’s CCTP V1 contracts, the migration deadline is no longer theoretical. Circle says CCTP V1 (Legacy) deprecation begins on October 31, 2026, and the contracts will be paused on December 1, 2026. The migration is a real integration change: V2 uses new contract addresses, different function parameters, new API endpoints, and a different way to retrieve attestations.
Check whether your integration still depends on CCTP V1
Start by searching your codebase for the legacy CCTP contracts and API calls rather than assuming that a dependency update is enough. The main contract names to look for are TokenMessenger, MessageTransmitter, TokenMinter, and Message. Circle maps these to TokenMessengerV2, MessageTransmitterV2, TokenMinterV2, and MessageV2, respectively. The V2 contracts are deployed at different addresses, so changing only an import or package version will not complete the migration.
Also search for V1 API paths such as /v1/attestations/ and /v1/messages/. If your application extracts a MessageSent event from a transaction receipt, hashes the message manually, and then polls an attestation endpoint, that is another strong sign that the old workflow is still embedded in your application.
Replace the V1 contract addresses and interfaces first
Once you have identified the legacy integration, switch the contract configuration to the V2 deployments for every supported source and destination chain you use. Do not hard-code addresses from an old configuration file without checking Circle’s current contract-address documentation, because crosschain applications usually maintain separate addresses for each blockchain domain.
The interface change is more significant than the address change. CCTP V2 modifies depositForBurn() by adding destinationCaller, maxFee, and minFinalityThreshold. The first identifies the address allowed to complete the receive operation on the destination chain, while the other two let the application control fees and the required transfer finality.
depositForBurn(
amount,
destinationDomain,
mintRecipient,
burnToken,
destinationCaller,
maxFee,
minFinalityThreshold
)V2 also removes depositForBurnWithCaller(); its destination-caller restriction is now represented by the destinationCaller argument on depositForBurn(). The old replaceDepositForBurn() function has no V2 equivalent, so any application depending on that behavior needs a separate design decision rather than a mechanical rename.
Choose Standard or Fast Transfer deliberately
The new minFinalityThreshold parameter changes how your application thinks about a transfer. A value of 1000 selects the Fast Transfer path, while 2000 selects Standard Transfer. Fast Transfer is designed for lower latency but can carry a variable fee, while Standard Transfer prioritizes finalized-chain confirmation and can be more cost-efficient depending on the route.
This is not a parameter that should simply be copied from a sample application. Decide what your product needs. A trading interface may benefit from faster settlement, while a treasury operation that can wait longer may prefer the Standard path. Your user interface should also make the resulting cost and speed understandable instead of silently choosing the faster option.
Move fee calculation out of hard-coded values
V2 introduces fee handling that should be part of your transfer flow rather than an afterthought. Circle provides a V2 fee endpoint that reports the available transfer options for a source and destination domain. Fast Transfer fees vary by route, so a value that worked yesterday on one chain pair should not be treated as a universal constant.
Before calling depositForBurn(), retrieve the applicable fee and set maxFee high enough for the transfer to succeed. If the actual fee exceeds the maximum you supplied, the source transaction can revert and the USDC is not burned. This makes fee estimation a prerequisite for a reliable transfer rather than something to calculate after submission.
const transferAmount = 10_000_000n; // 10 USDC
const maxFee = calculatedFeeWithBuffer;
await tokenMessenger.depositForBurn(
transferAmount,
destinationDomain,
mintRecipient,
burnToken,
destinationCaller,
maxFee,
1000 // Fast Transfer
);The example deliberately leaves fee calculation outside the contract call. In production, obtain current route information from the V2 API and convert the returned fee into the same base units used by the burn token. That keeps the transaction logic tied to current network conditions instead of a stale constant.
Replace the old attestation workflow
The biggest application-level simplification in V2 is the message retrieval flow. With V1, an integration commonly obtained the transaction receipt, located the MessageSent event, extracted the message bytes, calculated a message hash, and then requested an attestation using that hash. V2 provides a messages endpoint that can return the message, attestation, and decoded information using the source domain and transaction hash.
That means your application can reduce the amount of custom event parsing and message decoding it maintains. The conceptual V2 flow is:
- Submit the V2
depositForBurn()transaction. - Keep the resulting transaction hash and source domain.
- Query the V2 messages endpoint using that transaction information.
- Wait until the response contains the required attestation.
- Submit the message and attestation to
MessageTransmitterV2on the destination chain. - Verify the destination transaction before reporting the transfer as complete.
This is also a good point to separate transaction submission from transfer completion in your application state. A successful burn transaction means the source-side operation succeeded; it does not by itself mean the recipient has received USDC on the destination chain.
Account for expired Fast Transfers and re-attestation
Fast Transfer introduces another operational case that V1 applications may never have needed to handle. V2 provides a re-attestation endpoint for situations such as an expired Fast Transfer burn or a change in the finality path. The message format includes an expiration block, so a transfer that sits unresolved should not be treated as permanently stuck simply because the first attestation attempt expired.
Your backend should therefore distinguish at least three states: the source burn succeeded, the message has a usable attestation, and the destination redemption succeeded. That state model makes retries safer and prevents a temporary attestation problem from being reported to the user as a failed payment.
Test the migration on a testnet before switching production
Do not replace the production contracts first and discover interface problems through real funds. Run the complete flow against a supported test environment and test both transfer speeds if your application will expose both options. Confirm that the destination recipient receives the expected amount, that your fee calculation survives changing route data, and that your backend can recover when attestation availability is delayed.
Also test the negative cases. Try an insufficient maxFee, an incorrect destination caller, a transaction whose attestation is still pending, and a destination transaction that needs to be retried. A migration is complete only when the failure paths work as predictably as the successful path.
Remove V1 assumptions before the October cutoff
Circle's current migration guidance says V1 deprecation begins October 31, 2026, with the V1 contracts scheduled to be paused on December 1. Circle also says pending redemptions will remain available during the phase-out, but new burns will be progressively restricted as the system winds down. That means waiting until December is a poor migration strategy even if users are unlikely to lose access to already-pending transfers.
Before the cutoff, remove V1 addresses and ABI dependencies from production configuration, replace V1 API calls, update monitoring to understand V2 transfer states, and make sure your fee logic handles current route data. If you use a library that wraps CCTP, verify which contract version it actually targets rather than assuming the library has migrated because its package is current.
Use the migration to simplify the integration
The safest CCTP V1 migration is not a search-and-replace exercise. V2 changes the contract layer, transfer-speed selection, fee model, and attestation workflow at the same time, so the migration is a chance to remove custom logic that V2 now makes unnecessary.
For teams that want less protocol-specific code, Circle's App Kit also provides a higher-level bridging interface that can handle contract configuration and attestation retrieval. Teams that need direct control can stay with the V2 contracts and APIs. Either way, the useful deadline is October 31: by then, a production application should already be exercising the V2 path rather than relying on the shrinking V1 window.
Written by


