docs/content/standards/payment-kit.mdx
The Sui Payment Kit is a framework for secure, flexible payment processing on Sui. It provides persistent and ephemeral payment options, event-driven architecture, and built-in duplicate prevention.
The Payment Kit standardizes payment processing on Sui, enabling developers to build robust payment flows without reimplementing common payment verification and receipt management logic. Applications using the Payment Kit benefit from battle-tested security patterns and consistent payment handling across the ecosystem.
The Payment Kit provides the following core capabilities:
The Payment Kit consists of the following main components:
Handles coin transfers, payment validation, and receipt generation. The core validates that:
Optional persistent storage that tracks payment history and prevents duplicate payments. Registries provide:
The Payment Kit supports two payment processing modes:
1. Registry payments: Process payments through a PaymentRegistry with duplicate prevention and persistent receipts. Use this mode when:
2. Ephemeral payments: Process one-time payments without persistent storage. Use this mode when:
Duplicate prevention is enforced when processing payments via a PaymentRegistry. The system uses a composite PaymentKey derived from:
(UUIDv4).This composite key ensures that the same payment cannot be processed twice, even if individual components (like amount or receiver) are reused across different payments.
Every processed payment generates a PaymentReceipt object containing:
Receipts serve as proof of payment and can be used for off-chain verification, accounting, or integration with other systems.
When payments are processed through a PaymentRegistry, the system creates and stores PaymentRecord objects internally to track payment history and enable duplicate prevention.
Payment records differ from payment receipts in key ways:
PaymentRecords vs PaymentReceipts:
PaymentRecords: Internal registry storage structures that persist payment metadata for duplicate detection. These are stored within the registry's internal tables and are not directly accessible as objects.PaymentReceipts: User-facing objects returned after payment processing that serve as proof of payment. These can be stored, transferred, or used for off-chain verification.PaymentRecord lifecycle:
process_registry_payment is called, a PaymentRecord is created and stored in the registry using the composite PaymentKey.epoch_expiration_duration has passed.delete_payment_record to reclaim storage and reduce gas costs.PaymentRecord expiration:
A PaymentRecord include an expiration epoch calculated at the time of payment creation. This expiration mechanism:
set_config_epoch_expiration_durationA PaymentRecord cannot be deleted before its expiration epoch, ensuring a minimum retention period for duplicate detection. After expiration, administrators or users can delete records to free storage, though deletion is optional.
To create a new payment registry, you need to provide a name which is simply an ASCII-based string that is used to derive an address for the registry. In addition to a name, the package Namespace object must also be provided. Namespace provides a higher-order organizational structure for managing multiple payment registries.
module sui::payment_kit;
public fun create_registry(
namespace: &mut Namespace,
name: String,
ctx: &mut TxContext
)
This function creates a PaymentRegistry and a RegistryAdminCap for administrative control. The RegistryAdminCap is initially owned by the creator and can be shared or transferred as needed.
mainnet: 0xccd3e4c7802921991cd9ce488c4ca0b51334ba75483702744242284ccf3ae7c2
testnet: 0xa5016862fdccba7cc576b56cc5a391eda6775200aaa03a6b3c97d512312878db
Process payments through a registry with duplicate prevention:
module sui::payment_kit;
public fun process_registry_payment<T>(
registry: &mut PaymentRegistry,
nonce: String,
payment_amount: u64,
coin: Coin<T>,
receiver: Option<address>,
clock: &Clock,
ctx: &mut TxContext
)
Parameters:
registry: Mutable reference to the payment registry.nonce: Unique payment identifier (prevents duplicates).payment_amount: Expected payment amount in coin units.coin: Payment coin object.receiver: Optional receiver address (if None, funds stay in registry).clock: Sui clock object for timestamping.ctx: Transaction context.The function:
receiver or the registry (based on configuration)PaymentReceiptError conditions:
EDuplicatePayment: Payment with same composite key already exists.EPaymentAmountMismatch: Coin value doesn't match expected amount.Delete an expired PaymentRecord to free up storage:
module sui::payment_kit;
public fun delete_payment_record<T>(
registry: &mut PaymentRegistry,
payment_key: PaymentKey<T>,
ctx: &mut TxContext
)
Records can only be deleted after they expire based on the registry's configured expiration duration. Create a PaymentKey using the create_payment_key function with the original payment parameters.
Registry administrators can update configuration settings using the RegistryAdminCap:
Set expiration duration:
module sui::payment_kit;
public fun set_config_epoch_expiration_duration(
registry: &mut PaymentRegistry,
cap: &RegistryAdminCap,
epoch_expiration_duration: u64,
ctx: &mut TxContext
)
Set PaymentRegistry to receive funds:
module sui::payment_kit;
public fun set_config_registry_managed_funds(
registry: &mut PaymentRegistry,
cap: &RegistryAdminCap,
registry_managed_funds: bool,
ctx: &mut TxContext
)
When registry_managed_funds is true, payments accumulate in the registry for later withdrawal. When false, payments transfer immediately to receivers.
If a PaymentRegistry is set to manage funds, an administrator can withdraw accumulated funds:
module sui::payment_kit;
public fun withdraw_from_registry<T>(
registry: &mut PaymentRegistry,
cap: &RegistryAdminCap,
ctx: &mut TxContext
)
This function requires the RegistryAdminCap and returns all accumulated coins of type T from the registry. Only use this when the registry is configured to retain funds (controlled by the registry_managed_funds configuration setting).
For scenarios that don't require duplicate prevention or persistent records, use ephemeral payments:
module sui::payment_kit;
public fun process_ephemeral_payment<T>(
nonce: String,
payment_amount: u64,
coin: Coin<T>,
receiver: address,
clock: &Clock,
ctx: &mut TxContext
)
Ephemeral payments:
This mode is ideal for:
The Payment Kit defines a standard URI format for encoding payment requests. Transaction URIs allow applications to generate payment links that wallets and other clients can parse and execute.
Payment Kit Transaction URIs use the following format:
sui:pay?receiver=<receiverAddress>
&amount=<amount>
&coinType=<coinType>
&nonce=<nonce>
&label=<label>
&iconUrl=<iconUrl>
&message=<message>
®istry=<registry>
receiverThe destination address to receive the payment funds. Must be a valid Sui address.
amountThe amount field is a required query parameter. The value must be a positive u64. This field represents the native amount of a specified coin type (for example, 100000000 MISTS represents 0.1 SUI).
nonceThe nonce field is a required query parameter. This represents a unique ASCII-based identifier for this payment. Must be unique within the registry to prevent duplicate payments. Recommended format is UUIDv4. Cannot exceed 36 characters.
coinTypeThe coinType field is a required query parameter. The full type identifier of the coin to be transferred (for example, 0x2::sui::SUI or 0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI).
labelA human-readable name for the merchant or application receiving payment.
iconUrlA URL pointing to an icon image for the merchant or application. May be displayed to users during payment confirmation.
messageA description or context for the payment. May be displayed to users to explain the purpose of the payment.
registryThe object ID or ASCII-represented name of the PaymentRegistry to use for processing the payment. If provided, the payment is processed through the registry with duplicate prevention. If omitted, the payment is processed as an ephemeral payment.
Basic SUI payment:
sui:pay?receiver=0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
&amount=1000000000
&coinType=0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI
&nonce=550e8400-e29b-41d4-a716-446655440000
Payment with display metadata:
sui:pay?receiver=0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
&amount=1000000000
&coinType=0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI
&nonce=550e8400-e29b-41d4-a716-446655440000
&label=Coffee%20Shop
&iconUrl=https://example.com/icon.png
&message=Espresso%20and%20croissant
Registry-based payment (registry object ID):
sui:pay?receiver=0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
&amount=1000000000
&coinType=0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI
&nonce=550e8400-e29b-41d4-a716-446655440000
®istry=0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
Registry-based payment (registry ASCII name):
sui:pay?receiver=0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
&amount=1000000000
&coinType=0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI
&nonce=550e8400-e29b-41d4-a716-446655440000
®istry=default-payment-registry
All parameter values must be properly URL-encoded according to RFC 3986. Special characters in parameter values (such as spaces, colons, slashes) must be percent-encoded. Examples include:
%20%3A%2F::): %3A%3AWhen implementing transaction URI support:
u64 values, nonces do not exceed 36 characters, and coin types follow the correct format.label, iconUrl, and message parameters to users during payment confirmation to provide context.process_registry_payment when registry is provided, otherwise use process_ephemeral_payment.NamespaceRepresents the higher-order namespace for organizing payment registries:
public struct Namespace has key, store {
id: UID,
}
PaymentRegistryTracks payments and receipts with duplicate prevention:
public struct PaymentRegistry has key {
id: UID,
cap_id: ID,
config: VecMap<String, Value>,
version: u16,
}
RegistryAdminCapProvides administrative capabilities for a specific registry:
public struct RegistryAdminCap has key, store {
id: UID,
registry_id: ID,
}
PaymentTypeEnum representing the type of payment: Ephemeral (one-time) or Registry (tracked in a registry).
public enum PaymentType has copy, drop, store {
Ephemeral,
Registry(ID),
}
PaymentReceiptContains details of a processed payment:
public struct PaymentReceipt has key, store {
payment_type: PaymentType,
nonce: String,
payment_amount: u64,
receiver: address,
coin_type: String,
timestamp_ms: u64,
}
PaymentKeyUnique key for identifying payment records:
public struct PaymentKey<phantom T> has copy, drop, store {
nonce: String,
payment_amount: u64,
receiver: address,
}
PaymentRecordInternal structure storing payment record information:
public struct PaymentRecord has store {
epoch_at_time_of_record: u64,
}
The Payment Kit emits events for off-chain tracking and integration. Payment processing functions emit events containing:
Use these events to:
The Payment Kit defines the following error conditions:
RegistryAdminCap.