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:
- Receive notification from JPYDWrapper
- Calculate 1% (or 0.5%)
- Send JPYD to XPointMint and award XPT points to user
- Transfer remaining 99% (or 99.5%) to shop
- 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:
- Connect with MetaMask
- Enter amount
- Approve signature
- 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:
- Immediacy: Points awarded simultaneously with payment
- Transparency: All processing verifiable on blockchain
- Low Cost: No centralized system required
- 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
- GitHub Repository: https://github.com/kkuejo/japoint-payment
- Mobile Payment Demo: https://kkuejo.github.io/japoint-payment/
- Sepolia Deployment: DEPLOYMENT.md
- Setup Guide: SETUP_INSTRUCTIONS.md
- Usage Guide: USAGE_GUIDE.md
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:
- LinkedIn: https://www.linkedin.com/in/kenichi-uejo-b450a76b/?locale=ja_JP
- Facebook: https://www.facebook.com/kenichi.uejo/?locale=ja_JP
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