Skip to main content
Technical reference for the custom stablecoin. The source code is available on GitHub at coinbase/custom-stablecoin. For code examples and common workflows, see Quickstart and Examples.

Overview

Each custom stablecoin is an ERC-20 token that extends the standard with additional capabilities:
StandardDescription
ERC-20Fungible token interface
ERC-2612Permit — gasless approvals via EIP-712 signatures
ERC-3009Transfer with authorization — meta-transaction transfers
ERC-7572Contract-level metadata URI
ERC-165Interface detection
Your stablecoin’s contract address is provided during onboarding. See Key Addresses for the testnet CBTUSD address.
Coinbase may update the stablecoin implementation over time. Your contract address does not change on update, and the public interface described below remains stable.

ERC-20 functions

transfer

function transfer(address to, uint256 amount) external returns (bool)
Transfers amount tokens from the caller to to.Failure scenarios:
ConditionError
msg.sender or to is blocklistedAddressBlocklisted(account)
Transfers are pausedEnforcedPause()
Caller has insufficient balanceERC20InsufficientBalance

transferFrom

function transferFrom(address from, address to, uint256 amount) external returns (bool)
Transfers amount tokens from from to to, deducting from the caller’s allowance.Failure scenarios:
ConditionError
msg.sender, from, or to is blocklistedAddressBlocklisted(account)
Transfers are pausedEnforcedPause()
Caller’s allowance is insufficientERC20InsufficientAllowance
from has insufficient balanceERC20InsufficientBalance

approve

function approve(address spender, uint256 amount) external returns (bool)
Sets the caller’s allowance for spender to amount.Failure scenarios:
ConditionError
msg.sender or spender is blocklistedAddressBlocklisted(account)

balanceOf

function balanceOf(address account) external view returns (uint256)
Returns the token balance of account.

totalSupply

function totalSupply() external view returns (uint256)
Returns the total token supply currently in circulation.

allowance

function allowance(address owner, address spender) external view returns (uint256)
Returns the remaining number of tokens spender is allowed to transfer on behalf of owner.

decimals

function decimals() public view returns (uint8)
Returns the number of decimal places used for token amounts (between 6 and 18, set at deployment).

name

function name() public view returns (string)
Returns the token name (set at deployment).

symbol

function symbol() public view returns (string)
Returns the token symbol (set at deployment).

Memo functions

These functions attach a 32-byte memo to a transfer. The Memo event is emitted immediately after the ERC-20 Transfer event. Memos are useful for correlating on-chain transactions with off-chain order IDs, payment references, or other business records.
Memo values are permanently visible on the public blockchain. Do not include sensitive information such as customer names, account numbers, or PII. If you need to attach sensitive data, encrypt it off-chain before encoding it as bytes32 and decrypt it off-chain when reading the event. Use only non-sensitive references (such as a hashed or opaque order ID) as the on-chain memo value.

transferWithMemo

function transferWithMemo(address to, uint256 amount, bytes32 memo) external
Transfers amount from the caller to to with a memo.

transferFromWithMemo

function transferFromWithMemo(address from, address to, uint256 amount, bytes32 memo) external
Transfers amount from from to to with a memo, deducting from the caller’s allowance.

ERC-2612 Permit

Permit allows a token holder to authorize a spender using an off-chain EIP-712 signature, so the spender can pull tokens without the holder sending a prior approval transaction.

permit

function permit(
    address owner,
    address spender,
    uint256 value,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) external
Sets spender’s allowance for owner to value, validated by an EIP-712 signature signed by owner.Failure scenarios:
ConditionError
Signature is invalid or does not match ownerERC2612InvalidSigner
block.timestamp > deadlineERC2612ExpiredSignature
Nonce does not match nonces(owner)ERC2612InvalidSigner

nonces

function nonces(address owner) external view returns (uint256)
Returns the current ERC-2612 nonce for owner. This is a sequential counter — increment after each permit call.

DOMAIN_SEPARATOR

function DOMAIN_SEPARATOR() external view returns (bytes32)
Returns the EIP-712 domain separator for this contract. Most signing libraries compute this automatically from name(), "1" (version), chainId, and the contract address — you rarely need to call this directly.

ERC-3009 Transfer with authorization

ERC-3009 meta-transactions let a token holder sign a time-bounded transfer authorization. Any party can submit the transaction on-chain. Nonces are random 32-byte values (not sequential) to support multiple concurrent authorizations.ERC-3009 vs ERC-2612 Permit — when to use each:
ERC-2612 PermitERC-3009
What it authorizesApproval (spending allowance)A specific transfer
Who submitsAnyoneAnyone (transferWithAuthorization) or recipient only (receiveWithAuthorization)
Nonce typeSequential — one active at a timeRandom — multiple concurrent authorizations supported
Use caseGasless approve before a transferFromGasless direct transfer, pay-for-me flows
Front-running riskLowUse receiveWithAuthorization when front-running is a concern

transferWithAuthorization

