@circle-fin/usdckit - v0.18.0
    Preparing search index...

    USDC Acquiring Flow

    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:

    • A merchant that collects USDC payment for itself
    • A marketplace that collects USDC payment for merchants on the platform
    • A PSP that provides crypto acquiring services to the above parties

    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:

    • Acceptance Flow
      • The steps for the Acquiring Platform to receive payments made by users and to aggregate funds for every merchant
    • Withdraw Flow
      • The steps for Merchants to withdraw funds from the Acquiring Platform

    Refer to the later section for more details: Acquiring Flow Diagram

    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 - USDCKit
    • express: Server for the payment page
    npm 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 wallets will hold the consolidated funds from Payment Acceptance wallets
    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,
    })
    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

    • [Optional] Generate an ERC681 payment link for user to scan/click to prompt key transaction details into the user wallet
    • Display wallet address and payment link to users to facilitate payment
    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)
    })