bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 75쪽 중 33쪽
`1000` for `minFinalityThreshold` to use a
fast transfer. For standard finality, use `2000` instead and omit `PRE_FINALITY`
from the quote—see
[Transfer finality and fast burn allowance](/cctp/expanded-assets/concepts/architecture#transfer-finality-and-fast-burn-allowance).
On EVM, `destinationAddress` must be the raw packed 20-byte address. Do not
ABI-encode it to 32 bytes.
```typescript TypeScript theme={null}
const transferAbi = [
{
name: "crossChainTransfer",
type: "function",
stateMutability: "payable",
inputs: [
{ name: "tokenId", type: "bytes32" },
{ name: "amount", type: "uint256" },
{ name: "destinationDomain", type: "uint32" },
{ name: "destinationAddress", type: "bytes" },
{ name: "destinationCaller", type: "bytes32" },
{ name: "minFinalityThreshold", type: "uint32" },
{
name: "claim",
type: "tuple",
components: [
{ name: "signedQuote", type: "bytes" },
{ name: "refundAddress", type: "address" },
],
},
{ name: "autoExecuteHookData", type: "bool" },
{ name: "hookData", type: "bytes" },
],
outputs: [],
},
] as const;
const destinationAddress = encodePacked(["address"], [recipientOnArc]);
const ZERO_BYTES32 =
"0x0000000000000000000000000000000000000000000000000000000000000000" as const;
const claim = {
signedQuote: quote.signedQuote,
refundAddress: zeroAddress,
};
const feeTotalAmount = BigInt(quote.feeTotalAmount);
const transferHash = await sepoliaWallet.writeContract({
address: SEPOLIA_CCTS,
abi: transferAbi,
functionName: "crossChainTransfer",
args: [
tokenId,
transferAmount,
ARC_DOMAIN,
destinationAddress,
ZERO_BYTES32,
1000,
claim,
false,
"0x",
],
value: feeTotalAmount,
});
console.log(`Source transaction: ${transferHash}`);
```
## Step 8: Complete the transfer on Arc Testnet
Choose the completion path that matches the fee quote you requested in Step 6.
<Tabs>
<Tab title="Forwarded mint">
When the quote includes `FORWARD`, Circle submits the destination mint. Resolve
the token on Arc Testnet and wait until the recipient balance increases.
```typescript TypeScript theme={null}
const destinationTokenAddress = await arcPublic.readContract({
address: ARC_CCTS,
abi: serviceAbi,
functionName: "resolveTokenAddress",
args: [tokenId],
});
const startingBalance = await arcPublic.readContract({
address: destinationTokenAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: [recipientOnArc],
});
async function waitForDestinationBalance(
maxAttempts: number = 60,
): Promise<void> {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const balance = await arcPublic.readContract({
address: destinationTokenAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: [recipientOnArc],
});
if (balance >= startingBalance + transferAmount) {
console.log(
`Mint complete on Arc Testnet. Destination balance: ${balance}`,
);
return;
}
await new Promise((r) => setTimeout(r, 5000));
}
throw new Error("Forwarded mint did not complete in time");
}
await waitForDestinationBalance();
```
</Tab>
<Tab title="Direct mint">
Poll the Iris API until the attestation is ready, then submit `receiveMessage`
to the Arc Testnet `MessageTransmitterV2`. For the contract address and ABI, see
[Contract Addresses](/cctp/expanded-assets/references/contract-addresses) and
the [CCTP Contract Interfaces](/cctp/references/contract-interfaces).
```typescript TypeScript theme={null}
type IrisMessage = {
status: "pending_confirmations" | "complete";
attestation: `0x${string}`;
message: `0x${string}`;
};
type IrisResponse = { messages: IrisMessage[] };
async function pollAttestation(
sourceDomain: number,
txHash: `0x${string}`,
maxAttempts: number = 60,
): Promise<IrisMessage> {
const url = `${IRIS_BASE}/v2/messages/${sourceDomain}?transactionHash=${txHash}`;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const res = await fetch(url);
if (res.ok) {
const body = (await res.json()) as IrisResponse;
const msg = body.messages?.[0];
if (msg && msg.status === "complete") {
return msg;
}
}
await new Promise((r) => setTimeout(r, 5000));
}
throw new Error(`Attestation for ${txHash} did not complete in time`);
}
const attestation = await pollAttestation(ETHEREUM_DOMAIN, transferHash);
const messageTransmitterAbi = [
{
name: "receiveMessage",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "message", type: "bytes" },
{ name: "attestation", type: "bytes" },
],
outputs: [{ name: "success", type: "bool" }],
},
] as const;
const receiveHash = await arcWallet.writeContract({
address: ARC_MESSAGE_TRANSMITTER,
abi: messageTransmitterAbi,
functionName: "receiveMessage",
args: [attestation.message, attestation.attestation],
});
await arcPublic.waitForTransactionReceipt({ hash: receiveHash });
console.log(`Mint complete on Arc Testnet: ${receiveHash}`);
```
</Tab>
</Tabs>
After the transfer completes, the recipient holds the transferred EURC on Arc
Testnet. Verify by reading the recipient's balance on the Arc Testnet deployment
(`0x89b50855Aa3bE2f677Cd6303CeC089b5f319D72A`).
# CCTP for non-USDC contract addresses
Source: https://developers.circle.com/cctp/expanded-assets/references/contract-addresses
Deployed CCTP for non-USDC contract addresses across supported blockchains and environments.
`TokenManager` and `CrossChainToken` contracts are deployed per token. To
discover their addresses for a given `tokenId`, call
[`resolveTokenManager`](/cctp/expanded-assets/references/contract-reference) and
[`resolveTokenAddress`](/cctp/expanded-assets/references/contract-reference) on
the `CrossChainTokenService` for the local domain. For contract interfaces and
method signatures, see
[`CrossChainTokenService` Interface](/cctp/expanded-assets/references/contract-reference).
## Mainnet contract addresses
### `CrossChainTokenService`
Mainnet `CrossChainTokenService` is deployed via CREATE3 at the same address on
each listed chain.
| Blockchain | Domain | Address |
| - | - | - |
| **Ethereum** | 0 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://etherscan.io/address/0x431871229103b780868f8C6BB820cd16ECf942BC) |
| **Avalanche** | 1 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://snowtrace.io/address/0x431871229103b780868f8C6BB820cd16ECf942BC) |
| **OP Mainnet** | 2 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://optimistic.etherscan.io/address/0x431871229103b780868f8C6BB820cd16ECf942BC) |
| **Arbitrum** | 3 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://arbiscan.io/address/0x431871229103b780868f8C6BB820cd16ECf942BC) |
| **Base** | 6 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://basescan.org/address/0x431871229103b780868f8C6BB820cd16ECf942BC) |
| **Polygon PoS** | 7 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://polygonscan.com/address/0x431871229103b780868f8C6BB820cd16ECf942BC) |
| **Arc** | 26 | [`0x431871229103b780868f8C6BB820cd16ECf942BC`](https://explorer.arc.io/address/0x431871229103b780868f8C6BB820cd16ECf942BC) |
## Testnet contract addresses
### `CrossChainTokenService`
Sandbox `CrossChainTokenService` is deployed via CREATE3 at the same address on
each listed testnet.
| Blockchain | Domain | Address |
| - | - | - |
| **Ethereum Sepolia** | 0 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://sepolia.etherscan.io/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) |
| **Avalanche Fuji** | 1 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://testnet.snowtrace.io/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) |
| **OP Sepolia** | 2 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://sepolia-optimism.etherscan.io/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) |
| **Arbitrum Sepolia** | 3 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://sepolia.arbiscan.io/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) |
| **Base Sepolia** | 6 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://sepolia.basescan.org/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) |
| **Polygon PoS Amoy** | 7 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://amoy.polygonscan.com/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) |
| **Arc Testnet** | 26 | [`0x63753E722bd2C2A5DF6EE19C5106662208B81077`](https://explorer.testnet.arc.io/address/0x63753E722bd2C2A5DF6EE19C5106662208B81077) |
## Underlying CCTP contracts
CCTP for non-USDC routes messages through the existing CCTP
`MessageTransmitterV2` contract on each domain. The CCTP for non-USDC domain ID
matches the
[CCTP domain ID](/cctp/concepts/supported-chains-and-domains#domain-identifiers)
for the same blockchain. For `MessageTransmitterV2` addresses, see
[CCTP Contract Addresses](/cctp/references/contract-addresses).
# CCTP for non-USDC contract reference
Source: https://developers.circle.com/cctp/expanded-assets/references/contract-reference
CCTP for non-USDC contracts, types, errors, and non-obvious behaviors integrators need.
The following tables index contracts, types, errors, and sentinels, and document
behaviors that aren't obvious from reading the source.
For contract addresses, see
[Contract Addresses](/cctp/expanded-assets/references/contract-addresses).
## Contracts
| Contract | Purpose |
| - | - |
| `CrossChainTokenService` | Singleton entry point on each domain. Configures tokens, deploys remote token contracts, and routes crosschain transfers. |
| `TokenManager` | Deployed per token on each domain. Moves tokens during transfers; enforces rate limit, max transfer amount, and pause. |
| `CrossChainToken` | ERC-20 with `ERC20Permit` deployed for natively crosschain tokens. Holders call `crossChainTransfer` directly. |
| `CrossChainTokenExecutable` | Optional abstract helper for destination-domain hook receivers. Integrators can implement `executeWithCrossChainToken` directly. |
| `IDenylistProvider` | Interface for service-level and per-token denylist providers. One view: `isDenylisted(address)`. |
## Types
### `QuoteClaim`
Passed to every payable entrypoint to attach a signed fee quote and a refund
address.
```solidity Solidity theme={null}
struct QuoteClaim {
bytes signedQuote;
address payable refundAddress;
}
```
* `signedQuote`: version-prefixed, ABI-encoded fee quote returned by the Iris
fee-quote endpoint
(`POST /v2/quote/cctpx/{tokenId}/{sourceDomain}/{destinationDomain}`). Pass
the bytes value verbatim.
* `refundAddress`: receives fee refunds. Set to `address(0)` to opt out of
refund attribution. Non-zero values are screened against the USDC denylist on
collection; the call reverts if denied.
### `DeploymentParams`
Passed when configuring or deploying a token.
```solidity Solidity theme={null}
struct DeploymentParams {
TokenManagerType tokenManagerType;
bytes tokenManagerOwner;
bytes tokenManagerOperator;
bytes tokenOwner;
bytes tokenMinter;
uint256 initialSupply;
TokenManagerSettings tokenManagerSettings;
}
struct TokenManagerSettings {
uint256 rateLimit;
uint256 maxTransferAmount;
}
```
Address fields are `bytes` rather than `address` so the struct can carry non-EVM
addresses for non-EVM domains.
Some deployments omit `initialSupply` from the onchain ABI (for example current
sandbox `registerCustomToken`). Encoding a field the contract does not expect
produces an empty revert. Always verify the live ABI or function selector before
copying struct layouts from docs.
### `TokenManagerType`
```solidity Solidity theme={null}
enum TokenManagerType {
NATIVE_CROSSCHAIN_TOKEN, // 0
BURN_MINT, // 1
LOCK_UNLOCK // 2
}
```
See
[Token manager types](/cctp/expanded-assets/concepts/architecture#token-manager-types)
for when each applies.
### `ConnectionType`
```solidity Solidity theme={null}
enum ConnectionType {
OWNERLESS,
CUSTOM
}
```
Distinguishes the two integration models. See
[Ownerless and custom tokens](/cctp/expanded-assets/concepts/ownerless-vs-custom-tokens).
## Errors
Integrators should monitor for these reverts:
| Error | Source | Meaning |
| - | - | - |
| `TokenNotRegistered(bytes32 tokenId)` | `CrossChainTokenService` | `resolveTokenManager` or `resolveTokenAddress` called with a `tokenId` that has no deployment. |
| `HookDataExecutionFailed(bytes32 tokenId, address recipient, bytes reason)` | `CrossChainTokenService` | Recipient's hook reverted, or returned a value other than the `EXECUTE_SUCCESS` sentinel. |
| `DenylistedAddress(address account)` | `CrossChainTokenService` | Service-level denylist rejected `msg.sender`, `tx.origin`, or the inbound recipient. |
| `AccountDenylisted(address account)` | `CrossChainToken` | Per-token denylist rejected `from`, `to`, or the spender on an ERC-20 movement. |
## Sentinels
### `EXECUTE_SUCCESS`
```solidity Solidity theme={null}
bytes32 constant EXECUTE_SUCCESS = keccak256("circle-cctpx-execute-success");
```
The value that `executeWithCrossChainToken` must return. The service verifies
this on every hook call. Implement the public entrypoint on your receiver,
restrict `msg.sender` to the local `CrossChainTokenService`, and return this
sentinel. See
[Build a hook receiver](/cctp/expanded-assets/howtos/build-hook-receiver).
When the contracts are open-sourced, an optional `CrossChainTokenExecutable`
base can wrap the same pattern (external entrypoint returns the sentinel;
subclasses override an internal hook). Until then, implement the write ABI
directly.
## Non-obvious behaviors
These behaviors aren't apparent from reading the source but commonly trip up
integrators.
### `approve` goes to the `TokenManager`, not the service
For `BURN_MINT` and `LOCK_UNLOCK` transfers, the source token is moved by the
per-token `TokenManager`. ERC-20 approvals must target the `TokenManager`
address (returned by `resolveTokenManager(tokenId)`), not the
`CrossChainTokenService`.
### `destinationAddress` is packed address bytes, not ABI-encoded
`crossChainTransfer` takes `destinationAddress` as `bytes`. On EVM, pass the raw
20-byte address (`abi.encodePacked(recipient)` /
`encodePacked(["address"], [recipient])`). Do **not** use `abi.encode` /
`encodeAbiParameters([{ type: "address" }], …)`, which produces a 32-byte
left-padded word and breaks destination delivery. This is separate from
application `hookData`, which may still be ABI-encoded.
### Custom `tokenId` uses `customTokenDeploySalt`
For custom tokens, the `tokenId` is
`crossChainTokenId(deployer, customTokenDeploySalt(deployer, salt))`. Calling
`crossChainTokenId(deployer, salt)` alone yields a different id. Native
crosschain tokens use `crossChainTokenId(deployer, salt)` directly.
### `resolveTokenManager` and `resolveTokenAddress` revert on miss
Both revert with `TokenNotRegistered(tokenId)` when the predicted address has no
deployed code. They do not return `address(0)`. To probe for existence without
throwing, wrap the call in `try`/`catch` or check `extcodesize` on the
deterministic CREATE3-predicted address.
### `CrossChainToken` denylist provider replaces, it does not chain
`CrossChainToken` holds a single denylist provider slot. Calling
`updateDenylistProvider` overwrites the previous value rather than adding to it.
To combine multiple lists, deploy a composite provider that internally calls
each upstream. The service-level denylist still runs in addition to the
per-token layer.
### Ownerless deploys inherit Circle's denylist provider
When `deployRemoteOwnerlessToken` lands on a remote domain, the new
`CrossChainToken`'s `denylistProvider` is initialized to the service's provider.
### `tokenManagerOwner`, `tokenManagerOperator`, and related fields are `bytes`
The fields are typed `bytes` instead of `address` so the same struct works for
non-EVM domains in the future. On EVM, pack the address with
`abi.encodePacked(addr)` / `encodePacked(["address"], [addr])` (20 bytes). Do
not use `encodeAbiParameters([{ type: "address" }], [addr])`, which ABI-pads to
32 bytes and is not equivalent.
For an **existing** ERC-20 configured with `registerCustomToken`, pass empty
bytes (`0x`) for `tokenOwner` and `tokenMinter`; grant the `TokenManager` minter
role on the token in a separate step.
### `sourceAddress` in hook callbacks is `bytes`
Same reason as the deployment params: the hook signature carries the source
address as `bytes` so receivers can decode senders from non-EVM blockchains.
### Hook execution can be reorged on fast transfers
When a hook executes with `finalityThresholdExecuted < 2000`, the source
blockchain transaction is still pre-finality and could be reorged. Receivers
that opt into fast hook execution must design their logic to tolerate rollback
by deferring irreversible side-effects until finality is confirmed.
## Function index
### `CrossChainTokenService`
| Group | Functions |
| - | - |
| Crosschain transfer | `crossChainTransfer`, `transmitCrossChainTransfer` |
| Resolve and inspect | `resolveTokenManager`, `resolveTokenAddress`, `getOwnerlessTokenId`, `crossChainTokenId`, `customTokenDeploySalt`, `ownerlessTokenDeploySalt`, `isTrustedDomain` |
| Trust and limits | `addTrustedDomain`, `removeTrustedDomain`, `setRateLimits` |
| Denylist | `denylistProvider`, `setDenylistProvider`, `initializeDenylistProvider` |
| Roles and upgrade | `feeService`, `messageTransmitter`, `operator`, `transferOwnership`, `acceptOwnership`, `pause`, `unpause`, `upgradeToAndCall` |
### `TokenManager`
| Group | Functions |
| - | - |
| Transfer limits | `setRateLimit`, `setMaxTransferAmount`, `setRateLimitWindow` |
| Pause | `pause`, `unpause`, `updatePauser` |
| Implementation | `upgradeToCustomImplementation`, `resetToDefaultImplementation` |
| Ownership | `transferOwnership`, `acceptOwnership`, `transferOperatorship` |
| Circle assignment | `assignOwnership`, `assignOperatorship` (only callable on ownerless `TokenManager`s) |
| Service-only | `takeToken`, `giveToken` (called by `CrossChainTokenService`; not callable by integrators) |
### `CrossChainToken`
| Group | Functions |
| - | - |
| Crosschain | `crossChainTransfer`, `crossChainTransferFrom` |
| Mint and burn | `mint`, `burn`, `burnFrom`, `addMinter`, `removeMinter`, `transferMintership` |
| Denylist | `denylistProvider`, `isDenylisted`, `updateDenylistProvider` |
| Implementation | `upgradeToCustomImplementation`, `resetToDefaultImplementation` |
| Ownership | `transferOwnership`, `acceptOwnership` |
| Circle assignment | `assignOwnership`, `assignOperatorship` (only callable on ownerless `CrossChainToken`s) |
| ERC-20 surface | Standard ERC-20 (`transfer`, `transferFrom`, `approve`, `allowance`, `balanceOf`, `totalSupply`) plus `ERC20Permit` |
### `CrossChainTokenExecutable`
Optional helper (when published). Integrators may instead implement the public
entrypoint directly.
| Group | Functions |
| - | - |
| Hook entry point | `executeWithCrossChainToken` (returns `EXECUTE_SUCCESS`) |
| Hook implementation | `_executeWithCrossChainToken` (override when using the abstract base) |
# Get the fast transfer allowance
Source: https://developers.circle.com/cctp/howtos/get-fast-transfer-allowance
Check the remaining Fast Transfer allowance for USDC transfers
This how-to shows you how to retrieve the remaining Fast Transfer allowance
using the [CCTP API](/api-reference/cctp/all/get-fast-burn-usdc-allowance). The
Fast Transfer allowance is Circle's mechanism for backing faster-than-finality
USDC transfers before burns reach hard finality on source chains.
## Prerequisites
Before you begin, ensure you have:
* Installed cURL on your development machine
## Get the Fast Transfer allowance
Call the
[`GET /v2/fastBurn/USDC/allowance`](/api-reference/cctp/all/get-fast-burn-usdc-allowance)
endpoint to retrieve the current remaining Fast Transfer allowance.
**Example request**
```shell Shell theme={null}
curl --request GET \
--url 'https://iris-api-sandbox.circle.com/v2/fastBurn/USDC/allowance' \
--header 'Accept: application/json'
```
**Response**
```json theme={null}
{ "allowance": 99999999225.24174, "lastUpdated": "2025-12-02T1프로젝트로 돌아가기