Skip to main content

Merchant Operations Guide

Register merchants, manage currency/receiving-address settings, and query merchant data with the DolphinPay TypeScript SDK.

Merchants have no fee configuration — the platform fee lives in the shared AdminConfig and is 0 bps by policy. The platform accepts only native Circle USDC, so "supported currency" operations are in practice about registering the USDC receiving address.

All build* methods return a Transaction to sign and execute with your wallet; get*/is* methods query chain state over gRPC.

Merchant Registration

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

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

const txb = client.merchant.buildRegisterMerchant({
name: 'My Online Store', // required, max 100 chars
description: 'Digital goods', // max 500 chars
});

const result = await signAndExecute({ transaction: txb });
console.log('Merchant registered:', result.digest);

Registration creates two objects:

  • Merchant — the shared merchant account object.
  • MerchantCap — an owned capability object; it authorizes every subsequent merchant operation. Protect the wallet that owns it; the object ID itself is public metadata.

Finding Your Merchant After Registration

Instead of parsing transaction effects, query by owner address:

// One page of MerchantCaps owned by an address.
// limit is 1–50 per page (default 50); more caps are reachable via the cursor.
const page = await client.merchant.getMerchantCaps('0xOWNER_ADDRESS', { limit: 50 });
// page.caps: [{ objectId, merchantId, version, digest }, ...]
// page.pageInfo: { hasNextPage, endCursor }

// Fetch the next page when there are more caps
if (page.pageInfo.hasNextPage) {
const nextPage = await client.merchant.getMerchantCaps('0xOWNER_ADDRESS', {
cursor: page.pageInfo.endCursor,
limit: 50,
});
}

// Owners with a single cap: getMerchantByOwner returns the sole merchant
// (or null when the owner holds no cap). Owners with multiple caps: it
// throws unless you pass an explicit merchantId selector.
const result = await client.merchant.getMerchantByOwner('0xOWNER_ADDRESS');
if (result) {
console.log('Merchant ID:', result.merchant.id);
console.log('Name:', result.merchant.name);
}

// Explicit selection when the owner holds multiple caps
const selected = await client.merchant.getMerchantByOwner(
'0xOWNER_ADDRESS',
'0xMERCHANT_ID'
);

Updating Merchant Info

const txb = client.merchant.buildUpdateMerchantInfo({
merchantObjectId: '0xMERCHANT_OBJECT_ID',
merchantCapId: '0xMERCHANT_CAP_ID',
name: 'My Updated Store Name',
description: 'Updated description',
});

await signAndExecute({ transaction: txb });

Currency and Receiving Address

The only currency the contract accepts for payments is the network's native Circle USDC (USDC_TYPES from the SDK).

Add Supported Currency

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

const txb = client.merchant.buildAddSupportedCurrency({
merchantObjectId: '0xMERCHANT_OBJECT_ID',
merchantCapId: '0xMERCHANT_CAP_ID',
currencyType: USDC_TYPES.testnet,
receivingAddress: '0xYOUR_WALLET_ADDRESS',
});

await signAndExecute({ transaction: txb });

Update Receiving Address

const txb = client.merchant.buildSetReceivingAddress({
merchantObjectId: '0xMERCHANT_OBJECT_ID',
merchantCapId: '0xMERCHANT_CAP_ID',
currencyType: USDC_TYPES.testnet,
receivingAddress: '0xNEW_WALLET_ADDRESS',
});

Remove Supported Currency

const txb = client.merchant.buildRemoveSupportedCurrency({
merchantObjectId: '0xMERCHANT_OBJECT_ID',
merchantCapId: '0xMERCHANT_CAP_ID',
currencyType: USDC_TYPES.testnet,
});

Toggle Merchant Status

Activating/deactivating takes an explicit active flag:

// Deactivate (e.g. maintenance)
const txb = client.merchant.buildToggleMerchantStatus({
merchantObjectId: '0xMERCHANT_OBJECT_ID',
merchantCapId: '0xMERCHANT_CAP_ID',
active: false,
});

await signAndExecute({ transaction: txb });

Inactive merchants cannot receive new payments created via merchant validation.

Querying Merchant Data

Get Merchant Details

const result = await client.merchant.getMerchant('0xMERCHANT_OBJECT_ID');
const { merchant } = result;

