2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JPYC で B2B 送金を実装する — 受取先スクリーニングからトラベルルールまで

2
Last updated at Posted at 2026-04-29

2025年8月、JPYC株式会社は資金移動業登録を受け、同年10月に国内初の資金移動業型円建てステーブルコイン「JPYC」 として稼働を開始しました。Ethereum・Polygon・Avalanche の 3 チェーンで発行され、1 JPYC = 1 円で相互交換できる「電子決済手段」として、B2B 送金・EC 決済・クリエイター経済 など多様なユースケースに使われ始めています。

この記事では、JPYC を B2B 送金のレールに使う サービスを設計する際に、AML・コンプライアンス観点で押さえるべき実装を、具体コードとともに整理します。前回予定していた記事とは異なります(細かすぎるため)。

TL;DR

  • JPYC は 電子決済手段(2023 年改正資金決済法)として規制対象
  • 発行体 JPYC 株式会社は Elliptic と提携して AML 体制を構築済み
  • 利用事業者側(JPYC で送金する B2B サービス)にも AML 責務あり
    • 資金決済法(事業スキームにより登録要否が変わる)
    • 犯罪による収益の移転防止に関する法律(犯収法)
    • FATF トラベルルール(VASP 向けは 日本は犯収法で ¥100,000 超; 当プラットフォームでは$1,000を閾値としています)で送金者・受取者情報共有
  • 実装の肝: 受取先スクリーニング → 閾値検知 → トラベルルール発火 → 証跡保全

1. JPYC のネットワーク基礎

発行チェーン

チェーン コントラクト タイプ 特徴
Ethereum ERC-20 王道、ガス代高、EVM ツール豊富
Polygon ERC-20 (L2) 低ガス代、国内決済向けに人気
Avalanche ERC-20 (C-Chain) 高速、対応エコシステムは限定

3 チェーン共通の特徴:

  • 1 JPYC = 1 JPY の固定レート
  • 発行体が 裏付資産(日本円預金・国債) で全額担保
  • 償還(JPYC → JPY 日本円) は発行体経由

典型的な B2B 送金フロー

2. 想定されるユースケース

JPYC を B2B 送金レールに使う代表的なパターン:

  1. クリエイター報酬支払い: デザイナー・翻訳者・開発者への報酬を JPYC で即日払い(海外在住含む)
  2. 中小企業間の仕入代金決済: 銀行振込より低コスト・24時間即時決済
  3. 国際送金の一部代替: 日本→海外の送金で JPYC → USDC → 現地 CEX 出金のルート
  4. EC 受取: 国内 EC 事業者が JPYC 決済を受け入れ
  5. サブスクリプション型 BtoB: SaaS 事業者が月次 JPYC 請求

それぞれ AML 要件の濃淡が違う。B2B 仕入代金は閾値超過が日常的、クリエイター報酬は少額多数、といった感じです。

3. 受取先スクリーニングの実装

B2B 送金で最もクリティカルなのが 受取先アドレスのリスク評価 です。

最小構成

from dataclasses import dataclass

@dataclass
class ScreeningResult:
    level: str  # CRITICAL / HIGH / MEDIUM / LOW
    reasons: list[str]
    can_proceed: bool

async def screen_receiver(
    receiver: str, chain: str, amount_jpy: int
) -> ScreeningResult:
    reasons: list[str] = []
    level = "LOW"

    # 1. OFAC SDN 即時照合
    if await is_ofac_sanctioned(receiver, chain):
        return ScreeningResult(
            level="CRITICAL",
            reasons=["OFAC SDN 一致"],
            can_proceed=False,
        )

    # 2. 国内捜査公開リスト照合(警察庁公開の凍結対象等)
    if await is_domestic_watchlist(receiver, chain):
        return ScreeningResult(
            level="CRITICAL",
            reasons=["国内凍結対象リスト一致"],
            can_proceed=False,
        )

    # 3. ScamDB / Known Entities 照合
    entity = await lookup_known_entity(receiver, chain)
    if entity:
        if entity.category in ("darknet_market", "sanctioned_entity"):
            return ScreeningResult(
                level="CRITICAL",
                reasons=[f"{entity.category}: {entity.label}"],
                can_proceed=False,
            )
        if entity.category in ("mixing", "scam", "fraud_shop"):
            reasons.append(f"{entity.category}: {entity.label}")
            level = "HIGH"

    # 4. Neo4j グラフ近接度(1-3 ホップ)
    proximity = await check_graph_proximity(receiver, chain)
    if proximity.has_risk_neighbor and proximity.min_hops <= 2:
        if level == "LOW":
            level = "MEDIUM"
        reasons.append(f"risky neighbor: {proximity.category} at {proximity.min_hops} hops")

    # 5. ML 異常スコア
    ml_score = await compute_ml_anomaly(receiver, chain)
    if ml_score > 0.85:
        level = "HIGH" if level != "CRITICAL" else level
        reasons.append(f"ML anomaly {ml_score:.2f}")

    return ScreeningResult(
        level=level,
        reasons=reasons,
        can_proceed=(level != "CRITICAL"),
    )

