Skip to main content

Purchase Contract

Overview

The Purchase Contract provides an automated system for handling token purchases with stablecoin payments. It integrates directly with the RestrictedLockupToken and InterestPayment contracts to provide atomic purchase transactions that include originator payments, admin fees, interest prefunding, and token minting.

Key Features

  • Atomic Purchase Transactions: All operations (payments, fees, minting) occur in a single transaction
  • Multi-Party Payment Distribution: Supports originator payments, admin fees, and interest prefunding
  • AML/KYC Integration: Validates both payer and recipient compliance status
  • Purchase ID Tracking: Prevents duplicate transactions with unique purchase identifiers
  • Automation-Ready: Designed for automated execution by external systems
  • Flexible Configuration: Configurable wallet addresses for different payment recipients

Usage

Architecture Overview

Purchase Execution Flow

1. Purchase Parameters

Each purchase requires a structured set of parameters:

// For purchases with interest prefunding
struct PurchaseParamsWithInterest {
string purchaseId; // Unique identifier
address payerAddress; // Who pays USDC
address tokenRecipientAddress; // Who receives tokens
uint256 mintAmount; // Number of tokens to mint
uint256 originatorPurchaseAmount; // Payment to originator
uint256 prefundedInterestAmount; // Interest prefunding
uint256 adminFeeExcludingPrefundedInterestAmount; // Admin fees
uint256 totalAmount; // Total USDC required
}

// For basic purchases without interest
struct PurchaseParams {
string purchaseId; // Unique identifier
address payerAddress; // Who pays USDC
address tokenRecipientAddress; // Who receives tokens
uint256 originatorPurchaseAmount; // Payment to originator
uint256 mintAmount; // Number of tokens to mint
uint256 adminFeeAmount; // Admin fees
uint256 totalAmount; // Total USDC required
}

2. Execution Process

3. Token Minting

The contract mints the specified number of tokens to the recipient address. The mint amount is provided as a parameter in the purchase struct and must be calculated off-chain based on the purchase terms.

Example:

  • Purchase amount: $50,000 USDC
  • Token price: $1 per token
  • Tokens to mint: 50,000 tokens (calculated off-chain)
  • The mintAmount parameter would be set to 50,000

Configuration Management

Wallet Address Management

The contract supports configurable wallet addresses for different payment types:

// Set originator payment wallet
purchaseContract.updateOriginatorPaymentWallet(newOriginatorWallet);

// Set admin fee wallet
purchaseContract.updateAdminFeeWallet(newAdminFeeWallet);

// Set canceled purchase wallet
purchaseContract.updateCanceledPurchaseWallet(newCanceledPurchaseWallet);

// Set automation admin (who can execute purchases)
purchaseContract.updateAutomationAdmin(newAutomationAdmin);

Purchase Cancellation

In case of issues, purchases can be canceled by the automation admin:

Validation and Security Features

AML/KYC Validation

Both payer and token recipient must pass AML/KYC validation:

// Validated automatically during purchase execution
require(tokenContract.isAmlKycPassed(params.tokenRecipientAddress), "Recipient AML/KYC failed");
require(tokenContract.isAmlKycPassed(params.payerAddress), "Payer AML/KYC failed");

Purchase ID Uniqueness

Each purchase must have a unique identifier to prevent duplicate executions:

require(!usedPurchaseIds[params.purchaseId], "Purchase ID already used");
usedPurchaseIds[params.purchaseId] = true;

Amount Validation

The contract validates that the total amount equals the sum of all components:

require(
params.totalAmount ==
params.originatorPurchaseAmount +
params.prefundedInterestAmount +
params.adminFeeExcludingPrefundedInterestAmount,
"Invalid total amount"
);

Integration Example

Here's a complete example of executing a purchase:

// 1. Prepare purchase parameters (with interest prefunding)
PurchaseParamsWithInterest memory params = PurchaseParamsWithInterest({
purchaseId: "PURCHASE-2024-001",
payerAddress: investor,
tokenRecipientAddress: investor,
mintAmount: 100000e18, // 100,000 tokens (18 decimals)
originatorPurchaseAmount: 100000e6, // $100,000 USDC
prefundedInterestAmount: 5000e6, // $5,000 USDC interest prefunding
adminFeeExcludingPrefundedInterestAmount: 500e6, // $500 USDC admin fee
totalAmount: 105500e6 // $105,500 USDC total
});

// 2. Investor approves USDC spending
usdcToken.approve(purchaseContractAddress, params.totalAmount);

// 3. Automation admin executes purchase with interest
purchaseContract.executePurchaseWithInterest(params);

// Result:
// - Originator receives: $100,000 USDC
// - Interest payment contract receives: $5,000 USDC
// - Admin wallet receives: $500 USDC
// - Investor receives: 100,000 security tokens

Administrative Functions

Owner Functions

  • updateAutomationAdmin(): Change who can execute purchases
  • updateAdminFeeWallet(): Update admin fee recipient
  • updateCanceledPurchaseWallet(): Update cancellation refund recipient
  • updateOriginatorPaymentWallet(): Update originator payment recipient

Automation Admin Functions

  • executePurchase(): Execute a basic purchase transaction (without interest prefunding)
  • executePurchaseWithInterest(): Execute a purchase transaction with interest prefunding
  • cancelPurchase(): Cancel and refund a purchase

Error Handling

  • PurchaseContract_PurchaseIdAlreadyUsed: Duplicate purchase ID
  • PurchaseContract_TokenRecipientAmlKycNotPassed: Recipient compliance failure
  • PurchaseContract_PayerAmlKycNotPassed: Payer compliance failure
  • PurchaseContract_InvalidTotalAmount: Amount calculation mismatch
  • PurchaseContract_AdminFeeWalletNotSet: Required wallet address not configured

API Reference

Constructor

constructor(address contractAdmin_, address trustedForwarder_, address tokenContract_, address interestPayment_, address paymentToken_)

Initializes the PurchaseContract with configuration parameters.

Parameters:

  • contractAdmin_ (address): The contract owner/admin address
  • trustedForwarder_ (address): ERC-2771 trusted forwarder address for meta-transactions
  • tokenContract_ (address): RestrictedLockupToken contract address
  • interestPayment_ (address): InterestPayment contract address (can be zero address)
  • paymentToken_ (address): ERC-20 payment token address (typically USDC)

Requirements:

  • All addresses must be non-zero except interestPayment_
  • If interestPayment_ is provided, its payment token must match paymentToken_
  • If interestPayment_ is provided, its restricted token must match tokenContract_

Administrative Functions (API)

updateAutomationAdmin(address newAutomationAdmin_)

Updates the automation admin address who can execute purchases.

Parameters:

  • newAutomationAdmin_ (address): New automation admin address

Requirements:

  • Caller must be contract owner
  • Address must not be zero

Emits: AutomationAdminUpdated(oldAdmin, newAutomationAdmin_)


updateAdminFeeWallet(address newAdminFeeWallet_)

Updates the admin fee wallet address.

Parameters:

  • newAdminFeeWallet_ (address): New admin fee wallet address

Requirements:

  • Caller must be contract owner
  • Address must not be zero

Emits: AdminFeeWalletUpdated(oldWallet, newAdminFeeWallet_)


updateCanceledPurchaseWallet(address newCanceledPurchaseWallet_)

Updates the canceled purchase wallet address.

Parameters:

  • newCanceledPurchaseWallet_ (address): New canceled purchase wallet address

Requirements:

  • Caller must be contract owner
  • Address must not be zero

Emits: CanceledPurchaseWalletUpdated(oldWallet, newCanceledPurchaseWallet_)


updateOriginatorPaymentWallet(address newOriginatorPaymentWallet_)

Updates the originator payment wallet address.

Parameters:

  • newOriginatorPaymentWallet_ (address): New originator payment wallet address

Requirements:

  • Caller must be contract owner
  • Address must not be zero

Emits: OriginatorPaymentWalletUpdated(oldWallet, newOriginatorPaymentWallet_)


Purchase Functions

executePurchaseWithInterest(PurchaseParamsWithInterest params)

Executes a token purchase with USDC payment and interest funding.

Parameters:

  • params (PurchaseParamsWithInterest): Purchase parameters struct

PurchaseParamsWithInterest Structure:

struct PurchaseParamsWithInterest {
string purchaseId; // Unique purchase identifier
address payerAddress; // Address paying for the purchase
address tokenRecipientAddress; // Address receiving the tokens
uint256 mintAmount; // Number of tokens to mint
uint256 originatorPurchaseAmount; // Payment amount to originator
uint256 prefundedInterestAmount; // Amount to prefund interest
uint256 adminFeeExcludingPrefundedInterestAmount; // Admin fee amount
uint256 totalAmount; // Total payment amount
}

Requirements:

  • Caller must be automation admin
  • Interest payment contract must be configured
  • Purchase ID must be unique
  • Admin fee wallet and originator payment wallet must be set
  • Mint amount must be greater than zero
  • Total amount must equal sum of all component amounts
  • Both payer and token recipient must pass AML/KYC validation

Emits: PurchaseExecuted(purchaseId, payerAddress, tokenRecipientAddress, authorityAddress, originatorPurchaseAmount, prefundedInterestAmount, adminFeeExcludingPrefundedInterestAmount, mintAmount, payerAddress)


executePurchase(PurchaseParams params)

Executes a token purchase with USDC payment (without interest funding).

Parameters:

  • params (PurchaseParams): Purchase parameters struct

PurchaseParams Structure:

struct PurchaseParams {
string purchaseId; // Unique purchase identifier
address payerAddress; // Address paying for the purchase
address tokenRecipientAddress; // Address receiving the tokens
uint256 originatorPurchaseAmount; // Payment amount to originator
uint256 mintAmount; // Number of tokens to mint
uint256 adminFeeAmount; // Admin fee amount
uint256 totalAmount; // Total payment amount
}

Requirements:

  • Caller must be automation admin
  • Purchase ID must be unique
  • Admin fee wallet and originator payment wallet must be set
  • Both payer and token recipient must pass AML/KYC validation
  • Total amount must equal originator amount plus admin fee amount
  • Mint amount must be greater than zero

Emits: PurchaseExecuted(purchaseId, payerAddress, tokenRecipientAddress, authorityAddress, originatorPurchaseAmount, 0, adminFeeAmount, mintAmount, payerAddress)


cancelPurchase(address paymentTokenAddress_, uint256 amount_)

Cancels a purchase and transfers the amount to the canceled purchase wallet.

Parameters:

  • paymentTokenAddress_ (address): Address of the payment token to transfer
  • amount_ (uint256): Amount to transfer to canceled purchase wallet

Requirements:

  • Caller must be automation admin
  • Amount must be greater than zero
  • Canceled purchase wallet must be set

Emits: PurchaseCanceled(authorityAddress, paymentTokenAddress_, canceledPurchaseWallet, amount_)


Query Functions

isPurchaseIdUsed(string purchaseId_) → bool

Checks if a purchase ID has been used.

Parameters:

  • purchaseId_ (string): The purchase ID to check

Returns:

  • bool: True if the purchase ID has been used, false otherwise

automationAdmin() → address

Returns the current automation admin address.

Returns:

  • address: The automation admin address

adminFeeWallet() → address

Returns the current admin fee wallet address.

Returns:

  • address: The admin fee wallet address

canceledPurchaseWallet() → address

Returns the current canceled purchase wallet address.

Returns:

  • address: The canceled purchase wallet address

originatorPaymentWallet() → address

Returns the current originator payment wallet address.

Returns:

  • address: The originator payment wallet address

interestPayment() → address

Returns the InterestPayment contract address.

Returns:

  • address: The InterestPayment contract address

tokenContract() → address

Returns the RestrictedLockupToken contract address.

Returns:

  • address: The RestrictedLockupToken contract address

paymentToken() → address

Returns the payment token (USDC) contract address.

Returns:

  • address: The payment token contract address

usedPurchaseIds(string purchaseId) → bool

Returns whether a specific purchase ID has been used.

Parameters:

  • purchaseId (string): The purchase ID to query

Returns:

  • bool: True if the purchase ID has been used

Inherited Functions

Ownable Functions

owner() → address

Returns the current contract owner.

Returns:

  • address: The owner address

transferOwnership(address newOwner)

Transfers ownership to a new address.

Parameters:

  • newOwner (address): New owner address

Requirements:

  • Caller must be current owner

Emits: OwnershipTransferred(previousOwner, newOwner)


renounceOwnership()

Renounces ownership, leaving the contract without an owner.

Requirements:

  • Caller must be current owner

Emits: OwnershipTransferred(owner, address(0))


ERC-2771 Functions

isTrustedForwarder(address forwarder) → bool

Checks if an address is a trusted forwarder for meta-transactions.

Parameters:

  • forwarder (address): Address to check

Returns:

  • bool: True if the address is a trusted forwarder

trustedForwarder() → address

Returns the trusted forwarder address.

Returns:

  • address: The trusted forwarder address

Events

PurchaseExecuted(string indexed purchaseId, address indexed payerAddress, address indexed tokenRecipientAddress, address authorityAddress, uint256 originatorPurchaseAmount, uint256 prefundedInterestAmount, uint256 adminFeeExcludingPrefundedInterestAmount, uint256 mintAmount, address originatorAddress)

Emitted when a purchase is successfully executed.

Parameters:

  • purchaseId (string, indexed): Unique purchase identifier
  • payerAddress (address, indexed): Address that paid for the purchase
  • tokenRecipientAddress (address, indexed): Address that received the tokens
  • authorityAddress (address): Address that executed the purchase (automation admin)
  • originatorPurchaseAmount (uint256): Amount paid to originator
  • prefundedInterestAmount (uint256): Amount used to prefund interest
  • adminFeeExcludingPrefundedInterestAmount (uint256): Admin fee amount
  • mintAmount (uint256): Number of tokens minted
  • originatorAddress (address): Originator address (same as payer)

PurchaseCanceled(address indexed authorityAddress, address indexed paymentTokenAddress, address indexed canceledPurchaseWallet, uint256 amount)

Emitted when a purchase is canceled.

Parameters:

  • authorityAddress (address, indexed): Address that canceled the purchase
  • paymentTokenAddress (address, indexed): Payment token address
  • canceledPurchaseWallet (address, indexed): Wallet that received the refund
  • amount (uint256): Amount refunded

Administrative Events

AutomationAdminUpdated(address indexed oldAdmin, address indexed newAdmin)

Emitted when the automation admin is updated.

Parameters:

  • oldAdmin (address, indexed): Previous automation admin address
  • newAdmin (address, indexed): New automation admin address

AdminFeeWalletUpdated(address indexed oldWallet, address indexed newWallet)

Emitted when the admin fee wallet is updated.

Parameters:

  • oldWallet (address, indexed): Previous admin fee wallet address
  • newWallet (address, indexed): New admin fee wallet address

CanceledPurchaseWalletUpdated(address indexed oldWallet, address indexed newWallet)

Emitted when the canceled purchase wallet is updated.

Parameters:

  • oldWallet (address, indexed): Previous canceled purchase wallet address
  • newWallet (address, indexed): New canceled purchase wallet address

OriginatorPaymentWalletUpdated(address indexed oldWallet, address indexed newWallet)

Emitted when the originator payment wallet is updated.

Parameters:

  • oldWallet (address, indexed): Previous originator payment wallet address
  • newWallet (address, indexed): New originator payment wallet address

Inherited Events

OwnershipTransferred(address indexed previousOwner, address indexed newOwner)

Emitted when ownership is transferred.

Parameters:

  • previousOwner (address, indexed): Previous owner address
  • newOwner (address, indexed): New owner address

Custom Errors

Configuration Errors

PurchaseContract_InvalidZeroAddress()

Thrown when a zero address is provided where a valid address is required.


PurchaseContract_InvalidPaymentToken()

Thrown when the payment token doesn't match the InterestPayment contract's payment token.


PurchaseContract_InvalidTokenContract()

Thrown when the token contract doesn't match the InterestPayment contract's restricted token.


PurchaseContract_InterestPaymentNotConfigured()

Thrown when trying to use interest functionality without an InterestPayment contract.


Access Control Errors

PurchaseContract_OnlyAutomationAdmin()

Thrown when a non-automation admin tries to execute restricted functions.


Validation Errors

PurchaseContract_PurchaseIdAlreadyUsed()

Thrown when attempting to use a purchase ID that has already been used.


PurchaseContract_PurchaseIdNotUsed()

Thrown when referencing a purchase ID that hasn't been used.


PurchaseContract_InvalidAmount()

Thrown when an invalid amount (zero or negative) is provided.


PurchaseContract_InvalidTotalAmount()

Thrown when the total amount doesn't equal the sum of component amounts.


PurchaseContract_RemainderNotZero()

Thrown when there's a remainder in amount calculations.


Compliance Errors

PurchaseContract_TokenRecipientAmlKycNotPassed()

Thrown when the token recipient hasn't passed AML/KYC validation.


PurchaseContract_PayerAmlKycNotPassed()

Thrown when the payer hasn't passed AML/KYC validation.


Configuration Errors (API)

PurchaseContract_AdminFeeWalletNotSet()

Thrown when the admin fee wallet address hasn't been configured.


PurchaseContract_CanceledPurchaseWalletNotSet()

Thrown when the canceled purchase wallet address hasn't been configured.


PurchaseContract_OriginatorPaymentWalletNotSet()

Thrown when the originator payment wallet address hasn't been configured.


Transfer Errors

PurchaseContract_TransferFailed()

Thrown when a token transfer operation fails.


PurchaseContract_DecimalPrecisionMismatch()

Thrown when there's a decimal precision mismatch in calculations.


Inherited Errors

OwnableInvalidOwner(address owner)

Thrown when an invalid owner address is provided.

Parameters:

  • owner (address): The invalid owner address

OwnableUnauthorizedAccount(address account)

Thrown when an unauthorized account tries to perform owner-only operations.

Parameters:

  • account (address): The unauthorized account address

ReentrancyGuardReentrantCall()

Thrown when a reentrant call is detected.


SafeERC20FailedOperation(address token)

Thrown when an ERC-20 operation fails.

Parameters:

  • token (address): The token contract address where the operation failed