🐷

Automatic Point Rewards with Stablecoin Payments

に公開

Automatic Point Rewards with Stablecoin Payments - Realizing a Web3 Credit Card System

Introduction

With credit card payments, users automatically accumulate points when they make purchases. This mechanism, funded by merchant fees, serves as an effective marketing tool to boost consumer purchasing behavior and is widely adopted.

This article explains the XPOINT system, which implements this "merchant-funded point reward" mechanism on the blockchain. We've created a system where point tokens are automatically deposited into wallets when payments are made using stablecoins like JPYC, all implemented through smart contracts.

System Overview

Comparison with Traditional Credit Cards

Feature Credit Cards XPOINT
Payment Currency Fiat Currency Stablecoin (JPYD/JPYC)
Point Management Centralized Database Blockchain (ERC20 Token)
Fee Bearer Merchant Merchant (Shop)
Point Distribution Often Delayed Instant
Transparency Opaque Fully Transparent (Verifiable via transaction history)
Transaction Speed Seconds 5-60 seconds (Network dependent)

Architecture

The XPOINT system consists of the following smart contracts:

┌─────────────┐
│    User     │
└──────┬──────┘
       │ 1. JPYD Payment
       ▼
┌──────────────┐
│ JPYDWrapper  │ ← Adds notification to standard ERC20
└──────┬───────┘
       │ 2. Automatic Notification
       ▼
┌──────────────┐
│  Transfer10  │ ← Automatic payment distribution
│  Transfer5   │
└──┬────┬───┬──┘
   │    │   │
   │    │   └─→ 3. XPT Point Award (1% or 0.5%)
   │    └─────→ 4. Company Fee (1% or 0.5%)
   └──────────→ 5. Shop Revenue (99% or 99.5%)

Contract Relationships

Contract Architecture Diagram

The XPOINT system consists of 6 smart contracts:

┌──────────────────────────────────────────────────────────────┐
│                     XPOINT Ecosystem                         │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────┐                    ┌──────────────┐         │
│  │    JPYD     │◄───────────────────┤ JPYDWrapper  │         │
│  │  (ERC20)    │  transferFrom()    │              │         │
│  └──────┬──────┘                    └──────┬───────┘         │
│         │                                  │                 │
│         │ transfer()                       │ onTokenReceived │
│         │                                  │ (ITokenReceiver)│
│         ▼                                  ▼                 │
│  ┌──────────────┐                   ┌──────────────┐         │
│  │ ShopAddress  │◄──────────────────┤  Transfer10  │         │
│  │ (99% recv)   │  transfer()       │  Transfer5   │         │
│  └──────────────┘                   └──────┬───────┘         │
│                                            │                 │
│                                            │ transferXPoint  │
│                                            ▼                 │
│  ┌──────────────┐                   ┌──────────────┐         │
│  │  XPoint      │◄──────────────────┤ XPointMint   │         │
│  │  (ERC20)     │  mint()           │              │         │
│  └──────┬───────┘                   └──────────────┘         │
│         │                                                    │
│         │ transfer() [Auto-distributed]                      │
│         ▼                                                    │
│  ┌──────────────┐                                            │
│  │    User      │                                            │
│  │  (Customer)  │                                            │
│  └──────────────┘                                            │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Roles and Responsibilities of Each Contract

Contract Role Key Functions Dependencies
JPYD Payment Currency (Stablecoin) - ERC20 Standard
- EIP-2612 Permit
- Mint (Owner only)
None
JPYDWrapper Notification Addition - JPYD Transfer Intermediation
- Recipient Notification
- Revert on Failure
JPYD
Transfer10 Payment Distribution (1% fee) - Payment Reception
- 1%/99% Distribution
- Point Award Request
JPYD, XPointMint
Transfer5 Payment Distribution (0.5% fee) - Payment Reception
- 0.5%/99.5% Distribution
- Point Award Request
JPYD, XPointMint
XPointMint Point Issuance Management - JPYD Reception
- XPT Minting
- Fee Transfer to Company
JPYD, XPoint
XPoint Point Token - ERC20 Standard
- Mint (Authorized only)
None

Detailed Data Flow

Payment Transaction Flow

[Step 1] Preparation (Approve)
User → JPYD.approve(JPYDWrapper, amount)
└─→ Grant JPYDWrapper permission to spend amount

[Step 2] Payment Execution
User → JPYDWrapper.transfer(Transfer10, amount)
  ├─→ JPYD.transferFrom(User, Transfer10, amount)
  │   └─→ User's JPYD transferred to Transfer10
  └─→ Transfer10.onTokenReceived(User, amount)

