Skip to main content

Event Querying Guide

Learn how to query on-chain events, build payment history, and get merchant statistics using Sui's official GraphQL API.

Overview

The Event Module provides methods for:

  • Query payment events (Created, Completed, Failed, Cancelled, Expired)
  • Query merchant events (Registered, Updated, Status Changed)
  • Get payment history with automatic event aggregation
  • Get merchant statistics (volume, fees, success rates)
  • Pagination support for large datasets

All queries use Sui's official GraphQL API - no custom indexer needed!

Setup

Event querying is automatically available when you create a DolphinPay client:

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

const client = createClient({
packageId: '0x9c7ca262d020b005e0e6b6a5d083b329d58716e0d80c07b46804324074468f9c',
network: 'testnet', // Uses Sui's GraphQL endpoint for testnet
});

// Event module is ready to use
const events = client.events;

Payment History

Get Complete Payment History

The easiest way to get payment history for a merchant:

const paymentHistory = await client.events.getPaymentHistory({
merchantAddress: '0xMERCHANT_OWNER_ADDRESS',
limit: 50,
});

console.log(`Total payments: ${paymentHistory.payments.length}`);

paymentHistory.payments.forEach((payment) => {
console.log(`\nPayment ${payment.id}:`);
console.log(` Amount: ${payment.amount} (${payment.currency})`);
console.log(` Status: ${payment.status}`); // 'pending' | 'completed' | 'failed' | 'expired' | 'cancelled'
console.log(` Description: ${payment.description}`);
console.log(` Created: ${new Date(payment.createdAt).toLocaleString()}`);

if (payment.payer) {
console.log(` Payer: ${payment.payer}`);
}

if (payment.completedAt) {
console.log(` Completed: ${new Date(payment.completedAt).toLocaleString()}`);
console.log(` Fee: ${payment.feeAmount}`);
console.log(` Net Amount: ${payment.netAmount}`);
}

if (payment.failureReason) {
console.log(` Failure Reason: ${payment.failureReason}`);
}
});

Payment History with Pagination

let cursor: string | null = null;
let allPayments: PaymentHistoryEntry[] = [];

do {
const result = await client.events.getPaymentHistory({
merchantAddress: '0xMERCHANT_ADDRESS',
limit: 100,
cursor,
});

allPayments = [...allPayments, ...result.payments];
cursor = result.pageInfo.hasNextPage ? result.pageInfo.endCursor : null;
} while (cursor);

console.log(`Fetched ${allPayments.length} total payments`);

Merchant Statistics

Get Aggregated Statistics

const stats = await client.events.getMerchantStatistics('0xMERCHANT_ADDRESS');

console.log('Merchant Statistics:');
console.log(` Total Payments: ${stats.totalPayments}`);
console.log(` Completed: ${stats.totalCompleted}`);
console.log(` Failed: ${stats.totalFailed}`);
console.log(` Cancelled: ${stats.totalCancelled}`);
console.log(` Expired: ${stats.totalExpired}`);
console.log(` Total Volume: ${stats.totalVolume} (MIST)`);
console.log(` Total Fees: ${stats.totalFees} (MIST)`);

// Convert to SUI for display
import { mistToSui } from '@dolphinpay/sdk';

const volumeInSui = mistToSui(stats.totalVolume);
const feesInSui = mistToSui(stats.totalFees);

console.log(`\n Volume: ${volumeInSui} SUI`);
console.log(` Fees: ${feesInSui} SUI`);

// Calculate success rate
const successRate = (stats.totalCompleted / stats.totalPayments) * 100;
console.log(` Success Rate: ${successRate.toFixed(2)}%`);

Querying Specific Event Types

Query PaymentCreated Events

const createdEvents = await client.events.queryPaymentCreated({
sender: '0xMERCHANT_ADDRESS',
limit: 20,
});

console.log(`Found ${createdEvents.events.length} created payments:`);

createdEvents.events.forEach((event) => {
console.log(`\nPayment ID: ${event.data.payment_id}`);
console.log(` Merchant: ${event.data.merchant}`);
console.log(` Amount: ${event.data.amount}`);
console.log(` Currency: ${event.data.currency}`);
console.log(` Description: ${event.data.description}`);
console.log(` Timestamp: ${event.timestamp}`);
});

Query PaymentCompleted Events

const completedEvents = await client.events.queryPaymentCompleted({
limit: 20,
});

completedEvents.events.forEach((event) => {
console.log(`\nPayment ${event.data.payment_id} completed:`);
console.log(` Merchant: ${event.data.merchant}`);
console.log(` Payer: ${event.data.payer}`);
console.log(` Amount: ${event.data.amount}`);
console.log(` Fee: ${event.data.fee_amount}`);
console.log(` Net Amount: ${event.data.net_amount}`);
console.log(` Timestamp: ${event.timestamp}`);
});