OFAC SDN のインメモリキャッシュ

受取先照合は 送金のたび に走るので高速が必須。

# 起動時にファイルから全件ロード
OFAC_CRYPTO_ADDRESSES: set[str] = set()

async def bootstrap_ofac():
    global OFAC_CRYPTO_ADDRESSES
    addresses = await download_ofac_sdn_crypto_list()
    OFAC_CRYPTO_ADDRESSES = {a.lower() for a in addresses}

async def is_ofac_sanctioned(address: str, chain: str) -> bool:
    return address.lower() in OFAC_CRYPTO_ADDRESSES

更新は 毎週水曜の OFAC 定例更新 に合わせてクロン実行。

4. 閾値検知とトラベルルール

JPYC の金額 → USD 換算

JPYC は JPY ペッグなので、USD 換算だけで済みます。

async def to_usd_jpyc(amount_jpyc: int) -> float:
    """amount_jpyc は 6 decimals (JPYC の decimals)"""
    jpy = amount_jpyc / (10 ** 6)  # JPYC = JPY 1:1
    jpy_to_usd = await get_fx_rate_jpy_to_usd()  # 例: 1/150.0
    return jpy * jpy_to_usd

FX レートは 10 分キャッシュで十分。

トラベルルール 発火

閾値を超えた送金は、IVMS 101 メッセージを生成して受取側 VASP へ 送ります。日本の事業者なら犯収法の ¥100,000 超 が一次基準(当プラットフォームでは実際には閾値として$1,000を採用)。

# 日本の犯収法に基づく一次閾値(円建て)
TRAVEL_RULE_THRESHOLD_JPY = 100_000

# FATF VASP 向け推奨閾値(海外 VASP 接続用、保守的に併用)
TRAVEL_RULE_THRESHOLD_USD = 1000.0

async def maybe_fire_travel_rule(
    tx: JpycTxRequest,
) -> None:
    jpy = tx.amount_jpyc / (10 ** 6)  # JPYC = JPY 1:1
    usd = await to_usd_jpyc(tx.amount_jpyc)
    if jpy <= TRAVEL_RULE_THRESHOLD_JPY and usd <= TRAVEL_RULE_THRESHOLD_USD:
        return  # 両閾値以下

    # 受取先が VASP か self-custody かを判定
    vasp = await lookup_vasp_wallet(tx.receiver, tx.chain)
    if vasp is None:
        # セルフカストディ → トラベルルール対象外だが記録のみ残す
        await log_self_custody_transfer(tx)
        return

    # VASP 間送金 → IVMS 101 メッセージ作成
    ivms = build_ivms_101(
        originator={
            "name": tx.sender.legal_name,
            "address": tx.sender.address,
            "birthDate": tx.sender.birthdate,
            "nationalIdentifier": tx.sender.kyc_id,
        },
        beneficiary={
            "name": vasp.beneficiary_info(tx.receiver).legal_name,
            "accountNumber": tx.receiver,  # = ウォレットアドレス
        },
        asset={"symbol": "JPYC", "chain": tx.chain},
        amount=tx.amount_jpyc,
    )

    # Notabene / Sumsub / TRP 等のプロトコル経由で送信
    await travel_rule_provider.send(ivms, receiver_vasp=vasp)

VASP 特定の精度が肝

受取先が取引所のホットウォレット か 個人ウォレット かを正確に判定できないと、トラベルルールの発火可否がブレる。

CREATE TABLE exchange_hot_wallets (
    chain TEXT NOT NULL,
    address TEXT NOT NULL,
    exchange_name TEXT NOT NULL,
    japan_fsa_status TEXT,  -- 'registered' / 'not_registered' / 'enforcement_action'
    travel_rule_protocol TEXT,  -- 'notabene' / 'sumsub' / 'trp' / null
    PRIMARY KEY (chain, address)
);

CREATE INDEX idx_exchange_wallets_lookup ON exchange_hot_wallets(chain, address);

主要国内外取引所のホット ウォレット は OSINT 統合で初期投入 + 定期更新:

  • 国内: bitFlyer, Coincheck, bitbank, GMO コイン, BITPOINT 等
  • 海外: Binance, Coinbase, Kraken, Bybit, OKX, Bitfinex 等

5. ストラクチャリング(小分け回避)対策

閾値回避のために ¥100,000 未満を分割送金 するパターンには、24-72 時間の合算 ベロシティ チェック を入れます。

-- 過去 24h で同一ユーザー→同一受取先の合算
SELECT SUM(amount_jpy) AS total_jpy, COUNT(*) AS tx_count
FROM transactions
WHERE sender_user_id = $1
  AND receiver_address = $2
  AND created_at > NOW() - INTERVAL '24 hours';
async def check_structuring(
    user_id: str, receiver: str, chain: str
) -> bool:
    """合算で ¥100,000 超えるなら True(= トラベルルール発火 + レビュー)"""
    total_jpy = await sum_transfers_24h(user_id, receiver, chain)
    return total_jpy > TRAVEL_RULE_THRESHOLD_JPY

さらに保守的にやるなら:

  • 同一送金者 → 複数受取先(関連アドレス)の合算
  • 同一受取先クラスタ への合算

6. 証跡保全(犯収法 7 年義務)

犯収法では 確認記録・取引記録の 7 年保存 が義務。JPYC 送金でも同様。

保存すべき項目