function transferWithAuthorization(
    address from,
    address to,
    uint256 value,
    uint256 validAfter,
    uint256 validBefore,
    bytes32 nonce,
    uint8 v,
    bytes32 r,
    bytes32 s
) external
Executes a transfer from from to to validated by a signed authorization. The authorization must be signed by from. Anyone may submit the transaction.
ParameterDescription
fromPayer — must sign the authorization
toRecipient
valueAmount to transfer
validAfterEarliest unix timestamp at which the authorization is valid (exclusive)
validBeforeLatest unix timestamp at which the authorization is valid (exclusive)
nonceRandom 32-byte nonce — must not have been used or canceled
v, r, sECDSA signature components
Failure scenarios:
ConditionError
block.timestamp <= validAfterAuthorizationNotYetValid(validAfter)
block.timestamp >= validBeforeAuthorizationExpired(validBefore)
Nonce already used or canceledAuthorizationAlreadyUsed(authorizer, nonce)
Signature does not match fromInvalidAuthorization()
from, to, or msg.sender is blocklistedAddressBlocklisted(account)
Transfers are pausedEnforcedPause()

receiveWithAuthorization

Same parameters as transferWithAuthorization, but msg.sender must equal to. This prevents front-running by restricting submission to the intended recipient.
function receiveWithAuthorization(
    address from,
    address to,
    uint256 value,
    uint256 validAfter,
    uint256 validBefore,
    bytes32 nonce,
    uint8 v,
    bytes32 r,
    bytes32 s
) external

cancelAuthorization

function cancelAuthorization(
    address authorizer,
    bytes32 nonce,
    uint8 v,
    bytes32 r,
    bytes32 s
) external
Voids an unused authorization nonce. The authorizer must sign a CancelAuthorization message. Once canceled, the nonce cannot be used for a transfer.

authorizationState

function authorizationState(address authorizer, bytes32 nonce) external view returns (bool)
Returns true if the authorization nonce has been used or canceled.

View functions

isBlocklisted

function isBlocklisted(address account) external view returns (bool)
Returns true if account is on the blocklist. Blocklisted addresses cannot be the sender, recipient, or msg.sender of any transfer or approval.

contractURI

function contractURI() external view returns (string)
Returns the contract-level metadata URI (ERC-7572). The URI typically points to a JSON file containing the token’s name, symbol, and image.

supportsInterface

function supportsInterface(bytes4 interfaceId) external view returns (bool)
Returns true for ERC-20 (0x36372b07), ERC-2612 (0x9d8ff7da), and all inherited interfaces.

Safety controls

Custom stablecoins include two safety mechanisms managed by Coinbase. These are transparent on-chain — your integration should handle them gracefully.

Blocklist

Specific addresses can be blocked from sending or receiving tokens. Any transfer involving a blocklisted address will revert with AddressBlocklisted(account). You can check blocklist status before sending with isBlocklisted(address).

Pause

All token transfers can be paused in an emergency. Any transfer attempted while paused will revert with EnforcedPause(). Monitor the Paused and Unpaused events to detect state changes.

Events

Event signatureEmitted when
Transfer(address indexed from, address indexed to, uint256 amount)Tokens are transferred, minted (from = 0x0), or burned (to = 0x0)
Approval(address indexed owner, address indexed spender, uint256 amount)An allowance is set
Minted(address indexed minter, address indexed to, uint256 amount)Tokens are minted by Coinbase
Burned(address indexed burner, uint256 amount)Tokens are burned by Coinbase
Memo(bytes32 indexed memo)A memo-bearing transfer is performed
AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce)An ERC-3009 authorization is consumed
AuthorizationCanceled(address indexed authorizer, bytes32 indexed nonce)An ERC-3009 authorization is canceled
BlocklistStatusUpdated(address indexed account, bool blocklisted)An address is added to or removed from the blocklist
Paused(address account)All transfers are paused
Unpaused(address account)Transfers are resumed

Errors

ErrorDescription
AddressBlocklisted(address account)A blocklisted address attempted a transfer or approval
EnforcedPause()Operation attempted while transfers are paused
ERC20InsufficientBalanceCaller has insufficient balance for the transfer
ERC20InsufficientAllowanceCaller’s allowance is insufficient for transferFrom
ERC2612InvalidSignerPermit signature is invalid or nonce does not match
ERC2612ExpiredSignaturePermit deadline has passed
AuthorizationAlreadyUsed(address authorizer, bytes32 nonce)ERC-3009 nonce has already been consumed or canceled
AuthorizationNotYetValid(uint256 validAfter)ERC-3009 authorization submitted before validAfter
AuthorizationExpired(uint256 validBefore)ERC-3009 authorization submitted after validBefore
CallerMustBePayee(address caller, address payee)receiveWithAuthorization caller is not the to address
InvalidAuthorization()ERC-3009 signature is invalid for the given authorizer

Quickstart

Get up and running in 10 minutes

Examples

Code samples for transfers, permit, and ERC-3009

Key Addresses

Contract and mint addresses

Overview

Learn about Custom Stablecoins