[Step 3] Automatic Distribution (Inside Transfer10)
Transfer10._processPayment(amount, User)
  ├─→ Fee calculation: fee = amount * 1% = amount / 100
  │
  ├─→ [XPT Award Flow]
  │   ├─→ JPYD.approve(XPointMint, fee)
  │   └─→ XPointMint.transferXPoint(User)
  │       ├─→ JPYD.transferFrom(Transfer10, XPointMint, fee)
  │       ├─→ XPoint.mint(User, fee)  // 1 JPYD → 1 XPT
  │       └─→ JPYD.transfer(CompanyAddress, fee)
  │
  └─→ [Shop Transfer Flow]
      └─→ JPYD.transfer(ShopAddress, amount - fee)

[Complete]
✓ User: -amount JPYD, +fee XPT
✓ Shop: +99% JPYD
✓ Company: +1% JPYD (fee)

Event Flow

Each contract emits the following events for transaction tracking:

// JPYDWrapper
event NotificationAttempt(address indexed recipient, bool success);

// Transfer10/Transfer5
event PaymentProcessed(
    address indexed payer,
    uint256 amount,
    uint256 feeAmount,
    uint256 shopAmount
);

// XPointMint
event XPointTransferred(
    address indexed user,
    uint256 jpydAmount,
    uint256 xpointAmount
);

Interface Design

ITokenReceiver Interface

Transfer10/5 implements the ITokenReceiver interface:

interface ITokenReceiver {
    function onTokenReceived(
        address sender,
        uint256 amount
    ) external returns (bool);
}

This pattern is similar to ERC721's onERC721Received and ERC1155's onERC1155Received, enabling automatic processing upon token reception.

Technology Stack

Smart Contract Layer

Technology Version Purpose
Solidity 0.8.19 Smart contract language (for EIP-3855 non-compatible chains)
OpenZeppelin Contracts 5.5.0 - ERC20 Implementation
- Ownable (Owner Management)
- ERC20Permit (EIP-2612)
Foundry latest - Contract Development
- Testing
- Deployment

Solidity Version Selection Rationale:

  • Solidity 0.8.20+ uses EIP-3855 (PUSH0 opcode)
  • Some chains like Polygon don't support EIP-3855
  • Using 0.8.19 ensures compatibility with wider range of EVM chains

Frontend Layer

Technology Version Purpose
ethers.js 6.x - Wallet Connection
- Contract Interaction
- Transaction Submission
MetaMask SDK - Wallet Connection (Web/Mobile)
HTML/JavaScript - Simple Frontend Implementation
QRCode.js - QR Code Generation (for payment URL sharing)

Development Tools

Tool Purpose
Foundry (forge) - Smart Contract Build
- Unit Test Execution
- Gas Optimization Analysis
Foundry (cast) - Blockchain Interaction
- Contract Calls
- Event Verification
Git/GitHub Version Control, GitHub Pages Hosting

Blockchain Infrastructure

Layer Technology Details
Mainnet (Planned) Ethereum Mainnet - Most decentralized environment
- High security
- High gas fees ($5-20/tx)
L2 Solution (Planned) Polygon PoS - Low gas fees (~$0.01/tx)
- Fast (2 sec block time)
- Ethereum compatible
Testnet (Current) Sepolia - Free test ETH
- 12 sec block time
- Production-like environment
RPC Provider PublicNode - Free
- No API key required
- High availability

Development Environment

# Project Structure
XPOINT/
├── src/                    # Smart Contracts
│   ├── JPYD.sol
│   ├── XPoint.sol
│   ├── XPointMint.sol
│   ├── JPYDWrapper.sol
│   ├── Transfer10.sol
│   ├── Transfer5.sol
│   └── ITokenReceiver.sol
├── test/                   # Test Code
├── script/                 # Deployment Scripts
│   └── DeployFullSystem.s.sol
├── frontend/               # Frontend
│   ├── mobile-payment.html
│   ├── index.html
│   └── qr-codes-display.html
└── foundry.toml           # Foundry Configuration

Testing Strategy

// Comprehensive testing with Foundry
contract Transfer10Test is Test {
    function testAutomaticProcessing() public {
        // Test automatic distribution
    }

    function testDirectTransferProcessing() public {
        // Test direct transfer + manual processing
    }

    function testRevertOnInvalidAmount() public {
        // Test error cases
    }
}

Execution:

forge test -vv                          # Run all tests
forge test --match-test testAutomatic   # Run specific test
forge test --gas-report                 # Gas report

Technical Implementation

1. JPYD - Stablecoin

JPYD is an ERC20 token compatible with JPYC (Japanese Yen Stablecoin). In addition to standard ERC20 implementation, it supports EIP-2612 Permit functionality, enabling gasless Approve operations.

contract JPYD is ERC20, ERC20Permit, Ownable {
    constructor() ERC20("JPY Digital", "JPYD") ERC20Permit("JPY Digital") Ownable(msg.sender) {}

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
}

Key Points:

  • Compatible with existing stablecoins like JPYC
  • Standard ERC20, storable and transferable in any wallet

2. JPYDWrapper - Notification Functionality

Standard ERC20 tokens lack notification functionality when transfer() is executed. JPYDWrapper is a wrapper contract that solves this problem.

contract JPYDWrapper {
    IERC20 public jpydToken;

    function transfer(address to, uint256 amount) external returns (bool) {
        require(jpydToken.transferFrom(msg.sender, to, amount), "Transfer failed");
        _notifyTokenReceived(msg.sender, to, amount);
        return true;
    }

    function _notifyTokenReceived(address from, address to, uint256 amount) internal {
        bytes memory data = abi.encodeWithSelector(
            ITokenReceiver.onTokenReceived.selector,
            from,
            amount
        );
        (bool success, bytes memory returnData) = to.call(data);

        // Revert if notification fails to prevent tokens from being stuck
        require(success, string(abi.encodePacked(
            "Recipient contract call failed: ",
            _getRevertMsg(returnData)
        )));
    }
}

Critical Design Decision:

  • Revert entire transaction if notification fails
  • Prevents tokens from being delivered but unprocessed
  • Ensures security and reliability

3. Transfer10/Transfer5 - Automatic Distribution Logic

Core contract that receives payments and distributes automatically. Transfer10 has 1% fee, Transfer5 has 0.5% fee.

contract Transfer10 is ITokenReceiver {
    IERC20 public jpydToken;
    XPointMint public xpointMint;
    address public companyAddress;
    address public shopAddress;

    function onTokenReceived(address sender, uint256 amount) external override returns (bool) {
        require(msg.sender == address(jpydWrapper), "Only JPYDWrapper can call");
        _processPayment(amount, sender);
        return true;
    }

    function _processPayment(uint256 amount, address sender) internal {
        // 1% fee calculation
        uint256 xpointMintAmount = amount / 100;      // 1% → for XPT award
        uint256 shopAmount = amount - xpointMintAmount; // 99% → to shop

        // 1. Approve XPointMint & request point award
        require(jpydToken.approve(address(xpointMint), xpointMintAmount), "Approval failed");
        xpointMint.transferXPoint(sender);

        // 2. Transfer revenue to shop
        require(jpydToken.transfer(shopAddress, shopAmount), "Transfer to shop failed");

        emit PaymentProcessed(sender, amount, xpointMintAmount, shopAmount);
    }
}

Processing Flow:

  1. Receive notification from JPYDWrapper
  2. Calculate 1% (or 0.5%)
  3. Send JPYD to XPointMint and award XPT points to user
  4. Transfer remaining 99% (or 99.5%) to shop
  5. Everything completes in 1 transaction

4. XPoint - Point Token

The awarded points are also implemented as ERC20 tokens.

contract XPoint is ERC20, Ownable {
    mapping(address => bool) public minters;

    modifier onlyMinter() {
        require(minters[msg.sender], "Not authorized minter");
        _;
    }

    function mint(address to, uint256 amount) external onlyMinter {
        _mint(to, amount);
    }
}

Features:

  • Points manageable in wallet
  • Transferable (future point trading market possible)
  • High transparency (balance verifiable on blockchain)

Usage

Mobile Payment (Easiest)

Simply open the following URL in MetaMask Mobile:

https://kkuejo.github.io/japoint-payment/mobile-payment.html
  ?shop=Test Shop (1%)
  &type=transfer10
  &jpyd=0xdD870D138DC6081E664c5127226e815cc4C6f87D
  &wrapper=0xa30042F978913cE9B466e204E7F729AeBCb3c624
  &target=0xA3963E928B35Ac06cC519b2a1BbBc3F27aCf0460
  &japt=0x2eDf302548B23e9F599e483aE79cda6D8774c6fC

Steps:

  1. Connect with MetaMask
  2. Enter amount
  3. Approve signature
  4. XPT points instantly deposited to wallet

Technical Mechanism (For Developers)

// 1. Approve JPYD to JPYDWrapper
const jpyd = new ethers.Contract(jpydAddress, jpydABI, signer);
const approvalTx = await jpyd.approve(wrapperAddress, amount);
await approvalTx.wait();

// 2. Transfer to Transfer10 via JPYDWrapper
const wrapper = new ethers.Contract(wrapperAddress, wrapperABI, signer);
const transferTx = await wrapper.transfer(transfer10Address, amount);
await transferTx.wait();

// Complete! XPT automatically awarded to user's wallet

System Advantages

1. Complete Transparency

All transactions recorded on blockchain:

  • Clear fee rates (verifiable in contract code)
  • Guaranteed point awards (proven by transactions)
  • Fraud resistant (immutable)

2. Immediacy

While traditional credit cards often delay point awards, XPOINT:

  • Points awarded simultaneously with payment (completed in 1 transaction)
  • No waiting (only block confirmation, 5-60 seconds)

3. Low Cost

Using Ethereum Sepolia testnet (future: Polygon):

  • Low gas fees (Polygon: under $0.01/tx)
  • Low system operating costs (no servers required)

4. Programmability

As smart contracts:

  • Easy fee rate changes (0.5% with Transfer5, 1% with Transfer10, etc.)
  • Complex loyalty programs implementable
  • Integration with other DeFi protocols possible

Actual Deployment

Currently deployed on Ethereum Sepolia Testnet:

Contract Address Etherscan
JPYD 0xdD870D138DC6081E664c5127226e815cc4C6f87D View
XPoint (XPT) 0x2eDf302548B23e9F599e483aE79cda6D8774c6fC View
JPYDWrapper 0xa30042F978913cE9B466e204E7F729AeBCb3c624 View
Transfer10 (1%) 0xA3963E928B35Ac06cC519b2a1BbBc3F27aCf0460 View
Transfer5 (0.5%) 0x74F6CfD89751a677E74130752483a530e27D4819 View

Security Considerations

1. Revert on Notification Failure

A critical design of JPYDWrapper is to revert the entire transaction if onTokenReceived call fails. This:

  • Prevents tokens from being delivered but unprocessed
  • Prevents point theft by third parties

2. Permission Management

  • XPoint mint permission limited to XPointMint contract only
  • Transfer10/5 shop address and company address are fixed
  • Prevents unauthorized point issuance

3. Auditability

All code is open source and verified on Etherscan:

  • Anyone can review code
  • Fee rates verifiable
  • No fraudulent processing verifiable

Future Development

1. Migration to Polygon Mainnet

For lower cost and faster payments, considering deployment to Polygon PoS chain:

  • Transaction Speed: 2 seconds (faster than Sepolia's 12 seconds)
  • Gas Fees: ~$0.01/tx (significant reduction from Ethereum mainnet's $5-20/tx)

2. Integration with Official JPYC Token

Currently using JPYD (test), but intended for use with official JPYC stablecoin:

  • Stablecoin pegged 1:1 with actual Japanese Yen
  • Applicable for physical store payments

3. More Flexible Point Programs

  • Tier System: Variable point return rates based on purchase amount
  • Limited-time Campaigns: High return rates during specific periods
  • Category-based Returns: Different return rates per product category

Conclusion

The XPOINT system realizes traditional credit card point programs on blockchain, with improvements in:

  1. Immediacy: Points awarded simultaneously with payment
  2. Transparency: All processing verifiable on blockchain
  3. Low Cost: No centralized system required
  4. Flexibility: Programmable point programs

By combining stablecoins and smart contracts, we demonstrated that merchant-funded point rewards, an existing business practice, can be realized in a more transparent and efficient manner.

As a new payment and point system for the Web3 era, future developments are highly anticipated.


References

License

Research and Academic Use

You are free to use this for research and academic purposes.

Commercial Use

For commercial use, please obtain permission through one of the following:


Article Information

Publication Date: November 22, 2025
Author: DeIn-Kenichi
Target Audience: Blockchain Developers, Web3 Service Planners, FinTech Professionals

Key Technology Stack

Smart Contracts:

  • Solidity 0.8.19
  • OpenZeppelin Contracts 5.5.0
  • Foundry (forge, cast)

Frontend:

  • ethers.js 6.x
  • MetaMask SDK
  • HTML/JavaScript/CSS

Infrastructure:

  • Ethereum Sepolia Testnet
  • PublicNode RPC
  • GitHub Pages

Development Methodology:

  • Test-Driven Development (TDD)
  • Gas Optimization
  • Security-focused Design

Discussion