CREATE TABLE jpyc_transfers (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tx_hash TEXT UNIQUE NOT NULL,
    chain TEXT NOT NULL,  -- 'ethereum' / 'polygon' / 'avalanche'
    block_number BIGINT NOT NULL,
    sender_user_id UUID REFERENCES users(id),
    sender_address TEXT NOT NULL,
    receiver_address TEXT NOT NULL,
    amount_jpyc BIGINT NOT NULL,  -- 6 decimals
    amount_jpy NUMERIC(20, 0) NOT NULL,
    amount_usd NUMERIC(20, 6),
    screening_result JSONB NOT NULL,
    travel_rule_fired BOOLEAN DEFAULT FALSE,
    travel_rule_message_id TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_jpyc_sender ON jpyc_transfers(sender_user_id, created_at DESC);
CREATE INDEX idx_jpyc_receiver ON jpyc_transfers(receiver_address);

改竄不能な保管(対応予定)

  • Tamper-evident ログ: S3 Object Lock / Azure Immutable Blob に日次ダンプ
  • 監査エクスポート: 月次で金融庁・会計監査人向けの CSV エクスポート機能
  • 削除前の警告: 7 年経過時に削除候補通知、人間承認後に物理削除

7. 発行体 / 利用事業者 の役割分担

JPYC 発行体と、JPYC を業務利用する事業者では AML 責務のスコープが違います。

項目 JPYC 株式会社(発行体) 利用事業者(B2B送金サービス等)
発行時 KYC 必須(高強度)
顧客 KYC 必須
送金モニタリング 発行 / 償還レベルで把握 送金実行のたび
制裁照合 発行 / 償還時 送金実行時
トラベルルール VASP 間で必須 VASP であれば必須
疑わしい取引の届出 必要に応じて 必要に応じて
証跡保全 発行 / 償還記録 送金記録

利用事業者側は「JPYC 発行体が AML 対応してるから安心」ではなく、自前で送金レベルのモニタリングが必要。ここが今回の記事のメインテーマです。

8. チェーン別の実装注意点

Ethereum

  • ガスが高い(通常 $10-30 / tx)→ B2B 少額送金には不向き
  • Etherscan V2 API で TX 索引が簡単

Polygon

  • 国内事業者が選ぶことが多い(低ガス $0.01 / tx)
  • Etherscan V2 で chainid=137 として索引可能
  • POL(旧 MATIC)ガス支払い

Avalanche

  • C-Chain 使用、EVM 互換
  • Routescan API(Etherscan 互換、無料)でインデックス
  • 対応事業者は限定的
CHAIN_ETHERSCAN_ID = {
    "ethereum": "1",
    "polygon": "137",
    "avalanche": "43114",  # Routescan 経由
}

async def fetch_jpyc_transfers(chain: str, contract: str, since_block: int):
    chain_id = CHAIN_ETHERSCAN_ID[chain]
    # Avalanche は Routescan を使う
    base_url = (
        "https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan/api"
        if chain == "avalanche"
        else "https://api.etherscan.io/v2/api"
    )
    params = {
        "chainid": chain_id,
        "module": "account",
        "action": "tokentx",
        "contractaddress": contract,
        "startblock": str(since_block),
        "apikey": ETHERSCAN_API_KEY,
    }
    async with httpx.AsyncClient() as client:
        r = await client.get(base_url, params=params)
        return r.json().get("result", [])

9. 実装のチェックリスト

JPYC B2B 送金サービスを立ち上げるときの AML チェックリスト:

  • 暗号資産交換業 / 電子決済手段等取扱業者の登録要否を外部弁護士に確認
  • 顧客 KYC / eKYC を実装(本人確認書類 + 実質的支配者確認)
  • OFAC SDN リストの週次自動更新
  • 国内警察庁公表の凍結対象リスト照合
  • ScamDB / Known Entities レジストリ照合(ダークネット・ミキサー等)
  • Neo4j グラフ近接度(1-3 ホップ)
  • ML 異常スコア(IF + AE 程度で十分開始可能)
  • 閾値検知(犯収法 ¥100,000 超 / FATF VASP 向け USD/EUR 1,000 相当超)
  • トラベルルール プロトコル選定(Notabene / Sumsub / TRP 等)
  • VASP 特定用の取引所ホットウォレット レジストリ
  • ストラクチャリング対策の合算ベロシティ チェック
  • 証跡保全(7 年、改竄不能)
  • 疑わしい取引の届出(SAR)ドラフト生成
  • 誤検知率の週次モニタリング
  • FISC 準拠のセキュリティ統制(最低: 国内リージョン、TLS、暗号化、MFA、監査ログ)
  • 外部脆弱性診断 / ペネトレーション テスト(年次)

10. 国産のマルチチェーン AML として

筆者は株式会社refinancier として、ChainAnalyzer を運営しています。JPYC を含むステーブルコインの B2B 送金 AML に必要な機能をワンパッケージで提供:

  • JPYC 対応(Ethereum / Polygon / Avalanche 3 チェーン)
  • 受取先 5 層スクリーニング(OFAC → ScamDB → Known Entities → Graph → ML)
  • FATF トラベルルール 閾値検知とメタデータ生成
  • Token Contract Monitor で JPYC コントラクト自体の異常パターン監視(P1-P5)
  • Case Management で疑わしい送金を調査ケースとして記録
  • Follow Mode で受取先から先の資金フロー自動追跡
  • 日本語ネイティブ UI・ドキュメント・メール通知
  • FISC 準拠(Azure Japan East ホスティング、暗号化、監査ログ)
  • 無料プランから Enterprise 個別見積まで

ChainAnalyzer 公式サイト

まとめ

  • JPYC 発行体は Elliptic と提携して AML 体制整備済み、利用事業者側にも別途 AML 責務あり
  • 実装の肝は 受取先スクリーニング → 閾値検知 → トラベルルール → 証跡保全
  • Polygon が低ガスで国内 B2B 送金に現実的、Avalanche は Routescan 経由で索引
  • 7 年の取引記録保存、ストラクチャリング対策の合算ベロシティは必須
  • JPYC B2B 送金サービス立ち上げ前に、外部弁護士 + AML ツール選定を

このシリーズはこれで一区切りです。次は個別トピック(例: ブリッジ経由の資金追跡、Neo4j でのコミュニティ検出、etc.)を不定期で書いていく予定です。

ChainAnalyzer 公式サイト の無料プランで、ぜひ実装のイメージを掴んでください。


参考リンク:


ChainAnalyzer では、Solana / EVM / Bitcoin を含むマルチチェーンの
AMLリスクスコアリング、制裁リスト照合、ウォレットドレイナー検知、
トランザクション追跡を提供しています。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?