console.log('Name:', merchant.name);
console.log('Description:', merchant.description);
console.log('Owner:', merchant.owner);
console.log('Active:', merchant.isActive);
console.log('Created:', new Date(Number(merchant.createdAt)));

All Merchant fields are strictly parsed from the on-chain object: merchant.receivingAddresses (a Map of coin type → address), merchant.supportedCurrencies, and the full merchant.settings (including apiKeys) are populated. Malformed on-chain data throws a diagnostic error naming the object kind and field path (e.g. Merchant: invalid value at "receiving_addresses.contents[0]": …).

Check Merchant Status

const isActive = await client.merchant.isMerchantActive('0xMERCHANT_OBJECT_ID');

There is no getMerchantFeeConfig — merchants have no fee configuration. The platform-wide fee (0 bps) is read via client.admin.getAdminConfig().

Dry Run Testing

Each builder has a matching simulator:

const result = await client.merchant.dryRunRegisterMerchant(
{ name: 'Test Store', description: 'Testing registration' },
'0xYOUR_ADDRESS'
);

if (result.success) {
const txb = client.merchant.buildRegisterMerchant({
name: 'Test Store',
description: 'Testing registration',
});
await signAndExecute({ transaction: txb });
} else {
console.error('Would fail:', result.error ?? result.status);
}

Also available: dryRunUpdateMerchantInfo, dryRunAddSupportedCurrency, dryRunRemoveSupportedCurrency, dryRunSetReceivingAddress, dryRunToggleMerchantStatus — each takes (params, senderAddress).

Complete Setup Example

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

async function setupMerchant(ownerAddress: string, signAndExecute: any) {
const client = createClient({
network: 'testnet',
packageId: '<PACKAGE_ID>',
adminConfigId: '<ADMIN_CONFIG_ID>',
});

// 1. Register
const registerTxb = client.merchant.buildRegisterMerchant({
name: 'Acme Online Store',
description: 'Premium digital goods marketplace',
});
await signAndExecute({ transaction: registerTxb });

// 2. Look up the new Merchant and MerchantCap by owner (one page of caps)
const page = await client.merchant.getMerchantCaps(ownerAddress);
if (page.caps.length !== 1 || page.pageInfo.hasNextPage) {
throw new Error('Select the newly created MerchantCap explicitly');
}
const cap = page.caps[0];
const merchantCapId = cap.objectId;
const merchantId = cap.merchantId;

// 3. Register USDC receiving address
const usdcTxb = client.merchant.buildAddSupportedCurrency({
merchantObjectId: merchantId,
merchantCapId,
currencyType: USDC_TYPES.testnet,
receivingAddress: ownerAddress,
});
await signAndExecute({ transaction: usdcTxb });

// 4. Verify
const { merchant } = await client.merchant.getMerchant(merchantId);
console.log('Setup complete:', merchant.name, 'active:', merchant.isActive);

return { merchantId, merchantCapId };
}

Error Handling

buildRegisterMerchant throws locally on an empty name or on name/description exceeding the limits (100 / 500 chars). On-chain aborts map to MerchantErrorCode, e.g.:

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

// E_NOT_AUTHORIZED = 1, E_MERCHANT_NOT_FOUND = 201, E_MERCHANT_INACTIVE = 202,
// E_INVALID_NAME = 203, E_CURRENCY_ALREADY_SUPPORTED = 205,
// E_CURRENCY_NOT_SUPPORTED = 206, E_INVALID_ADDRESS = 207

Best Practices

  • Guard the MerchantCap. It authorizes every merchant mutation; protect the owning wallet and do not transfer the cap unintentionally.
  • Look up IDs with getMerchantCaps / getMerchantByOwner when an owner has one merchant. getMerchantByOwner returns the sole merchant for a single-cap owner and throws for a multi-cap owner unless you pass an explicit merchantId selector.
  • Select caps explicitly when an owner has multiple merchants. getMerchantCaps returns one page of 1–50 caps (page.caps + page.pageInfo); pass pageInfo.endCursor as cursor to reach further caps. Compare the cap list before and after registration to identify the new object, then use cap.objectId and cap.merchantId.
  • Dry run before executing operations you haven't run before.

Next Steps