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
| Value | Status | Meaning |
|---|---|---|
0 | PENDING | Created, awaiting execution |
1 | SUCCESS | Executed; funds transferred |
2 | FAILED | Reserved; not set by any current code path |
3 | EXPIRED | Declared status; not persisted by the current expiry path |
4 | CANCELLED | Cancelled by the merchant |
Lifecycle
- Create —
create_payment<T>(orcreate_payment_with_merchant<T>) validates the currency againstAdminConfig, validates inputs, and returns aPaymentin PENDING status.Tmust be the allowed currency (native Circle USDC). - Execute —
execute_payment<T>(orexecute_payment_with_merchant<T>) takes aCoin<T>whose value exactly equalsamount, reads the fee rate fromAdminConfig(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). - Cancel —
cancel_paymentlets the merchant cancel a PENDING payment. - Expire — execution after
expires_atattempts to mark the payment EXPIRED and then aborts. Because the transaction aborts, that write is rolled back: the stored object remains PENDING and noPaymentExpiredevent is emitted. Off-chain code must compareexpires_atwith 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 inu128. 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_paymentandexecute_paymentabort withE_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