Query Payment Failed Events

const failedEvents = await client.events.queryPaymentFailed({
sender: '0xMERCHANT_ADDRESS',
limit: 10,
});

failedEvents.events.forEach((event) => {
console.log(`\nPayment ${event.data.payment_id} failed:`);
console.log(` Reason: ${event.data.reason}`);
console.log(` Payer: ${event.data.payer}`);
});

Query Payment Cancelled Events

const cancelledEvents = await client.events.queryPaymentCancelled({
sender: '0xMERCHANT_ADDRESS',
limit: 10,
});

cancelledEvents.events.forEach((event) => {
console.log(`\nPayment ${event.data.payment_id} cancelled:`);
console.log(` Reason: ${event.data.reason}`);
});

Query Payment Expired Events

const expiredEvents = await client.events.queryPaymentExpired({
limit: 10,
});

expiredEvents.events.forEach((event) => {
console.log(`Payment ${event.data.payment_id} expired at ${event.timestamp}`);
});

Merchant Events

Query Merchant Registered Events

const registeredEvents = await client.events.queryMerchantRegistered({
limit: 10,
});

registeredEvents.events.forEach((event) => {
console.log(`\nMerchant Registered:`);
console.log(` ID: ${event.data.merchant_id}`);
console.log(` Owner: ${event.data.owner}`);
console.log(` Name: ${event.data.name}`);
console.log(` Timestamp: ${event.timestamp}`);
});

Query Merchant Settings Updated

const updatedEvents = await client.events.queryMerchantSettingsUpdated({
sender: '0xMERCHANT_ADDRESS',
limit: 20,
});

updatedEvents.events.forEach((event) => {
console.log(`\nMerchant ${event.data.merchant_id} updated:`);
console.log(` Updated by: ${event.data.updated_by}`);
console.log(` Timestamp: ${event.timestamp}`);
});

Query Merchant Status Changes

const statusEvents = await client.events.queryMerchantStatusChanged({
limit: 10,
});

statusEvents.events.forEach((event) => {
const status = event.data.is_active ? 'Activated' : 'Deactivated';
console.log(`\nMerchant ${event.data.merchant_id} ${status}`);
console.log(` Changed by: ${event.data.changed_by}`);
console.log(` Timestamp: ${event.timestamp}`);
});

Advanced Query Patterns

Filter by Date Range

// Get payments from last 7 days
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;

const recentPayments = await client.events.getPaymentHistory({
merchantAddress: '0xMERCHANT_ADDRESS',
limit: 100,
});

const filteredPayments = recentPayments.payments.filter(
(payment) => payment.createdAt >= sevenDaysAgo
);

console.log(`Payments in last 7 days: ${filteredPayments.length}`);

Group Payments by Status

const history = await client.events.getPaymentHistory({
merchantAddress: '0xMERCHANT_ADDRESS',
limit: 200,
});

const byStatus = history.payments.reduce((acc, payment) => {
if (!acc[payment.status]) {
acc[payment.status] = [];
}
acc[payment.status].push(payment);
return acc;
}, {} as Record<string, PaymentHistoryEntry[]>);

console.log('Payments by Status:');
Object.entries(byStatus).forEach(([status, payments]) => {
console.log(` ${status}: ${payments.length}`);
});

Calculate Daily Statistics

interface DailyStats {
date: string;
count: number;
volume: bigint;
fees: bigint;
}

async function getDailyStats(merchantAddress: string, days: number) {
const history = await client.events.getPaymentHistory({
merchantAddress,
limit: 1000,
});

const dailyStats = new Map<string, DailyStats>();

history.payments
.filter((p) => p.status === 'completed')
.forEach((payment) => {
const date = new Date(payment.createdAt).toISOString().split('T')[0];

if (!dailyStats.has(date)) {
dailyStats.set(date, {
date,
count: 0,
volume: BigInt(0),
fees: BigInt(0),
});
}

const stats = dailyStats.get(date)!;
stats.count++;
stats.volume += BigInt(payment.amount);
stats.fees += BigInt(payment.feeAmount || 0);
});

return Array.from(dailyStats.values())
.sort((a, b) => b.date.localeCompare(a.date))
.slice(0, days);
}

// Get last 30 days
const stats = await getDailyStats('0xMERCHANT_ADDRESS', 30);
stats.forEach((day) => {
console.log(`${day.date}: ${day.count} payments, ${mistToSui(day.volume.toString())} SUI`);
});

Real-time Updates (Polling)

Since WebSocket is not recommended, use polling for near-real-time updates:

class PaymentMonitor {
private lastCursor: string | null = null;
private intervalId: NodeJS.Timeout | null = null;

async start(
merchantAddress: string,
onNewPayment: (payment: PaymentHistoryEntry) => void
) {
// Poll every 5 seconds
this.intervalId = setInterval(async () => {
try {
const result = await client.events.getPaymentHistory({
merchantAddress,
limit: 10,
cursor: this.lastCursor,
});

if (result.payments.length > 0) {
// Process new payments
result.payments.forEach(onNewPayment);

// Update cursor for next poll
this.lastCursor = result.pageInfo.endCursor;
}
} catch (error) {
console.error('Polling error:', error);
}
}, 5000);
}

stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
}

// Usage
const monitor = new PaymentMonitor();
monitor.start('0xMERCHANT_ADDRESS', (payment) => {
console.log('New payment:', payment.id);
// Update UI, send notification, etc.
});

// Stop when done
// monitor.stop();

Building Analytics Dashboard

Complete Dashboard Data

async function getDashboardData(merchantAddress: string) {
// Get statistics
const stats = await client.events.getMerchantStatistics(merchantAddress);

// Get recent payments
const history = await client.events.getPaymentHistory({
merchantAddress,
limit: 100,
});

// Calculate metrics
const totalRevenue = mistToSui(stats.totalVolume);
const totalFees = mistToSui(stats.totalFees);
const netRevenue = parseFloat(totalRevenue) - parseFloat(totalFees);
const successRate = (stats.totalCompleted / stats.totalPayments) * 100;

// Get recent activity (last 24 hours)
const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;
const recentPayments = history.payments.filter(
(p) => p.createdAt >= oneDayAgo
);

const recentCompleted = recentPayments.filter((p) => p.status === 'completed');
const recentRevenue = recentCompleted.reduce(
(sum, p) => sum + parseFloat(mistToSui(p.amount)),
0
);

return {
overview: {
totalPayments: stats.totalPayments,
completed: stats.totalCompleted,
failed: stats.totalFailed,
successRate: successRate.toFixed(2) + '%',
},
revenue: {
total: totalRevenue + ' SUI',
fees: totalFees + ' SUI',
net: netRevenue.toFixed(4) + ' SUI',
},
last24Hours: {
payments: recentPayments.length,
completed: recentCompleted.length,
revenue: recentRevenue.toFixed(4) + ' SUI',
},
recentPayments: history.payments.slice(0, 10),
};
}

// Usage
const dashboard = await getDashboardData('0xMERCHANT_ADDRESS');
console.log('Dashboard Data:', JSON.stringify(dashboard, null, 2));

Error Handling

try {
const history = await client.events.getPaymentHistory({
merchantAddress: '0xMERCHANT_ADDRESS',
limit: 50,
});

console.log(`Found ${history.payments.length} payments`);
} catch (error) {
if (error.message.includes('GraphQL')) {
console.error('GraphQL query failed:', error.message);
} else if (error.message.includes('Network')) {
console.error('Network error, retrying...');
// Implement retry logic
} else {
console.error('Unexpected error:', error);
}
}

Best Practices

1. Use Pagination for Large Datasets

// Good: Paginate through large result sets
let cursor: string | null = null;
do {
const result = await client.events.getPaymentHistory({
merchantAddress: '0xMERCHANT_ADDRESS',
limit: 100, // Reasonable page size
cursor,
});
// Process result.payments
cursor = result.pageInfo.hasNextPage ? result.pageInfo.endCursor : null;
} while (cursor);

// Bad: Trying to fetch everything at once
// const result = await client.events.getPaymentHistory({
// merchantAddress: '0xMERCHANT_ADDRESS',
// limit: 10000, // Too large!
// });

2. Cache Results

// Cache payment history to reduce API calls
const cache = new Map<string, { data: any; timestamp: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

async function getCachedPaymentHistory(merchantAddress: string) {
const cached = cache.get(merchantAddress);

if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}

const data = await client.events.getPaymentHistory({
merchantAddress,
limit: 100,
});

cache.set(merchantAddress, { data, timestamp: Date.now() });
return data;
}

3. Handle Rate Limiting

async function queryWithRetry<T>(
queryFn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await queryFn();
} catch (error) {
if (i === maxRetries - 1) throw error;

// Exponential backoff
await new Promise((resolve) =>
setTimeout(resolve, Math.pow(2, i) * 1000)
);
}
}
throw new Error('Max retries exceeded');
}

// Usage
const history = await queryWithRetry(() =>
client.events.getPaymentHistory({
merchantAddress: '0xMERCHANT_ADDRESS',
limit: 50,
})
);

Next Steps

API Reference

For complete API documentation, see: