📝

ステーブルコイン決済による自動ポイント付与システム

に公開

ステーブルコイン決済で自動ポイント付与 - Web3版クレジットカードシステムの実現

はじめに

クレジットカード決済では、利用者がカードで支払うと自動的にポイントが貯まります。この仕組みは、事業者が手数料を負担することで成立しており、利用者の購買意欲を高める効果的なマーケティング手法として広く使われています。

本記事では、この「事業者負担によるポイント付与」の仕組みをブロックチェーン上で実現したXPOINTシステムについて解説します。JPYCなどのステーブルコインを使った決済で、自動的にポイントトークンがウォレットに付与される仕組みを、スマートコントラクトで実装しました。

システムの概要

従来のクレジットカードとの比較

項目 クレジットカード XPOINT
決済通貨 法定通貨 ステーブルコイン(JPYD/JPYC)
ポイント管理 中央集権的なデータベース ブロックチェーン(ERC20トークン)
手数料負担者 加盟店 加盟店(ショップ)
ポイント付与 後日付与が多い 即座に付与
透明性 不透明 完全に透明(トランザクション履歴で確認可能)
決済速度 数秒 5-60秒(ネットワークによる)

アーキテクチャ

JAPOINTシステムは、以下のスマートコントラクトで構成されています:

┌─────────────┐
│   利用者    │
└──────┬──────┘
       │ 1. JPYD支払い
       ▼
┌──────────────┐
│ JPYDWrapper  │ ← 標準ERC20に通知機能を追加
└──────┬───────┘
       │ 2. 自動通知
       ▼
┌──────────────┐
│  Transfer10  │ ← 支払いを自動分配
│  Transfer5   │
└──┬────┬───┬──┘
   │    │   │
   │    │   └─→ 3. XPTポイント付与(1% or 0.5%)
   │    └─────→ 4. 会社手数料(1% or 0.5%)
   └──────────→ 5. ショップ売上(99% or 99.5%)

コントラクト間の関係性

コントラクト構成図

XPOINTシステムは、6つのスマートコントラクトで構成されています:

┌──────────────────────────────────────────────────────────────┐
│                      XPOINTエコシステム                       │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────┐                    ┌──────────────┐         │
│  │    JPYD     │◄───────────────────┤ JPYDWrapper  │         │
│  │  (ERC20)    │  transferFrom()    │              │         │
│  └──────┬──────┘                    └──────┬───────┘         │
│         │                                  │                 │
│         │ transfer()                       │ onTokenReceived │
│         │                                  │ (ITokenReceiver)│
│         ▼                                  ▼                 │
│  ┌──────────────┐                   ┌──────────────┐         │
│  │ ShopAddress  │◄──────────────────┤  Transfer10  │         │
│  │ (99%受取)    │  transfer()       │  Transfer5   │         │
│  └──────────────┘                   └──────┬───────┘         │
│                                            │                 │
│                                            │ transferXPoint  │
│                                            ▼                 │
│  ┌──────────────┐                   ┌──────────────┐         │
│  │  XPoint      │◄──────────────────┤  XPointMint  │         │
│  │  (ERC20)     │  mint()           │              │         │
│  └──────┬───────┘                   └──────────────┘         │
│         │                                                    │
│         │ transfer() [自動付与]                               │
│         ▼                                                    │ 
│  ┌──────────────┐                                            │
│  │    User      │                                            │
│  │  (利用者)    │                                             │
│  └──────────────┘                                            │
│                                                              │
└──────────────────────────────────────────────────────────────┘

各コントラクトの役割と責任

コントラクト 役割 主要機能 依存先
JPYD 決済通貨(ステーブルコイン) - ERC20標準機能
- EIP-2612 Permit
- Mint(オーナーのみ)
なし
JPYDWrapper 通知機能の追加 - JPYD転送の仲介
- 受信者への通知
- 失敗時のrevert
JPYD
Transfer10 決済分配(1%手数料) - 決済の受付
- 1%/99%分配
- ポイント付与依頼
JPYD, XPointMint
Transfer5 決済分配(0.5%手数料) - 決済の受付
- 0.5%/99.5%分配
- ポイント付与依頼
JPYD, XPointMint
XPointMint ポイント発行管理 - JPYD受取
- XPT Mint
- 会社への手数料送付
JPYD, XPoint
XPoint ポイントトークン - ERC20標準機能
- Mint(認可制)
なし

データフローの詳細

決済トランザクションの流れ

[ステップ1] 事前準備(Approve)
User → JPYD.approve(JPYDWrapper, amount)
└─→ JPYDWrapperにamount分の使用許可

[ステップ2] 決済実行
User → JPYDWrapper.transfer(Transfer10, amount)
  ├─→ JPYD.transferFrom(User, Transfer10, amount)
  │   └─→ UserのJPYDがTransfer10に移動
  └─→ Transfer10.onTokenReceived(User, amount)

[ステップ3] 自動分配(Transfer10内部)
Transfer10._processPayment(amount, User)
  ├─→ 手数料計算: fee = amount * 1% = amount / 100
  │
  ├─→ [XPT付与フロー]
  │   ├─→ JPYD.approve(XPointMint, fee)
  │   └─→ XPointMint.transferXPoint(User)
  │       ├─→ JPYD.transferFrom(Transfer10, XPointMint, fee)
  │       ├─→ XPoint.mint(User, fee)  // 1JPYD → 1XPT
  │       └─→ JPYD.transfer(CompanyAddress, fee)
  │
  └─→ [ショップ送金フロー]
      └─→ JPYD.transfer(ShopAddress, amount - fee)

[完了]
✓ User: -amount JPYD, +fee XPT
✓ Shop: +99% JPYD
✓ Company: +1% JPYD(手数料)

イベントフロー

各コントラクトは以下のイベントを発行し、トランザクションの追跡が可能です:

// 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
);

インターフェース設計

ITokenReceiver インターフェース

Transfer10/5はITokenReceiverインターフェースを実装しています:

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

このパターンは、ERC721のonERC721ReceivedやERC1155のonERC1155Receivedと同様の設計で、トークン受信時の自動処理を実現しています。

技術スタック

スマートコントラクト層

技術 バージョン 用途
Solidity 0.8.19 スマートコントラクト言語(EIP-3855非対応チェーン向け)
OpenZeppelin Contracts 5.5.0 - ERC20実装
- Ownable(所有者管理)
- ERC20Permit(EIP-2612)
Foundry latest - コントラクト開発
- テスト
- デプロイ

Solidityバージョンの選択理由:

  • Solidity 0.8.20以降はEIP-3855(PUSH0命令)を使用
  • Polygon等の一部チェーンではEIP-3855が未サポート
  • 0.8.19を使用することで、幅広いEVMチェーンに対応

フロントエンド層

技術 バージョン 用途
ethers.js 6.x - ウォレット接続
- コントラクト操作
- トランザクション送信
MetaMask SDK - ウォレット接続(Web/Mobile)
HTML/JavaScript - シンプルなフロントエンド実装
QRCode.js - QRコード生成(決済URL共有用)

開発ツール層

ツール 用途
Foundry (forge) - スマートコントラクトのビルド
- ユニットテスト実行
- ガス最適化分析
Foundry (cast) - ブロックチェーンとの対話
- コントラクト呼び出し
- イベント確認
Git/GitHub バージョン管理、GitHub Pagesホスティング

ブロックチェーンインフラ

レイヤー 使用技術 詳細
メインネット(想定) Ethereum Mainnet - 最も分散化された環境
- 高いセキュリティ
- 高いガス代($5-20/tx)
L2ソリューション(想定) Polygon PoS - 低ガス代(~$0.01/tx)
- 高速(2秒ブロックタイム)
- Ethereum互換
テストネット(現在) Sepolia - 無料テストETH
- 12秒ブロックタイム
- 本番と同じ環境
RPC プロバイダー PublicNode - 無料
- APIキー不要
- 高可用性

開発環境

# プロジェクト構成
XPOINT/
├── src/                    # スマートコントラクト
│   ├── JPYD.sol
│   ├── XPoint.sol
│   ├── XPointMint.sol
│   ├── JPYDWrapper.sol
│   ├── Transfer10.sol
│   ├── Transfer5.sol
│   └── ITokenReceiver.sol
├── test/                   # テストコード
├── script/                 # デプロイスクリプト
│   └── DeployFullSystem.s.sol
├── frontend/               # フロントエンド
│   ├── mobile-payment.html
│   ├── index.html
│   └── qr-codes-display.html
└── foundry.toml           # Foundry設定

