Skip to main content

Core Modules

The DolphinPay package contains four modules: payment, merchant, admin, and events. This page covers the data structures and how they interact; the per-function references are in Payment API, Merchant API, and Admin API.

Payment module

dolphinpay::payment handles the payment lifecycle: create → execute, or create → cancel/expire.

Payment object

public struct Payment has key, store {
id: UID,
merchant: address,
payer: Option<address>,
amount: u64, // USDC base units (6 decimals)
currency: TypeName,
created_at: u64, // ms
completed_at: Option<u64>, // ms
expires_at: u64, // ms
status: u8,
metadata: VecMap<String, String>,
description: String,
refundable: bool, // always false (ADR-004)
refund_window: u64, // always 0 (ADR-004)
}

refundable and refund_window are legacy layout fields kept for compatibility. They are hard-coded to false/0 at creation and there is no function that changes them — there are no on-chain refunds.

Status codes

ValueStatusMeaning
0PENDINGCreated, awaiting execution
1SUCCESSExecuted; funds transferred
2FAILEDReserved; not set by any current code path
3EXPIREDDeclared status; not persisted by the current expiry path
4CANCELLEDCancelled by the merchant

Lifecycle

  1. Createcreate_payment<T> (or create_payment_with_merchant<T>) validates the currency against AdminConfig, validates inputs, and returns a Payment in PENDING status. T must be the allowed currency (native Circle USDC).
  2. Executeexecute_payment<T> (or execute_payment_with_merchant<T>) takes a Coin<T> whose value exactly equals amount, reads the fee rate from AdminConfig (0 bps by policy), routes any non-zero fee to the treasury, and transfers the remainder to the merchant. State is updated before transfers (checks-effects-interactions).
  3. Cancelcancel_payment lets the merchant cancel a PENDING payment.
  4. Expire — execution after expires_at attempts to mark the payment EXPIRED and then aborts. Because the transaction aborts, that write is rolled back: the stored object remains PENDING and no PaymentExpired event is emitted. Off-chain code must compare expires_at with the current time rather than relying on a persisted EXPIRED state.

Merchant module

dolphinpay::merchant manages merchant records. Merchants have no fee configuration — the platform fee lives only in AdminConfig.

Merchant object

public struct Merchant has key, store {
id: UID,
owner: address,
name: String, // 1–100 chars
description: String, // ≤ 500 chars
receiving_addresses: VecMap<TypeName, address>,
settings: MerchantSettings,
created_at: u64,
is_active: bool,
supported_currencies: VecSet<TypeName>,
}
public struct MerchantCap has key {
id: UID,
merchant_id: ID,
}
public struct MerchantSettings has store {
auto_settlement: bool,
settlement_threshold: u64,
webhook_url: String,
api_keys: vector<String>,
require_confirmation: bool,
}

register_merchant shares the Merchant object and transfers the MerchantCap to the sender. All mutations require the matching cap.

Although the merchant can register a receiving address per coin type, the platform accepts only one currency (USDC), so in practice at most the USDC entry matters. execute_payment_with_merchant sends funds to the merchant's USDC receiving address if set, otherwise to the merchant owner address.

Admin module

dolphinpay::admin is the governance module. init runs once at publish: it transfers an AdminCap to the deployer and shares an AdminConfig.

AdminConfig object

public struct AdminConfig has key {
id: UID,
default_platform_fee_bps: u64, // 0 by policy
treasury: address, // @0x0 until set
allowed_currency: Option<TypeName>, // None until set; payments abort
total_merchants: u64,
total_payments: u64,
total_volume: u64,
}

Fee model

  • One global fee in AdminConfig.default_platform_fee_bps, initialized to 0 and currently 0 by policy.
  • Contract ceiling: MAX_PLATFORM_FEE_BPS = 1000 (10%).
  • Setting a non-zero fee aborts unless a treasury address is configured (E_FEE_REQUIRES_TREASURY).
  • Fee math: fee = amount * fee_bps / 10000, computed in u128. Any non-zero fee is transferred to the treasury, never to the merchant.
  • There are no per-merchant fees and no fee constants anywhere else.

Currency policy

  • Exactly one coin type is accepted platform-wide: native Circle USDC (6 decimals).
  • It is set post-deploy via set_allowed_currency<T> because the USDC package differs per network and is not a build dependency.
  • Until it is set, create_payment and execute_payment abort with E_CURRENCY_NOT_SET.

Events module

dolphinpay::events defines the event structs and emit_* helpers used by the other modules:

  • Payment: PaymentCreated, PaymentCompleted, PaymentFailed (defined but not emitted by any current flow), PaymentCancelled, PaymentExpired (defined; expiry is currently recorded via status, not this event).
  • Merchant: MerchantRegistered, MerchantSettingsUpdated, MerchantStatusChanged.

Off-chain clients index these events through the SDK's Sui gRPC client.

End-to-end example (Move)

// Merchant registers (shares Merchant, transfers MerchantCap to sender)
merchant::register_merchant(
string::utf8(b"Acme Store"),
string::utf8(b"Digital goods"),
ctx,
);

// Anyone creates a payment of 10 USDC (10_000_000 base units)
let payment = payment::create_payment<USDC>(
&admin_config,
merchant_address,
10_000_000,
string::utf8(b"Order #12345"),
vec_map::empty(),
3600, // 1 hour expiry
ctx,
);
transfer::public_share_object(payment);

// Payer executes with a Coin<USDC> of exactly 10_000_000
payment::execute_payment<USDC>(&mut payment, &admin_config, coin, ctx);

Testing

cd contract
sui move test

Next steps