This section provides a sample code to demonstrate how to use USDCKit to set up a USDC acquiring flow for a commerce use case.
This sample code could be helpful to the following types of businesses:
Here is a diagram to illustrate the high-level business flow and to demonstrate the basic architecture of the platform. Refer to the detail steps below to understand the logic better:
Refer to the later section for more details:
This section will provide a step by step guide to go through the sample code to provide detailed explanation. Alternatively, check on the full code on Acceptance Flow and Withdraw Flow.
Install the dependencies for the sample
@circle-fin/usdckit
- USDCKitexpress
: Server for the payment pagenpm install @circle-fin/usdckit express
Run the following command to create and register an Entity Secret. The command will prompt you to enter the API Key generated from the previous step.
npx @circle-fin/usdckit register-entity-secret
After running the command, your new Entity Secret will be displayed in the console and saved to an environment file (.env
by default). A Recovery File is also stored (recovery.dat
by default).
A UI-based approach for setting up the Entity Secret is also available.
Initialize a client for the sample app. The client will use the ETH-SEPOLIA
chain by default
import { createClient } from '@circle-fin/usdckit'
import { ETH_SEPOLIA } from '@circle-fin/usdckit/chains'
const client = createClient({
apiKey: 'REPLACE_WITH_CIRCLE_API_KEY',
entitySecret: 'REPLACE_WITH_CIRCLE_ENTITY_SECRET',
chain: ETH_SEPOLIA,
logLevel: 'debug',
})
Create necessary wallet sets to manage fund flow. A Wallet Set is a container object that allows creation of wallets under it and helps organize wallets into groups to differentiate fund purposes and makes maintenance easier.
We will create the following wallet sets and corresponding wallets
merchant-wallet-set
)const merchantWalletSet = await client.createWalletSet({
name: 'merchant-wallet-set',
})
const merchantWalletSetId = merchantWalletSet.id
const merchantWallet = await client.createAccount({
walletSetId: merchantWalletSetId,
refId: 'merchant',
chain: ETH_SEPOLIA,
})
payment-acceptance-wallet-set
)const paymentAcceptanceWalletSet = await client.createWalletSet({
name: 'payment-acceptance-wallet-set',
})
const paymentAcceptanceWalletSetId = paymentAcceptanceWalletSet.id
The task will select all payment acceptance wallets under the payment-acceptance-wallet-set
Wallet Set dedicated to a merchant whose balance is greater than 1 USDC and transfer its USDC balance to merchantWallet
async function sweepPaymentAcceptanceWalletsToMerchantWallets(destinationWallet) {
const accountsToSweep = await client.getAccounts({
token: ETH_SEPOLIA.contracts.USDC,
amountGte: '1',
walletSetId: paymentAcceptanceWalletSetId,
chain: ETH_SEPOLIA,
})
if (accountsToSweep.length === 0) return
// `sweep()` calculates zero amount and calls `transfer()` under the hood
const txs = await client.sweep({
// Pass in array of wallets to do a batched transfer
from: accountsToSweep,
to: destinationWallet,
token: ETH_SEPOLIA.contracts.USDC,
// Use the `merchantWallet` as a source of funds for fees
fees: FEE_LEVEL_WITH_PREPARE({
level: 'HIGH',
account: merchantWallet,
}),
chain: ETH_SEPOLIA,
})
// Wait for transaction receipts
console.log(
await Promise.all(txs.map(tx => client.waitForTransactionReceipt({ ...tx, chain: ETH_SEPOLIA })))
)
}
Use our cross chain send to consolidate the payment received across multiple chains into a single chain of choice.
import { createClient } from '@circle-fin/usdckit'
import { ETH_SEPOLIA } from '@circle-fin/usdckit/chains'
const WITHDRAW_DESTINATIONS = {
// Add withdraw addresses and amounts
// '0xREPLACE_WITH_WITHDRAW_DESTINATION_ADDRESS': '20',
}
// Initialize the client
const client = createClient({
apiKey: process.env.CIRCLE_API_KEY || '',
entitySecret: process.env.CIRCLE_ENTITY_SECRET || '',
chain: ETH_SEPOLIA,
logLevel: 'debug',
})
// Fetch the merchant wallet from Acceptance Flow
const [ merchantWallet ] = await client.getAccounts({
address: '0xREPLACE_WITH_MERCHANT_WALLET_ADDRESS',
chain: ETH_SEPOLIA,
})
// Fetch the merchant wallet from Acceptance Flow
const [ merchantWallet_MATIC_AMOY ] = await client.getAccounts({
address: '0xREPLACE_WITH_MERCHANT_WALLET_ADDRESS',
chain: MATIC_AMOY,
})
// (Optional) Aggregate multiple merchant wallet balances to a single wallet
await client.sweep({
from: merchantWallet_MATIC_AMOY,
to: merchantWallet,
token: MATIC_AMOY.contracts.USDC,
chain: MATIC_AMOY,
})
await Promise.all(
Object.entries(WITHDRAW_DESTINATIONS).map(([address, amount]) => {
return client.transfer({
from: merchantWallet,
to: address,
amount,
token: ETH_SEPOLIA.contracts.USDC,
chain: ETH_SEPOLIA,
})
})
)
For testing purposes, we will fund the merchant
wallet used in FEE_LEVEL_WITH_PREPARE
with native tokens as the source of fees for the fund consolidation task
await client.drip({
account: merchantWallet,
token: null,
chain: ETH_SEPOLIA,
})
For every order that requires payment, create a new Payment Acceptance wallet to receive USDC for clear accounting and order reconciliation
import crypto from 'crypto'
import express from 'express'
const app = express()
const port = 3000
// Create Payment Acceptance wallet per Order
app.get('/pay', async (req, res) => {
const order = {
id: crypto.randomUUID(),
token: ETH_SEPOLIA.contracts.USDC,
amount: '10',
} as const
// Create Payment Acceptance wallet for the Order
const paymentAcceptanceWallet = await client.createAccount({
// Record Order ID on the wallet for reference
refId: `order-${order.id}`,
walletSetId: paymentAcceptanceWalletSetId,
chain: ETH_SEPOLIA,
})
// Generate ERC-681 link
const paymentLink = await client.generateTransferLink({
to: paymentAcceptanceWallet,
amount: order.amount,
token: order.token,
chain: ETH_SEPOLIA,
})
return res.send(`
<html>
<body>
<h1>Order ${order.id} - ${order.amount} USDC</h1>
<p>Send ${order.amount} USDC to ${paymentAcceptanceWallet}</p>
<p>ERC-681: <a href="${paymentLink}">${paymentLink}</a></p>
<a href="${paymentLink}">
<img src="https://api.qrserver.com/v1/create-qr-code/?data=${encodeURIComponent(paymentLink)}" />
</a>
</body>
</html>
`)
})
Start the express
server and the fund consolidation task. The task will run on 5min intervals.
app.listen(port, () => {
console.log(`Listening on port ${port}`)
// Start Cronjob to check Payment Acceptance wallets balances
setInterval(async () => {
try {
await sweepPaymentAcceptanceWalletsToMerchantWallets(merchantWallet)
} catch (err) {
client.logger.error(err)
}
}, 300000)
})
The sample code can be run with npx tsx script.mts
Visit the payment page at http://127.0.0.1:3000/pay. Use any method to transfer the amount to the Payment Acceptance address displayed.
When the fund consolidation triggers, the Payment Acceptance's balance will be transferred to the Merchant wallet
import { setTimeout } from 'node:timers/promises'
import { createClient } from '@circle-fin/usdckit'
import { ETH_SEPOLIA } from '@circle-fin/usdckit/chains'
import { FEE_LEVEL_WITH_PREPARE } from '@circle-fin/usdckit/actions'
import crypto from 'crypto'
import express from 'express'
// Initialize the client
const client = createClient({
apiKey: process.env.CIRCLE_API_KEY || '',
entitySecret: process.env.CIRCLE_ENTITY_SECRET || '',
chain: ETH_SEPOLIA,
logLevel: 'debug',
})
// Create a Merchant Wallet Set
const merchantWalletSet = await client.createWalletSet({
name: 'merchant-wallet-set',
})
const merchantWalletSetId = merchantWalletSet.id
// Create a Payment Acceptance Wallet Set
const paymentAcceptanceWalletSet = await client.createWalletSet({
name: 'payment-acceptance-wallet-set',
})
const paymentAcceptanceWalletSetId = paymentAcceptanceWalletSet.id
// Create Merchant wallet to collect the funds to
const merchantWallet = await client.createAccount({
walletSetId: merchantWalletSetId,
refId: 'merchant',
chain: ETH_SEPOLIA,
})
// Fund the merchant wallet so it can act as a fee source
await client.drip({ account: merchantWallet, token: null, chain: ETH_SEPOLIA })
await setTimeout(10000)
async function sweepPaymentAcceptanceWalletsToMerchantWallets(destinationWallet) {
const accountsToSweep = await client.getAccounts({
token: ETH_SEPOLIA.contracts.USDC,
amountGte: '1',
walletSetId: paymentAcceptanceWalletSetId,
chain: ETH_SEPOLIA,
})
if (accountsToSweep.length === 0) return
// `sweep()` calculates zero amount and calls `transfer()` under the hood
const txs = await client.sweep({
// Pass in array of wallets to do a batched transfer
from: accountsToSweep,
to: destinationWallet,
token: ETH_SEPOLIA.contracts.USDC,
// Use the `merchantWallet` as a source of funds for fees
fees: FEE_LEVEL_WITH_PREPARE({
level: 'HIGH',
account: merchantWallet,
}),
chain: ETH_SEPOLIA,
})
// Wait for transaction receipts
console.log(
await Promise.all(txs.map(tx => client.waitForTransactionReceipt({ ...tx, chain: ETH_SEPOLIA })))
)
}
const app = express()
const port = 3000
// Create Payment Acceptance wallet per Order
app.get('/pay', async (req, res) => {
const order = {
id: crypto.randomUUID(),
token: ETH_SEPOLIA.contracts.USDC,
amount: '10',
} as const
// Create Payment Acceptance wallet for the Order
const paymentAcceptanceWallet = await client.createAccount({
// Record Order ID on the wallet for reference
refId: `order-${order.id}`,
walletSetId: paymentAcceptanceWalletSetId,
chain: ETH_SEPOLIA,
})
// Generate ERC-681 link
const paymentLink = await client.generateTransferLink({
to: paymentAcceptanceWallet,
amount: order.amount,
token: order.token,
chain: ETH_SEPOLIA,
})
return res.send(`
<html>
<body>
<h1>Order ${order.id} - ${order.amount} USDC</h1>
<p>Send ${order.amount} USDC to ${paymentAcceptanceWallet}</p>
<p>ERC-681: <a href="${paymentLink}">${paymentLink}</a></p>
<a href="${paymentLink}">
<img src="https://api.qrserver.com/v1/create-qr-code/?data=${encodeURIComponent(paymentLink)}" />
</a>
</body>
</html>
`)
})
app.listen(port, () => {
console.log(`Listening on port ${port}`)
// Start Cronjob to check Payment Acceptance wallets balances
setInterval(async () => {
try {
await sweepPaymentAcceptanceWalletsToMerchantWallets(merchantWallet)
} catch (err) {
client.logger.error(err)
}
}, 300000)
})