テスト戦略

// Foundryによる包括的なテスト
contract Transfer10Test is Test {
    function testAutomaticProcessing() public {
        // 自動分配のテスト
    }

    function testDirectTransferProcessing() public {
        // 直接転送+手動処理のテスト
    }

    function testRevertOnInvalidAmount() public {
        // エラーケースのテスト
    }
}

実行:

forge test -vv                          # 全テスト実行
forge test --match-test testAutomatic   # 特定テスト実行
forge test --gas-report                 # ガスレポート

技術的な実装

1. JPYD - ステーブルコイン

JPYDは、JPYC(日本円ステーブルコイン)と互換性のあるERC20トークンです。標準的なERC20実装に加えて、EIP-2612のPermit機能をサポートしており、ガスレスでのApprove操作が可能です。

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);
    }
}

ポイント

  • JPYC等の既存ステーブルコインでも利用可能
  • 標準ERC20なので、あらゆるウォレットで保管・送金可能

2. JPYDWrapper - 通知機能の追加

標準のERC20トークンは、transfer()実行時に受信者に通知する機能がありません。JPYDWrapperは、この問題を解決するラッパーコントラクトです。

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して、トークンが宙に浮くのを防ぐ
        require(success, string(abi.encodePacked(
            "Recipient contract call failed: ",
            _getRevertMsg(returnData)
        )));
    }
}

重要な設計判断

  • 通知が失敗した場合はトランザクション全体をrevert
  • これにより、トークンが受信者のコントラクトに届いたのに処理されない状態を防止
  • セキュリティと信頼性を確保

3. Transfer10/Transfer5 - 自動分配ロジック

決済を受け取り、自動的に分配するコアコントラクトです。Transfer10は1%、Transfer5は0.5%の手数料率です。

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% 手数料計算
        uint256 xpointMintAmount = amount / 100;      // 1% → XPT付与用
        uint256 shopAmount = amount - xpointMintAmount; // 99% → ショップへ

        // 1. XPointMintにApprove & ポイント付与を依頼
        require(jpydToken.approve(address(xpointMint), xpointMintAmount), "Approval failed");
        xpointMint.transferXPoint(sender);

        // 2. ショップに売上を送金
        require(jpydToken.transfer(shopAddress, shopAmount), "Transfer to shop failed");

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

処理フロー

  1. JPYDWrapperから通知を受信
  2. 1%(または0.5%)を計算
  3. XPointMintにJPYDを送ってXPTポイントを利用者に付与
  4. 残り99%(または99.5%)をショップに送金
  5. すべてが1トランザクションで完了

4. XPoint - ポイントトークン

付与されるポイント自体もERC20トークンとして実装されています。

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);
    }
}

特徴

  • ポイントもウォレットで管理可能
  • 譲渡可能(将来的にポイント取引市場も可能)
  • 透明性が高い(残高がブロックチェーン上で確認可能)

利用方法

モバイル決済(最も簡単)

MetaMask Mobileで以下のURLを開くだけで決済可能:

https://kkuejo.github.io/japoint-payment/mobile-payment.html
  ?shop=テストショップ(1%)
  &type=transfer10
  &jpyd=0xdD870D138DC6081E664c5127226e815cc4C6f87D
  &wrapper=0xa30042F978913cE9B466e204E7F729AeBCb3c624
  &target=0xA3963E928B35Ac06cC519b2a1BbBc3F27aCf0460
  &japt=0x2eDf302548B23e9F599e483aE79cda6D8774c6fC

手順

  1. MetaMaskで接続
  2. 金額を入力
  3. 署名を承認
  4. 即座にXPTポイントがウォレットに付与される

技術的な仕組み(開発者向け)

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

// 2. JPYDWrapperを通してTransfer10に送金
const wrapper = new ethers.Contract(wrapperAddress, wrapperABI, signer);
const transferTx = await wrapper.transfer(transfer10Address, amount);
await transferTx.wait();

// 完了!ユーザーのウォレットにXPTが自動付与されている

システムの利点

1. 完全な透明性

すべてのトランザクションがブロックチェーン上に記録されるため:

  • 手数料率が明確(コントラクトコードで確認可能)
  • ポイント付与が確実(トランザクションで証明)
  • 不正が困難(改ざん不可能)

2. 即時性

従来のクレジットカードでは後日ポイント付与が一般的ですが、XPOINTでは:

  • 決済と同時にポイント付与(1トランザクションで完結)
  • 待ち時間なし(ブロック承認のみ、5-60秒)

3. 低コスト

Ethereum Sepoliaテストネット(将来的にPolygonなど)を使用することで:

  • ガス代が低い(Polygonなら1トランザクション$0.01以下)
  • システム運用コストが低い(サーバー不要)

4. プログラマビリティ

スマートコントラクトなので:

  • 手数料率の変更が容易(Transfer5で0.5%、Transfer10で1%など)
  • 複雑なロイヤリティプログラムも実装可能
  • 他のDeFiプロトコルとの連携も可能

実際のデプロイメント

現在、Ethereum Sepoliaテストネットにデプロイ済みです:

コントラクト アドレス Etherscan
JPYD 0xdD870D138DC6081E664c5127226e815cc4C6f87D 確認
XPoint (XPT) 0x2eDf302548B23e9F599e483aE79cda6D8774c6fC 確認
JPYDWrapper 0xa30042F978913cE9B466e204E7F729AeBCb3c624 確認
Transfer10 (1%) 0xA3963E928B35Ac06cC519b2a1BbBc3F27aCf0460 確認
Transfer5 (0.5%) 0x74F6CfD89751a677E74130752483a530e27D4819 確認

セキュリティ考慮事項

1. 通知失敗時のリバート

JPYDWrapperの重要な設計として、onTokenReceivedの呼び出しが失敗した場合、トランザクション全体をrevertします。これにより:

  • トークンが受信者に届いたのに処理されない状態を防止
  • 第三者によるポイントの横取りを防止

2. 権限管理

  • XPointのmint権限は、XPointMintコントラクトのみ
  • Transfer10/5のショップアドレスと会社アドレスは固定
  • 不正なポイント発行を防止

3. 監査可能性

すべてのコードはオープンソースで、Etherscan上で検証済み:

  • 誰でもコードを確認可能
  • 手数料率が正しいか確認可能
  • 不正な処理がないか確認可能

今後の展開

1. Polygon Mainnetへの移行

より低コストで高速な決済のため、Polygon PoSチェーンへの展開を検討中:

  • トランザクション速度: 2秒(Sepoliaの12秒から高速化)
  • ガス代: ~$0.01/tx(Ethereumメインネットの$5-20/txから大幅削減)

2. JPYC公式トークンとの連携

現在はJPYD(テスト用)を使用していますが、JPYC公式ステーブルコインでの利用を想定:

  • 実際の日本円と1:1でペッグされたステーブルコイン
  • 実店舗での決済にも対応可能

3. より柔軟なポイントプログラム

  • ティア制度: 購入金額に応じてポイント還元率を変動
  • 時間限定キャンペーン: 特定期間のみ高還元率
  • カテゴリー別還元: 商品カテゴリーごとに異なる還元率

結論

XPOINTシステムは、従来のクレジットカードポイントプログラムをブロックチェーン上で実現し、さらに以下の点で改善しました:

  1. 即時性: 決済と同時にポイント付与
  2. 透明性: すべての処理がブロックチェーン上で確認可能
  3. 低コスト: 中央集権的なシステム不要
  4. 柔軟性: プログラマブルなポイントプログラム

ステーブルコインとスマートコントラクトを組み合わせることで、事業者負担によるポイント付与という既存の商習慣を、より透明で効率的な形で実現できることを示しました。

Web3時代の新しい決済・ポイントシステムとして、今後の発展が期待されます。


参考リンク

ライセンス

研究・学術用途

研究や学術目的での使用は自由に行っていただけます。

商用利用

商用で利用する場合は、以下のいずれかの方法で許可を取得してください:


記事情報

執筆日: 2025年11月22日
著者: DeIn-Kenichi
対象読者: ブロックチェーン開発者、Web3サービス企画者、FinTech関係者

主要技術スタック

スマートコントラクト:

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

フロントエンド:

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

インフラ:

  • Ethereum Sepolia Testnet
  • PublicNode RPC
  • GitHub Pages

開発手法:

  • テスト駆動開発(TDD)
  • ガス最適化
  • セキュリティ重視設計

Discussion