Skip to main content

Getting Started as a Merchant

This guide walks you through registering as a DolphinPay merchant and accepting native Circle USDC payments on Sui testnet.

What is a DolphinPay merchant?

A merchant is any address that registers on-chain to receive payments. As a merchant you get:

  • USDC settlement — payments in native Circle USDC (6 decimals), enforced on-chain
  • No fees — the platform fee is 0 bps by policy, and merchants have no fee configuration; you receive the full payment amount
  • Capability-based control — a MerchantCap object authorizes all changes to your merchant
  • Receiving address control — set where your USDC settles
  • Status control — activate or deactivate your merchant at any time

There are no on-chain refunds (per ADR-004): once a payment executes, funds transfer to your receiving address with finality. Handle any make-goods off-chain.

Prerequisites

Your customers will need testnet USDC: https://faucet.circle.com (select Sui testnet).

The official DolphinPay Sui Testnet deployment is live and ready for the native Circle USDC flow documented below. Obtain the current Package ID and AdminConfig ID only from DEPLOYMENT.md — do not use IDs from anywhere else. Alternatively, you can publish your own package with the Testnet Deployment guide.

Step 1: Register your merchant

Using the frontend

  1. Open the DolphinPay app (deployed on Cloudflare Workers, or run bun run dev in frontend/)
  2. Connect your wallet (testnet)
  3. Go to the merchant page and register with a name and description
  4. Confirm the transaction — you receive a shared Merchant object and an owned MerchantCap

Using the SDK

import { createClient } from '@dolphinpay/sdk';

const client = createClient({
network: 'testnet',
packageId: '<PACKAGE_ID>', // from DEPLOYMENT.md
adminConfigId: '<ADMIN_CONFIG_ID>', // from DEPLOYMENT.md
});

const txb = client.merchant.buildRegisterMerchant({
name: 'My Store',
description: 'Online store selling digital products',
});

// Sign and execute with your wallet
const result = await signAndExecuteTransaction({ transaction: txb });
console.log('Merchant registered:', result.digest);

Find your merchant objects

// One page of MerchantCaps owned by your address (each points at its Merchant object).
// The limit is 1–50 per page (default 50); more caps are reachable via the cursor.
const page = await client.merchant.getMerchantCaps('0xYOUR_ADDRESS', { limit: 50 });
for (const cap of page.caps) {
console.log(cap.objectId, '→', cap.merchantId);
}
if (page.pageInfo.hasNextPage) {
const nextPage = await client.merchant.getMerchantCaps('0xYOUR_ADDRESS', {
cursor: page.pageInfo.endCursor,
});
}

// If you hold exactly one MerchantCap, this returns your sole merchant.
// If you hold multiple caps, it throws unless you pass an explicit
// merchantId selector: getMerchantByOwner('0xYOUR_ADDRESS', '0xMERCHANT_ID').
const merchant = await client.merchant.getMerchantByOwner('0xYOUR_ADDRESS');

Step 2: Set your USDC receiving address

Add USDC support and point it at the address where funds should settle. All merchant mutations require both your Merchant object ID and your MerchantCap ID.

import { USDC_TYPES } from '@dolphinpay/sdk';

const txb = client.merchant.buildAddSupportedCurrency({
merchantObjectId: '0xYOUR_MERCHANT_OBJECT_ID',
merchantCapId: '0xYOUR_MERCHANT_CAP_ID',
currencyType: USDC_TYPES.testnet,
receivingAddress: '0xYOUR_RECEIVING_ADDRESS',
});

To change the address later:

const txb = client.merchant.buildSetReceivingAddress({
merchantObjectId: '0xYOUR_MERCHANT_OBJECT_ID',
merchantCapId: '0xYOUR_MERCHANT_CAP_ID',
currencyType: USDC_TYPES.testnet,
receivingAddress: '0xYOUR_NEW_ADDRESS',
});
note

USDC is the only currency the platform accepts — the contract aborts on any other coin type. There is no multi-currency support.

Step 3: Create payment requests

import { usdcToUnits, USDC_TYPES } from '@dolphinpay/sdk';

// Validated against your merchant record (must be active)
const txb = client.payment.buildCreatePaymentWithMerchant({
merchantObjectId: '0xYOUR_MERCHANT_OBJECT_ID',
amount: usdcToUnits('25'), // '25' USDC (decimal string) → base-unit string
currencyType: USDC_TYPES.testnet,
description: 'Order #12345',
expirySeconds: 3600, // payment link valid for 1 hour
});

const result = await signAndExecuteTransaction({ transaction: txb });

The Payment is created as a shared object, so any payer can execute it. Share the checkout link (/pay/[paymentId] in the frontend) with your customer.

There is also buildCreatePayment({ merchant: '0xADDRESS', ... }), which pays directly to an address without merchant validation.

Step 4: Customer executes the payment

The customer pays with a USDC coin from their wallet:

const txb = client.payment.buildExecutePaymentWithMerchant(
{
paymentId: '0xPAYMENT_OBJECT_ID',
merchantObjectId: '0xYOUR_MERCHANT_OBJECT_ID',
coinObjectId: '0xCUSTOMER_USDC_COIN_ID',
amount: usdcToUnits('25'), // exact amount is split from the coin in-transaction
},
USDC_TYPES.testnet
);

await customerSignAndExecuteTransaction({ transaction: txb });

On execution the contract validates the coin type against AdminConfig, checks the amount and expiry, and transfers the full amount to your receiving address (platform fee is 0 bps).

Pending payments can be cancelled before execution:

const txb = client.payment.buildCancelPayment({
paymentId: '0xPAYMENT_OBJECT_ID',
reason: 'Order cancelled by customer',
});

Step 5: Track your payments

Query state over gRPC (the SDK's only transport — no JSON-RPC, no websockets):

// Payment status
const { payment } = await client.payment.getPayment('0xPAYMENT_OBJECT_ID');
console.log(payment.status, payment.amount); // amount in USDC base units

// Merchant record
const { merchant } = await client.merchant.getMerchant('0xYOUR_MERCHANT_OBJECT_ID');
console.log(merchant.isActive);

The contract emits payment creation/completion/cancellation and merchant lifecycle events; use client.events to query payment history and merchant statistics. PaymentFailed and PaymentExpired are defined but are not emitted by current contract paths.

Managing your merchant

// Update name/description
const txb1 = client.merchant.buildUpdateMerchantInfo({
merchantObjectId: '0xYOUR_MERCHANT_OBJECT_ID',
merchantCapId: '0xYOUR_MERCHANT_CAP_ID',
name: 'My Store (renamed)',
description: 'Updated description',
});

// Deactivate (stops new merchant-validated payments) / reactivate
const txb2 = client.merchant.buildToggleMerchantStatus({
merchantObjectId: '0xYOUR_MERCHANT_OBJECT_ID',
merchantCapId: '0xYOUR_MERCHANT_CAP_ID',
active: false,
});

Security best practices

  • Protect your MerchantCap — whoever owns it controls your merchant. Keep the owning wallet secure and never transfer the cap unintentionally. Its object ID is public metadata, not a signing credential.
  • Wallets sign, servers don't — the SDK only builds transactions; never handle private keys in application code.
  • Verify receiving addresses before updating them; funds transfer with finality and there are no on-chain refunds.
  • Use dry runs — every builder has a dryRun* counterpart (e.g. client.merchant.dryRunRegisterMerchant(params, sender)) to test without gas.
  • Don't hard-code IDs — read the Package ID and AdminConfig ID from configuration backed by DEPLOYMENT.md.

Troubleshooting

Payment execution aborts

  • The coin must be testnet native Circle USDC (USDC_TYPES.testnet); any other type is rejected on-chain
  • The coin value must match the payment amount — pass amount so the SDK splits it exactly
  • The payment may have expired or been cancelled

"Unauthorized merchant operation"

  • Check you passed the MerchantCap that matches the Merchant object

Merchant-validated creation fails

  • Ensure the merchant is active (client.merchant.isMerchantActive(...))

Next steps