Payment Kit Standard
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.
Key features
The Payment Kit provides the following core capabilities:
- Secure payment processing: Validates payment amounts and transfers coins safely.
- Payment registries: Optional persistent storage for payment receipts with duplicate detection.
- Flexible receipt management: Generates receipts for payment tracking and verification.
- Event-driven architecture: Emits events for off-chain tracking and integration.
- Multi-coin support: Works with any Sui coin type.
- Transaction URIs: Standardized URI format in order to create encoded links for user friendly payment flows.
Architecture components
The Payment Kit consists of the following main components:
Payment processing core
Handles coin transfers, payment validation, and receipt generation. The core validates that:
- Payment amounts match expected values
- Coins have sufficient balance
- Transfers complete successfully
- Receipts contain accurate payment information
Registry system
Optional persistent storage that tracks payment history and prevents duplicate payments. Registries provide:
- Payment record storage with composite keys
- Configurable expiration policies for payment records
- Withdrawal capabilities for accumulated funds
- Administrative controls via capabilities
Core concepts
Payment modes
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:
- You need to prevent duplicate payments
- Payment history must be tracked
- Compliance or auditing requires payment records
- Funds should accumulate in a managed registry
2. Ephemeral payments: Process one-time payments without persistent storage. Use this mode when:
- Duplicate prevention is not enforced
Duplicate prevention
Duplicate prevention is enforced when processing payments via a PaymentRegistry. The system uses a composite PaymentKey derived from:
- Nonce: Unique identifier for each payment
(UUIDv4). - Amount: Payment value in coin units.
- Coin type: The specific coin type.
- Receiver address: Destination address for the payment.
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.
Payment receipts
Every processed payment generates a PaymentReceipt object containing:
- Payment nonce for reference
- Amount paid
- Coin type used
- Receiver address
- Timestamp of payment
- Registry information (for registry payments; not applicable to ephemeral payments)
Receipts serve as proof of payment and can be used for off-chain verification, accounting, or integration with other systems.
Payment records
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:
- Creation: When
process_registry_paymentis called, aPaymentRecordis created and stored in the registry using the compositePaymentKey. - Storage: Records persist in the registry's internal table, indexed by their unique payment key.
- Expiration: Records become eligible for deletion after the registry's configured
epoch_expiration_durationhas passed. - Deletion: Expired records can be removed using
delete_payment_recordto reclaim storage and reduce gas costs.
PaymentRecord expiration:
A PaymentRecord include an expiration epoch calculated at the time of payment creation. This expiration mechanism:
- Prevents indefinite storage growth in registries
- Allows for eventual cleanup of historical payment data
- Balances duplicate prevention needs with storage efficiency
- Can be configured per-registry via
set_config_epoch_expiration_duration
A 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.
Working with payment registries
Creating a registry
To create a new payment registry, you need to provide a name which is simply an ACSII-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.
Namespace objects
mainnet: 0xccd3e4c7802921991cd9ce488c4ca0b51334ba75483702744242284ccf3ae7c2
testnet: 0xa5016862fdccba7cc576b56cc5a391eda6775200aaa03a6b3c97d512312878db
Processing registry payments
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 (ifNone, funds stay in registry).clock: Sui clock object for timestamping.ctx: Transaction context.
The function:
- Verifies the payment amount matches the coin value
- Checks for duplicate payments using the composite key
- Records the payment in the registry
- Transfers funds to
receiveror the registry (based on configuration) - Generates and returns a
PaymentReceipt - Emits a payment event
Error conditions:
EDuplicatePayment: Payment with same composite key already exists.EPaymentAmountMismatch: Coin value doesn't match expected amount.
Managing payment records
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.
Configuring registries
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.
Withdrawing from a registry
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).
Processing ephemeral payments
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:
- Do not check for duplicates
- Do not store payment records on-chain
- Transfer funds immediately to the receiver
- Generate receipts for the transaction
- Emit payment events for off-chain tracking
- Have lower gas costs than registry payments
This mode is ideal for:
- Duplicate prevention is not required
- Applications with external payment tracking systems
Transaction URIs
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.
URI format
Payment Kit Transaction URIs use the following format:
sui:<address>
?amount=<amount>
&coinType=<coinType>
&nonce=<nonce>
&label=<label>
&icon=<icon>
&message=<message>
®istry=<registry>
URI parameters
address
The destination address to receive the payment funds. Must be a valid Sui address. There is no pathname for the destination address.
amount
The amount field is a required pathname. The value must be a non-negative integer or decimal numbers. This field represents the display amount of a specified coin type (for example, 0.05 SUI).
If an amount value represents a decimal that is less than 1, it must must include a leading 0 before the decimal.
nonce
The nonce field is a required pathname. 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.
coinType
The coinType field is a required pathname. The full type identifier of the coin to be transferred (for example, 0x2::sui::SUI).
label
A human-readable name for the merchant or application receiving payment.
icon
A URL pointing to an icon image for the merchant or application. Maybe be displayed to users during payment confirmation.
message
A description or context for the payment. May be displayed to users to explain the purpose of the payment.
registry
The 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.
Example URIs
Basic SUI payment:
sui:0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
?amount=0.62
&nonce=550e8400-e29b-41d4-a716-446655440000
Payment with custom coin type:
sui:0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
?amount=50
&coinType=0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN
&nonce=550e8400-e29b-41d4-a716-446655440000
Payment with display metadata:
sui:0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
?amount=1.2
&nonce=550e8400-e29b-41d4-a716-446655440000
&label=Coffee%20Shop
&icon=https://example.com/icon.png
&message=Espresso%20and%20croissant
Registry-based payment (registry object ID):
sui:0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
?amount=0.1
&nonce=550e8400-e29b-41d4-a716-446655440000
®istry=0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
Registry-based payment (Registry ACSII Name):
sui:0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
?amount=0.1
&nonce=550e8400-e29b-41d4-a716-446655440000
®istry=default-payment-registry
URI encoding
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:
- Space:
%20 - Colon:
%3A - Slash:
%2F - Double colon (
::):%3A%3A
Implementation notes
When implementing transaction URI support:
- Validate all parameters: Ensure addresses are valid Sui addresses, amounts are valid
u64values, and coin types follow the correct format. - Generate unique nonces: Use UUIDv4 or another cryptographically secure random identifier to prevent accidental duplicates.
- Handle missing optional parameters: Provide sensible defaults (such as
0x2::sui::SUIforcoinType) when optional parameters are omitted. - Display metadata to users: Show the
label,icon, andmessageparameters to users during payment confirmation to provide context. - Route to correct payment function: Use
process_registry_paymentwhenregistryis provided, otherwise useprocess_ephemeral_payment.
Key structures
Namespace
Represents the higher-order namespace for organizing payment registries:
public struct Namespace has key, store {
id: UID,
}
PaymentRegistry
Tracks payments and receipts with duplicate prevention:
public struct PaymentRegistry has key {
id: UID,
cap_id: ID,
config: VecMap<String, Value>,
version: u16,
}
RegistryAdminCap
Provides administrative capabilities for a specific registry:
public struct RegistryAdminCap has key, store {
id: UID,
registry_id: ID,
}
PaymentType
Enum representing the type of payment: Ephemeral (one-time) or Registry (tracked in a registry).
public enum PaymentType has copy, drop, store {
Ephemeral,
Registry(ID),
}
PaymentReceipt
Contains 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,
}
PaymentKey
Unique key for identifying payment records:
public struct PaymentKey<phantom T> has copy, drop, store {
nonce: String,
payment_amount: u64,
receiver: address,
}
PaymentRecord
Internal structure storing payment record information:
public struct PaymentRecord has store {
epoch_at_time_of_record: u64,
}
Events
The Payment Kit emits events for off-chain tracking and integration. Payment processing functions emit events containing:
- Payment nonce
- Payment amount
- Coin type
- Receiver address
- Timestamp
- Registry information (for registry payments)
Use these events to:
- Track payment history off-chain
- Trigger external workflows
- Update application state
- Generate reports and analytics
- Integrate with accounting systems
Error codes
The Payment Kit defines the following error conditions:
- Duplicate payment detection: Payment with the same composite key already processed.
- Payment amount mismatch: Coin value doesn't match expected payment amount.
- Payment record not found: Attempted to access non-existent payment record.
- Payment record not expired: Attempted to delete a record before expiration.
- Unauthorized admin: Operation requires
RegistryAdminCap. - Registry already exists: Attempted to create duplicate registry in namespace.
- Invalid registry name: Registry name doesn't meet requirements.
Source code
Related links
Derived objects enable deterministic object addresses, Transfer-to-Object capabilities, guaranteed uniqueness, and native parallelization for building scalable composable systems on Sui.