Complete guide to integrating with StateSet’s native CCTP module for cross-chain USDC transfers
This guide covers integrating with StateSet’s native CCTP module implementation. Unlike EVM chains where CCTP is implemented via smart contracts, StateSet has built CCTP directly into the blockchain as a Cosmos SDK module, providing superior performance, security, and integration capabilities.
# Download and install the StateSet CLI
curl -L https://get.stateset.network | bash
# Verify installation
stateset version
# Output: v1.0.0
# Configure for testnet
stateset config chain-id stateset-testnet-1
stateset config node https://rpc-test.stateset.network:443
# Create a new account
stateset keys add testaccount
# Get testnet USDC from faucet
curl -X POST https://faucet-test.stateset.network/credit \
-H "Content-Type: application/json" \
-d '{"address": "stateset1...", "coins": ["1000000usdc"]}'
# JavaScript/TypeScript
npm install @stateset/cctp-sdk @stateset/client
# Go
go get github.com/stateset/stateset/x/cctp
# Python
pip install stateset-cctp
import { StateSetClient } from '@stateset/client';
import { CCTPModule } from '@stateset/cctp-sdk';
// Initialize client
const client = await StateSetClient.connect('https://rpc.stateset.network');
const cctp = new CCTPModule(client);
// Set up signer
const signer = await client.getSigner('your-mnemonic-or-private-key');
// Example: Burn USDC to transfer to Ethereum
async function transferToEthereum() {
const burnMsg = {
from: signer.address,
amount: {
denom: 'usdc',
amount: '1000000000' // 1000 USDC (6 decimals)
},
destinationDomain: 0, // Ethereum
mintRecipient: ethers.utils.hexZeroPad('0x742d35...', 32),
burnToken: 'usdc'
};
// Execute burn
const tx = await cctp.depositForBurn(burnMsg);
console.log('Burn tx hash:', tx.transactionHash);
console.log('Message hash:', tx.messageHash);
// Wait for attestation from Circle
const attestation = await cctp.waitForAttestation(tx.messageHash);
console.log('Attestation received:', attestation);
// The attestation can now be used on Ethereum to mint the USDC
return { message: tx.message, attestation };
}
package main
import (
"context"
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/stateset/stateset/x/cctp/types"
"github.com/stateset/stateset/x/cctp/client/cli"
)
func TransferUSDC(ctx context.Context, clientCtx client.Context) error {
// Create deposit for burn message
msg := &types.MsgDepositForBurn{
From: clientCtx.GetFromAddress().String(),
Amount: sdk.NewCoin("usdc", sdk.NewInt(1000000000)),
DestinationDomain: 0, // Ethereum
MintRecipient: hexutil.MustDecode("0x742d35Cc6634C0532925a3b844Bc9e7595f6E321"),
BurnToken: "usdc",
}
// Validate message
if err := msg.ValidateBasic(); err != nil {
return fmt.Errorf("invalid message: %w", err)
}
// Broadcast transaction
res, err := cli.BroadcastTx(clientCtx, msg)
if err != nil {
return fmt.Errorf("broadcast failed: %w", err)
}
// Extract message hash from events
messageHash := ExtractMessageHash(res.Events)
fmt.Printf("Message hash: %s\n", messageHash)
return nil
}
from stateset_cctp import CCTPClient, DepositForBurnMsg
from stateset_cctp.attestation import AttestationService
# Initialize client
client = CCTPClient(
rpc_endpoint="https://rpc.stateset.network",
private_key="your-private-key"
)
# Transfer USDC to Arbitrum
async def transfer_to_arbitrum():
# Create burn message
msg = DepositForBurnMsg(
from_address=client.address,
amount={"denom": "usdc", "amount": "500000000"}, # 500 USDC
destination_domain=3, # Arbitrum
mint_recipient=format_evm_address("0x742d35..."),
burn_token="usdc"
)
# Execute burn
result = await client.deposit_for_burn(msg)
print(f"Transaction hash: {result.tx_hash}")
print(f"Message hash: {result.message_hash}")
# Get attestation
attestation_service = AttestationService()
attestation = await attestation_service.get_attestation(
result.message_hash
)
return {
"message": result.message,
"attestation": attestation
}
// Process orders with payment on any supported chain
class CrossChainOrderProcessor {
constructor(
private cctp: CCTPModule,
private orderModule: OrderModule
) {}
async processOrderWithCrossChainPayment(
orderId: string,
paymentChain: number,
paymentTx: string
) {
// Verify payment on source chain
const payment = await this.verifyPayment(paymentChain, paymentTx);
if (paymentChain !== STATESET_DOMAIN) {
// Wait for CCTP transfer to complete
const cctpTransfer = await this.waitForCCTPTransfer(
payment.messageHash
);
// Update order with payment
await this.orderModule.updatePayment({
orderId,
paymentInfo: {
type: 'cross_chain_usdc',
sourceChain: paymentChain,
amount: cctpTransfer.amount,
transactionHash: cctpTransfer.txHash
}
});
}
// Fulfill order
await this.orderModule.fulfillOrder(orderId);
}
private async waitForCCTPTransfer(messageHash: string) {
// Monitor for receive message transaction
return new Promise((resolve) => {
const subscription = this.cctp.subscribeToEvents({
messageHash
}).subscribe(event => {
if (event.type === 'mint_and_withdraw') {
subscription.unsubscribe();
resolve(event);
}
});
});
}
}
// Require multiple signatures for large CCTP transfers
class MultiSigCCTP {
constructor(
private cctp: CCTPModule,
private multisig: MultisigModule
) {}
async proposeTransfer(params: CCTPTransferParams) {
// Create multisig proposal
const proposal = await this.multisig.createProposal({
messages: [{
type: 'cctp/MsgDepositForBurn',
value: {
from: this.multisig.address,
amount: params.amount,
destinationDomain: params.destinationDomain,
mintRecipient: params.recipient,
burnToken: 'usdc'
}
}],
metadata: {
title: `CCTP Transfer ${params.amount.amount} USDC to chain ${params.destinationDomain}`,
description: params.description
}
});
return proposal.id;
}
async executeTransfer(proposalId: string) {
// Check if proposal is approved
const proposal = await this.multisig.getProposal(proposalId);
if (!proposal.isApproved()) {
throw new Error('Proposal not approved');
}
// Execute the transfer
return await this.multisig.executeProposal(proposalId);
}
}
// Automatically rebalance treasury across chains
class TreasuryManager {
private readonly TARGET_BALANCE = 100000; // $100k USDC per chain
private readonly REBALANCE_THRESHOLD = 0.2; // 20% deviation
async rebalanceAllChains() {
const balances = await this.getAllChainBalances();
const transfers = this.calculateOptimalTransfers(balances);
// Execute transfers in parallel where possible
const transferPromises = transfers.map(transfer =>
this.executeCCTPTransfer(transfer)
);
const results = await Promise.allSettled(transferPromises);
// Log results
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Transfer ${index} completed:`, result.value);
} else {
console.error(`Transfer ${index} failed:`, result.reason);
}
});
}
private calculateOptimalTransfers(balances: ChainBalance[]): Transfer[] {
const totalBalance = balances.reduce((sum, b) => sum + b.amount, 0);
const targetPerChain = totalBalance / balances.length;
const transfers: Transfer[] = [];
const surplus = balances.filter(b => b.amount > targetPerChain * (1 + this.REBALANCE_THRESHOLD));
const deficit = balances.filter(b => b.amount < targetPerChain * (1 - this.REBALANCE_THRESHOLD));
// Match surplus with deficit chains
for (const source of surplus) {
for (const dest of deficit) {
const transferAmount = Math.min(
source.amount - targetPerChain,
targetPerChain - dest.amount
);
if (transferAmount > 1000) { // Min $1k transfer
transfers.push({
sourceChain: source.domain,
destChain: dest.domain,
amount: transferAmount
});
source.amount -= transferAmount;
dest.amount += transferAmount;
}
}
}
return transfers;
}
}
// Implement rate limiting and compliance checks
class ComplianceCCTP {
private dailyLimits = new Map<string, number>();
private readonly MAX_DAILY_LIMIT = 1000000; // $1M USDC
async transferWithCompliance(params: CCTPTransferParams) {
// Check daily limits
const dailyTotal = await this.getDailyTotal(params.from);
if (dailyTotal + parseInt(params.amount.amount) > this.MAX_DAILY_LIMIT) {
throw new Error('Daily transfer limit exceeded');
}
// Compliance checks
if (await this.isBlacklisted(params.mintRecipient)) {
throw new Error('Recipient address is blacklisted');
}
// Check destination chain restrictions
if (!await this.isDestinationAllowed(params.from, params.destinationDomain)) {
throw new Error('Transfers to this destination not allowed');
}
// Execute transfer with monitoring
const result = await this.cctp.depositForBurn(params);
// Record for compliance
await this.recordTransfer({
from: params.from,
amount: params.amount,
destination: params.destinationDomain,
recipient: params.mintRecipient,
timestamp: Date.now(),
messageHash: result.messageHash
});
return result;
}
}
# Query module configuration
stateset query cctp owner
stateset query cctp signature-threshold
stateset query cctp max-message-body-size
# Query attesters
stateset query cctp attesters
stateset query cctp attester stateset1attester...
# Query token pairs
stateset query cctp token-pairs
stateset query cctp token-pair 0 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
# Query burn limits
stateset query cctp per-message-burn-limit usdc
# Query paused state
stateset query cctp burning-minting-paused
stateset query cctp sending-receiving-paused
// Query module state
const owner = await cctp.query.owner();
const threshold = await cctp.query.signatureThreshold();
const attesters = await cctp.query.attesters();
// Query specific token pair
const tokenPair = await cctp.query.tokenPair({
remoteDomain: 0,
remoteToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
});
// Query burn limits
const burnLimit = await cctp.query.perMessageBurnLimit({
token: 'usdc'
});
// Monitor all CCTP events
const subscription = cctp.subscribeToEvents()
.subscribe(event => {
switch(event.type) {
case 'deposit_for_burn':
console.log('Burn initiated:', event);
trackBurn(event);
break;
case 'message_sent':
console.log('Message sent:', event);
break;
case 'message_received':
console.log('Message received:', event);
trackReceive(event);
break;
case 'mint_and_withdraw':
console.log('USDC minted:', event);
completeTrasfer(event);
break;
}
});
// Monitor specific message
const messageSubscription = cctp.subscribeToMessage(messageHash)
.subscribe(update => {
console.log('Message status:', update.status);
if (update.status === 'completed') {
messageSubscription.unsubscribe();
}
});
// Set up webhooks for CCTP events
app.post('/webhooks/cctp', async (req, res) => {
const event = req.body;
// Verify webhook signature
if (!verifyWebhookSignature(req)) {
return res.status(401).send('Invalid signature');
}
// Process event
switch(event.type) {
case 'cctp.deposit_for_burn':
await handleBurnEvent(event);
break;
case 'cctp.mint_and_withdraw':
await handleMintEvent(event);
break;
case 'cctp.message_received':
await handleMessageReceived(event);
break;
}
res.status(200).send('OK');
});
import { MockCCTPModule } from '@stateset/cctp-sdk/testing';
describe('CCTP Integration', () => {
let cctp: MockCCTPModule;
beforeEach(() => {
cctp = new MockCCTPModule();
});
it('should burn USDC for cross-chain transfer', async () => {
const burnMsg = {
from: 'stateset1test...',
amount: { denom: 'usdc', amount: '1000000' },
destinationDomain: 0,
mintRecipient: '0x742d35...',
burnToken: 'usdc'
};
const result = await cctp.depositForBurn(burnMsg);
expect(result.transactionHash).toBeDefined();
expect(result.messageHash).toBeDefined();
expect(result.nonce).toBeGreaterThan(0);
});
});
// Test full cross-chain flow
describe('Cross-chain USDC Transfer', () => {
it('should complete transfer from StateSet to Ethereum', async () => {
// 1. Burn on StateSet
const burnResult = await statesetCCTP.depositForBurn({
amount: { denom: 'usdc', amount: '1000000000' },
destinationDomain: 0,
mintRecipient: ethRecipient,
burnToken: 'usdc'
});
// 2. Wait for attestation
const attestation = await waitForAttestation(burnResult.messageHash);
expect(attestation).toBeDefined();
// 3. Mint on Ethereum
const mintTx = await ethereumCCTP.receiveMessage(
burnResult.message,
attestation
);
// 4. Verify balance
const balance = await ethereumUSDC.balanceOf(ethRecipient);
expect(balance.toString()).toBe('1000000000');
});
});
class CCTPErrorHandler {
async handleCCTPOperation(operation: () => Promise<any>) {
try {
return await operation();
} catch (error) {
if (error.code === 'INSUFFICIENT_FUNDS') {
throw new Error('Insufficient USDC balance for transfer');
}
if (error.code === 'INVALID_RECIPIENT') {
throw new Error('Recipient address format is invalid');
}
if (error.code === 'PAUSED') {
throw new Error('CCTP operations are currently paused');
}
if (error.code === 'BURN_LIMIT_EXCEEDED') {
throw new Error('Transfer amount exceeds per-message burn limit');
}
if (error.code === 'NONCE_ALREADY_USED') {
throw new Error('This message has already been processed');
}
// Unknown error
console.error('CCTP operation failed:', error);
throw error;
}
}
}
// Process multiple transfers efficiently
class BatchCCTPProcessor {
async processBatch(transfers: Transfer[]) {
// Group by destination domain
const grouped = this.groupByDomain(transfers);
// Process each group in parallel
const results = await Promise.all(
Object.entries(grouped).map(([domain, transfers]) =>
this.processGroup(parseInt(domain), transfers)
)
);
return results.flat();
}
private async processGroup(domain: number, transfers: Transfer[]) {
// For same destination, can optimize with batching
if (transfers.length > 10) {
return this.processBatchMessage(domain, transfers);
}
// Otherwise process individually in parallel
return Promise.all(transfers.map(t => this.processSingle(t)));
}
}
// Cache attestations and frequently used data
class CachedCCTP {
private attestationCache = new Map<string, string>();
private tokenPairCache = new Map<string, TokenPair>();
async getAttestation(messageHash: string): Promise<string> {
// Check cache first
if (this.attestationCache.has(messageHash)) {
return this.attestationCache.get(messageHash)!;
}
// Fetch from service
const attestation = await this.attestationService.get(messageHash);
// Cache for future use
this.attestationCache.set(messageHash, attestation);
return attestation;
}
}
function validateCCTPTransfer(params: CCTPTransferParams) {
// Validate amount
const amount = parseInt(params.amount.amount);
if (isNaN(amount) || amount <= 0) {
throw new Error('Invalid amount');
}
// Validate destination domain
if (!SUPPORTED_DOMAINS.includes(params.destinationDomain)) {
throw new Error('Unsupported destination domain');
}
// Validate recipient address format
if (!isValidAddressFormat(params.mintRecipient, params.destinationDomain)) {
throw new Error('Invalid recipient address format');
}
// Validate sender has sufficient balance
if (!hasSufficientBalance(params.from, amount)) {
throw new Error('Insufficient balance');
}
}
// Implement rate limiting for CCTP operations
class RateLimitedCCTP {
private limits = new Map<string, RateLimit>();
async checkRateLimit(address: string, amount: number) {
const limit = this.limits.get(address) || this.createLimit(address);
// Check per-transaction limit
if (amount > limit.maxPerTransaction) {
throw new Error('Transaction exceeds maximum allowed amount');
}
// Check hourly limit
const hourlyTotal = await this.getHourlyTotal(address);
if (hourlyTotal + amount > limit.maxPerHour) {
throw new Error('Hourly transfer limit exceeded');
}
// Check daily limit
const dailyTotal = await this.getDailyTotal(address);
if (dailyTotal + amount > limit.maxPerDay) {
throw new Error('Daily transfer limit exceeded');
}
}
}
// Update your integration to use StateSet's native module
class CCTPMigration {
// Old Noble integration
async oldTransfer() {
await nobleClient.execute(
nobleContract,
{ deposit_for_burn: { ... } }
);
}
// New StateSet integration
async newTransfer() {
await statesetCCTP.depositForBurn({
from: signer.address,
amount: { denom: 'usdc', amount: '1000000' },
destinationDomain: 0,
mintRecipient: recipient,
burnToken: 'usdc'
});
}
}
For integration support: