# Smart Contract Integration

This guide is for developers who want to integrate MakaPay at the smart contract level. Learn how to receive on-chain callbacks when payments are processed, enabling fully automated and trustless payment flows.

***

## Overview

MakaPay V4 smart contracts provide:

1. **CREATE2 Deterministic Addresses**: Payment addresses are computed before deployment
2. **On-Chain Callbacks**: Your contract can receive notifications when payments arrive
3. **Frontrun Protection**: Fee amounts are baked into the address computation
4. **Cross-Chain Recovery**: Same address works on all EVM chains

```
┌─────────────────────────────────────────────────────────────────┐
│                     Payment Flow (V4)                           │
│                                                                 │
│  1. Compute Address ──→ 2. Customer Pays ──→ 3. Process Payment │
│                                                     │           │
│                                                     ▼           │
│                                           4. Callback to Your   │
│                                              Contract (optional)│
└─────────────────────────────────────────────────────────────────┘
```

***

## The `onPaymentReceived` Callback

The most powerful feature for developers is the **optional callback** that V4 contracts can trigger when a payment is processed. If your merchant address is a smart contract that implements the callback interface, it will be called automatically.

### Interface

```solidity
interface IPaymentReceiverV4 {
    /**
     * @notice Called when payment is processed and funds are sent to this contract
     * @param data Arbitrary callback data (set when payment is created)
     * @param token The token address that was received
     * @param amount Total amount that was in the payment wallet
     * @param fee Fee amount that was deducted (0 if no fee)
     */
    function onPaymentReceived(
        bytes calldata data,
        address token,
        uint256 amount,
        uint256 fee
    ) external;
}
```

### When Is It Called?

The callback is triggered when **all of these conditions are met**:

1. The payment is processed via `PaymentWalletFactoryV4.process()` or `processNoFee()`
2. The `merchant` address has contract code (is a smart contract)
3. The `callbackData` parameter is non-empty

### Callback Data

The `callbackData` is arbitrary bytes that you define when creating the payment. This allows you to pass context to your contract, such as:

* Order IDs
* User identifiers
* Product SKUs
* Subscription IDs
* Any custom data your application needs

***

## Real-World Example: Gas Tank Deposits

MakaPay uses its own V4 callback system for the Gas Tank. Here's how the GasTankV4 contract receives deposit notifications:

### How It Works

1. **Payment Created**: A deposit payment is created with `merchant = GasTank contract address`
2. **User Sends USDT**: User sends USDT to the computed payment address
3. **Payment Processed**: MakaPay calls `factory.process()` with callback data
4. **Callback Triggered**: GasTank's `onPaymentReceived()` is called
5. **Balance Updated**: User's Gas Tank balance is credited automatically

### GasTank Implementation

```solidity
contract GasTankV4 {
    mapping(string => uint256) public userBalances;

    /**
     * @notice Callback from PaymentWalletV4 when deposit is processed
     * @param data Encoded (orderId, userId)
     * @param token Token received (must be USDT)
     * @param amount Total amount deposited
     * @param fee Fee deducted (should be 0 for deposits)
     */
    function onPaymentReceived(
        bytes calldata data,
        address token,
        uint256 amount,
        uint256 fee
    ) external {
        // Decode the callback data
        (string memory orderId, string memory userId) = abi.decode(
            data,
            (string, string)
        );

        // Verify caller is a legitimate payment wallet
        address expectedWallet = paymentFactory.computeAddress(
            orderId,
            address(this),  // merchant = this contract
            token,
            address(0),     // feeRecipient
            0               // feeAmount
        );
        require(msg.sender == expectedWallet, "Untrusted caller");

        // Credit user's balance
        uint256 depositAmount = amount - fee;
        userBalances[userId] += depositAmount;

        emit Deposited(userId, depositAmount, orderId);
    }
}
```

### Key Security Pattern

Notice how the GasTank verifies the caller:

```solidity
address expectedWallet = paymentFactory.computeAddress(...);
require(msg.sender == expectedWallet, "Untrusted caller");
```

**This is critical.** Since anyone can call your `onPaymentReceived()` function, you must verify that the caller is a legitimate payment wallet. The CREATE2 computation ensures only the correct wallet can produce a matching address.

***

## Use Case Examples

### 1. Automated Subscription Service

```solidity
contract SubscriptionService is IPaymentReceiverV4 {
    struct Subscription {
        uint256 expiresAt;
        string tier;
    }

    mapping(address => Subscription) public subscriptions;

    function onPaymentReceived(
        bytes calldata data,
        address token,
        uint256 amount,
        uint256 fee
    ) external {
        // Verify caller (see security pattern above)

        // Decode subscription data
        (address user, string memory tier, uint256 duration) = abi.decode(
            data,
            (address, string, uint256)
        );

        // Extend subscription
        uint256 currentExpiry = subscriptions[user].expiresAt;
        if (currentExpiry < block.timestamp) {
            currentExpiry = block.timestamp;
        }

        subscriptions[user] = Subscription({
            expiresAt: currentExpiry + duration,
            tier: tier
        });

        emit SubscriptionExtended(user, tier, duration);
    }
}
```

### 2. NFT Minting on Payment

```solidity
contract NFTSale is IPaymentReceiverV4, ERC721 {
    uint256 public nextTokenId;
    uint256 public price;

    function onPaymentReceived(
        bytes calldata data,
        address token,
        uint256 amount,
        uint256 fee
    ) external {
        // Verify caller

        // Decode recipient
        address recipient = abi.decode(data, (address));

        // Verify payment amount
        require(amount - fee >= price, "Insufficient payment");

        // Mint NFT
        _mint(recipient, nextTokenId++);
    }
}
```

### 3. Escrow Release

```solidity
contract Escrow is IPaymentReceiverV4 {
    struct Deal {
        address buyer;
        address seller;
        uint256 amount;
        bool funded;
    }

    mapping(bytes32 => Deal) public deals;

    function onPaymentReceived(
        bytes calldata data,
        address token,
        uint256 amount,
        uint256 fee
    ) external {
        // Verify caller

        bytes32 dealId = abi.decode(data, (bytes32));
        Deal storage deal = deals[dealId];

        require(!deal.funded, "Already funded");
        require(amount - fee >= deal.amount, "Insufficient");

        deal.funded = true;
        emit DealFunded(dealId, amount - fee);
    }

    function releaseFunds(bytes32 dealId) external {
        // Release logic...
    }
}
```

### 4. DAO Treasury Deposits

```solidity
contract DAOTreasury is IPaymentReceiverV4 {
    mapping(address => uint256) public contributions;
    uint256 public totalContributions;

    function onPaymentReceived(
        bytes calldata data,
        address token,
        uint256 amount,
        uint256 fee
    ) external {
        // Verify caller

        address contributor = abi.decode(data, (address));
        uint256 netAmount = amount - fee;

        contributions[contributor] += netAmount;
        totalContributions += netAmount;

        emit ContributionReceived(contributor, netAmount);
    }
}
```

***

## CREATE2 Deterministic Addresses

### How Address Computation Works

V4 uses CREATE2 to compute payment addresses before any contract is deployed:

```solidity
// Salt includes ALL payment parameters
bytes32 salt = keccak256(abi.encodePacked(
    orderId,
    merchant,
    token,
    feeRecipient,
    feeAmount
));

// Address computed using CREATE2 formula
address wallet = address(uint160(uint256(keccak256(abi.encodePacked(
    bytes1(0xff),
    factoryAddress,
    salt,
    proxyBytecodeHash
)))));
```

### Why This Matters

1. **Pre-Computed**: Address is known before deployment
2. **Deterministic**: Same inputs always produce same address
3. **Frontrun Resistant**: Fee amount is part of the salt (V4 upgrade)
4. **Cross-Chain**: Same address on all EVM chains

### Computing Addresses Off-Chain

```javascript
import { ethers } from 'ethers';

async function computePaymentAddress(factory, params) {
    const { orderId, merchant, token, feeRecipient, feeAmount } = params;

    return await factory.computeAddress(
        orderId,
        merchant,
        token,
        feeRecipient,
        feeAmount
    );
}

// Example usage
const address = await computePaymentAddress(factory, {
    orderId: "order_12345",
    merchant: "0xYourContract...",
    token: USDT_ADDRESS,
    feeRecipient: FEE_COLLECTOR,
    feeAmount: ethers.parseUnits("1.13", 6)  // 6 decimals for USDT
});

console.log("Send payment to:", address);
```

***

## V4 Contract Reference

### PaymentWalletFactoryV4

The main entry point for all payment operations.

#### `computeAddress()`

```solidity
function computeAddress(
    string calldata orderId,
    address merchant,
    address token,
    address feeRecipient,
    uint256 feeAmount
) external view returns (address)
```

Pre-computes the payment wallet address.

#### `process()`

```solidity
function process(
    string calldata orderId,
    address merchant,
    address token,
    address feeRecipient,
    uint256 feeAmount,
    bytes calldata callbackData
) external returns (address wallet, uint256 amount)
```

Processes a payment with the committed fee. Use for normal payments.

**Parameters:**

* `callbackData`: If non-empty and merchant is a contract, triggers `onPaymentReceived()`

#### `processNoFee()`

```solidity
function processNoFee(
    string calldata orderId,
    address merchant,
    address token,
    address tokenToWithdraw,
    address feeRecipient,
    uint256 feeAmount,
    bytes calldata callbackData
) external returns (address wallet, uint256 amount)
```

Processes without taking fee. Use for:

* Underpayments
* Wrong token recovery
* Gas Tank scenarios

#### `getWalletStatus()`

```solidity
function getWalletStatus(
    string calldata orderId,
    address merchant,
    address token,
    address feeRecipient,
    uint256 feeAmount,
    address tokenToCheck
) external view returns (
    address wallet,
    bool exists,
    uint256 balance
)
```

Check if a wallet exists and its balance.

***

### PaymentWalletV4

Minimal proxy wallet that receives and forwards payments.

#### `execute()`

```solidity
function execute(
    address token,
    address to,
    uint256 fee,
    address feeRecipient,
    bytes calldata callbackData
) external returns (uint256 amount)
```

Called by factory only. Transfers funds and optionally triggers callback.

***

## Security Considerations

### 1. Always Verify the Caller

Your `onPaymentReceived()` can be called by anyone. Always verify:

```solidity
function onPaymentReceived(...) external {
    // Compute what the wallet address SHOULD be
    address expectedWallet = factory.computeAddress(
        orderId,
        address(this),
        token,
        feeRecipient,
        feeAmount
    );

    // Verify caller matches
    require(msg.sender == expectedWallet, "Untrusted caller");

    // Now safe to process...
}
```

### 2. Handle Reentrancy

Use `nonReentrant` modifier if your callback updates state:

```solidity
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract MyContract is ReentrancyGuard, IPaymentReceiverV4 {
    function onPaymentReceived(...) external nonReentrant {
        // Safe from reentrancy
    }
}
```

### 3. Validate Token Address

Don't blindly trust the token parameter:

```solidity
function onPaymentReceived(
    bytes calldata data,
    address token,
    uint256 amount,
    uint256 fee
) external {
    require(token == ACCEPTED_TOKEN, "Wrong token");
    // ...
}
```

### 4. Callback Failures Revert Everything

If your callback reverts, the entire payment transaction reverts. Make sure your callback logic is robust:

```solidity
// BAD: Could revert and block payment
function onPaymentReceived(...) external {
    require(someUnreliableCondition, "Failed");  // Risky!
}

// GOOD: Graceful handling
function onPaymentReceived(...) external {
    // Store for later processing if complex logic needed
    pendingPayments[orderId] = PaymentData({...});
    emit PaymentQueued(orderId);
}
```

***

## Integration Checklist

Before going live with smart contract integration:

* [ ] Implement `IPaymentReceiverV4` interface
* [ ] Add caller verification using `computeAddress()`
* [ ] Add reentrancy protection
* [ ] Validate token addresses
* [ ] Handle callback data decoding errors gracefully
* [ ] Test with small amounts first
* [ ] Test failure scenarios (insufficient payment, wrong token)
* [ ] Audit your contract

***

## Deployed Contract Addresses

### MakaChain Mainnet (Chain ID: 777178)

| Contract                       | Address                                      |
| ------------------------------ | -------------------------------------------- |
| PaymentWalletFactoryV4         | `0xCB45Df5057b9a287F9b95Af7ccdC49A1C9F2fcfA` |
| PaymentWalletV4 Implementation | `0x0e77A0A5845C49EbC8EEA521024b5a04F7B0E20B` |
| GasTankV4                      | `0xE94EB77a80BddD3a1c1813ed17894af0a3837d2C` |
| USDT                           | `0x30C28E393E0732335235685B345c95E6465Ad8A5` |

### Other Chains

Factory and implementation addresses are **identical** across all supported chains (Ethereum, Polygon, BSC) due to CREATE2 deployment.

***

## Need Help?

* **Technical Support**: <dev@makapay.io>
* **Documentation**: <https://docs.makapay.io>
* **GitHub**: <https://github.com/makapay>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.makapay.io/developers/smart-contract-integration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
