ChainScore — アーキテクチャ

EthereumBitcoinXRPTRONSolana アドレスを対象にしたオンチェーン信用スコアリング & リスク評価サービス。 DeFi 活動・取引先信用度・ネットワーク多様性・OFAC 制裁照合など 13 項目以上の指標から、0〜1000 の信用スコアと 5 段階リスク評価を可視化します。


目次

  1. システム概要
  2. 技術スタック
  3. 高レベル構成図
  4. コンポーネント
  5. リクエスト / ジョブの流れ
  6. データモデル
  7. スコアリング方式
  8. 外部サービス
  9. 多言語対応
  10. セキュリティとプライバシー
  11. 運用
  12. デプロイパイプライン
  13. 可観測性
  14. コスト試算

システム概要

ChainScore は、ブロックチェーンアドレスを入力するだけで、そのオンチェーン履歴に基づく信用評価を即座に得られる公開 Web アプリです。

5 チェーン対応(アドレス形式から自動判定):

チェーン アドレス形式 データソース
Ethereum (EVM) 0x[40 桁の hex](大小無視・小文字化) Etherscan API
Bitcoin bc1... / 1... / 3... Blockstream Esplora
XRP Ledger r[24-34 桁の base58] xrpscan API
TRON T[33 桁の base58] TronGrid API
Solana [32-44 桁の base58] Solana JSON-RPC (mainnet-beta)

アドレス検出順は EVM → BTC → XRP → TRON → SOL。TRON 文字列は BTC レガシーと衝突し、Solana は最も寛容なので最後にチェック。EVM 以外は 大文字小文字を保ったまま保存・検索される。

主要な出力:


技術スタック

アプリケーション

階層 技術
フロントエンド Next.js 14(App Router)、React 18、TypeScript
チャート Recharts(レーダー / ゲージ / 折れ線)
API Fastify 4(Node.js 20)、TypeScript
ワーカー AWS Lambda(Node.js 20)、TypeScript
キャッシュ Redis(ioredis)
永続化 DynamoDB(DocumentClient)
キュー SQS(DLQ あり)
多言語化 自前 React Context(外部 i18n ライブラリ不使用)
テスト Vitest

インフラ

階層 サービス
CDN / DNS CloudFront + Route53(ACM 証明書は us-east-1)
WAF AWS WAFv2(共通ルール、匿名 IP ブロック、レート制限)
常駐コンピュート ECS Fargate(Frontend + Score API、各 2 タスク)
ジョブコンピュート Lambda(score-engine)を SQS でトリガー
状態 DynamoDB(PITR + 削除保護)
キャッシュ ElastiCache Redis(cache.t4g.micro)
ネットワーク パブリック + プライベートサブネットの VPC、NAT ゲートウェイ
シークレット AWS Secrets Manager(Etherscan API キー)
コンテナレジストリ ECR
プロビジョニング CloudFormation(2 リージョン × 3 スタック)
ロギング CloudWatch Logs(/ecs/*/aws/lambda/*)
エラー監視(任意) Sentry
アナリティクス(任意) Plausible(Cookieless、同意連動)
広告(任意) Google AdSense(同意連動でスクリプト読込)

高レベル構成図

flowchart TB Browser["ユーザーブラウザ
(chain-score.com)
Next.js SSR + React"] subgraph Edge["エッジ (us-east-1 cert)"] CF["CloudFront + WAF
SSL 終端 / レート制限 / 静的キャッシュ"] end subgraph Region["ap-northeast-1"] ALB["ALB
/api/* → score-api / else → frontend"] subgraph ECS["ECS Fargate"] FE["frontend (Next.js × 2)"] API["score-api (Fastify × 2)"] end Redis[("ElastiCache Redis
スコア/レート制限/統計/価格キャッシュ")] DDB[("DynamoDB
scores / jobs
PITR 有効")] SQS[/"SQS
score-jobs + DLQ"/] Lambda["Lambda: score-engine
(Node 20)"] Secrets[["Secrets Manager
Etherscan API キー"]] end subgraph External["外部サービス"] Etherscan["Etherscan API
(EVM)"] Esplora["Blockstream Esplora
(Bitcoin)"] Xrpscan["xrpscan API
(XRP)"] Tron["TronGrid API
(TRON)"] Sol["Solana RPC
(SOL)"] Gecko["CoinGecko
(価格)"] end Browser -->|HTTPS| CF CF --> ALB ALB --> FE ALB --> API API --> Redis API --> DDB API --> SQS API -.価格プロキシ.-> Gecko SQS -->|trigger| Lambda Lambda --> DDB Lambda --> Redis Lambda --> Secrets Lambda --> Etherscan Lambda --> Esplora Lambda --> Xrpscan Lambda --> Tron Lambda --> Sol Lambda --> Gecko

コンポーネント

frontend/ (Next.js、ECS Fargate)

Next.js 14 App Router。基本的にクライアントコンポーネント中心、SEO メタデータと初期データ取得のみ軽い SSR を行う構成。

主要ファイル:

score-api/ (Fastify、ECS Fargate)

ステートレスな 2 レプリカ HTTP 層。リクエスト検証・キャッシュ・ジョブディスパッチを担当。

エンドポイント:

パス メソッド 用途
/health GET ヘルスチェック
/api/score/:address GET キャッシュ済みスコアを返す、または ジョブ enqueue + 202
/api/score/:address?refresh=1 GET キャッシュをバイパスして再計算。Redis に 60 秒/アドレスのスロットルキー(超過時 429 + Retry-After)
/api/score/:address/status GET フロントが done まで polling する
/api/stats GET コミュニティ中央値(チェーン別の検索数 + スコア + メトリック中央値)
/api/whales?limit=&minUsd= GET 直近の大口送金フィード(短キャッシュ 5 秒)
/api/price?symbol=BTC&currency=USD GET サーバ側 CoinGecko プロキシ(5 分 Redis キャッシュ)

横断機能:

score-engine/ (Lambda、SQS トリガー)

ステートレスなワーカー。重いオンチェーンデータ取得とスコア計算を担当。

メッセージごとの処理フロー:

  1. Secrets Manager から API キーを取得(コンテナメモリでキャッシュ)。
  2. CoinGecko から JPY レートを取得(3 通貨)。
  3. チェーン別分岐:
  4. scoring.ts で重み付きスコアを計算(チェーン別ウェイトは合計 100%)。
  5. DynamoDB(scores テーブル)と Redis に保存。
  6. ジョブステータスを done に更新。

失敗時: エラーをジョブテーブルに failed として記録。3 回リトライ後 DLQ に送信。

infra/ (CloudFormation + スクリプト)

2 リージョンに 3 スタック:

スタック リージョン リソース
chainscore-cert us-east-1 ACM 証明書 + WAFv2 ACL(マネージドルール + レート制限)
chainscore-network ap-northeast-1 VPC、パブリック/プライベートサブネット、NAT、IGW、ECR リポジトリ
chainscore ap-northeast-1 DynamoDB、SQS、ElastiCache、ALB、ECS クラスタ + 2 サービス、Lambda、IAM、Secrets Manager、CloudFront、Route53 レコード、CloudWatch アラーム

スクリプト:


リクエスト / ジョブの流れ

コールドアドレス(キャッシュなし)

sequenceDiagram autonumber participant B as ブラウザ participant CF as CloudFront participant ALB as ALB participant API as score-api participant Q as SQS participant L as Lambda
score-engine participant S as DDB / Redis B->>CF: GET /score/0xabc CF->>ALB: forward ALB->>API: GET /api/score/:addr API->>S: キャッシュ / DDB 検索 S-->>API: miss API->>Q: ジョブ enqueue API-->>B: 202 (jobId) Q->>L: trigger L->>L: オンチェーン取得
(Etherscan / Esplora / etc.) L->>L: スコア計算 L->>S: スコア + キャッシュ書き込み loop 完了するまで polling B->>API: GET /api/score/:addr/status API->>S: 状態確認 S-->>API: done API-->>B: { status: "done" } end B->>API: GET /api/score/:addr API->>S: hit S-->>API: スコア JSON API-->>B: スコア JSON

ウォームアドレス(キャッシュヒット)

同じフローだが、最初の DDB / Redis 検索でキャッシュ済みレコードが返り、ジョブ enqueue は発生しない。


データモデル

DynamoDB — chainscore-scores

PK: address (S) 例: "0xabc..." (EVM は小文字化、BTC/XRP/TRON/SOL は原文ママ) SK: chain#timestamp (S) 例: "eth#2025-04-25T08:14:00.000Z" 属性: chain S "eth" | "btc" | "xrp" | "tron" | "sol" timestamp S ISO 8601 score N 0–1000 metrics L [{ key, label, weight, value }] balance M { native, symbol, jpy, jpyPerNative } history L [{ t, v }] ← 90 ポイント上限 recentTxs L [{ hash, ts, direction, counterparty, valueNative, status, flagged }] ← 500 件上限 tokens L [{ symbol, contract, decimals, balance }] ← ETH のみ ERC-20 残高 tokenTxs L [{ hash, ts, direction, counterparty, symbol, contract, value, status, flagged }] ← 300 件上限、ETH のみ compliance M { status, selfSanctioned, sanctionedCounterpartyHits, notes } ttl N エポック秒(30 日後に自動削除)

ScanIndexForward: false + Limit: 1 で、各チェーン × アドレスごとの最新スコアを取得。

DynamoDB — chainscore-jobs

PK: jobId (S) uuidv4 属性: address S chain S status S "pending" | "processing" | "done" | "failed" error S (status="failed" の場合のみ) createdAt S updatedAt S

Redis キー一覧

キー TTL 用途
score:{chain}:{addr} string (JSON) 24h ホットスコアキャッシュ
stats:{chain}:addresses set 30d 検索済みアドレス集合(SCARD で実検索数を返す)
stats:{chain}:scores list (上限 1000) 30d 直近スコアサンプル
stats:{chain}:metric:{key} list (上限 1000) 30d 直近メトリックサンプル
stats:{chain}:count integer 30d レガシーカウンタ(後方互換のため保持、UI には未使用)
refresh-throttle:{chain}:{addr} string 60s 「最新に更新」ボタンのアドレス単位スロットル
price:{symbol}:{currency} string 5m CoinGecko プロキシキャッシュ

スコアリング方式

各チェーンが固有のメトリックセットを持ち、ウェイトの合計は 100%compliance は意図的に重め(16%)で、selfSanctioned 該当アドレスはスコア ≤ 100 に強制キャップ。

Ethereum(14 メトリック)

キー ウェイト シグナル
age 10 最初の取引からの経過年数
txFreq 8 累計取引数の対数
balance 8 30 日間の残高分散(小さいほど高評価)
defi 10 DeFi プロトコルの幅と深さ
verified 10 やり取りしたコントラクトの Etherscan 検証率
counterparty 10 信頼できる相手(主要 CEX / ブルーチップ DeFi / ステーブルコイン)との取引比率
networkDiv 6 ユニーク取引相手数の対数
txSuccess 5 1 − 失敗 tx 率
dormancy 4 直近取引からの経過日数
stablecoinUsage 4 USDC/USDT/DAI/BUSD/FRAX とのやり取り比率
gasEfficiency 3 ガス価格中央値付近で取引している割合
nftActivity 3 OpenSea/Blur/LooksRare との取引比率
contractDeployer 3 デプロイしたコントラクト数
compliance 16 OFAC SDN + Tornado Cash + Lazarus 等

Bitcoin(13 メトリック)

キー ウェイト シグナル
age 10 最初の取引からの経過年数
txFreq 8 累計取引数の対数
balance 7 取引量分布の分散
lightning 10 P2WSH スクリプト比率(Lightning Network のプロキシ)
utxoDiv 6 UTXO 数 + 年齢分散
dormancy 8 直近取引からの経過日数
counterparty 10 信頼できる主要 CEX アドレスとの取引比率
networkDiv 6 ユニーク取引相手数の対数
holdingValue 5 現在の BTC 残高の対数
avgFee 4 手数料率(sat/vB)の妥当性
segwitUsage 5 SegWit(bc1q / P2WSH)採用比率
taprootUsage 5 Taproot(bc1p)採用比率
compliance 16 OFAC SDN + Hydra + Lazarus 等

TRON(12 メトリック)

キー ウェイト シグナル
age 10 アカウント開設からの経過年数
txFreq 8 累計取引数の対数
balance 8 取引量分布の分散
usdtActivity 14 USDT-TRC20 転送回数(対数)
counterparty 10 信頼できる主要 CEX / 取引先との比率
networkDiv 7 ユニーク取引相手数の対数
txSuccess 5 成功 tx 比率
holdingValue 7 TRX 残高の対数
dormancy 4 直近取引からの経過日数
bandwidthUsage 4 free_net_usage / free_net_limit ベースの効率
smartContract 7 スマートコントラクト呼び出し比率
compliance 16 OFAC SDN + 制裁ウォレット

Solana(14 メトリック)

キー ウェイト シグナル
age 10 最初の署名からの経過年数
txFreq 8 累計取引数の対数
balance 7 取引量分布の分散
splDiversity 5 保有 SPL トークン種別数
stablecoinUsage 5 USDC / USDT (SPL) との取引比率
defi 10 DeFi プログラムの幅と件数
counterparty 10 信頼できる主要 CEX / プログラムとの比率
networkDiv 7 ユニーク取引相手数の対数
txSuccess 5 成功 tx 比率
dormancy 4 直近取引からの経過日数
holdingValue 7 SOL 残高の対数
nftActivity 3 NFT マーケット (Magic Eden 等) との比率
programDeployer 3 デプロイ済みプログラム数
compliance 16 OFAC SDN + 制裁ウォレット

XRP(14 メトリック)

キー ウェイト シグナル
age 10 アカウント開設からの経過年数
txFreq 8 累計取引数の対数
balance 7 取引量分布の分散
trustLines 10 保有 TrustLine 数(IOU エコシステム参加度)
counterparty 10 信頼できる取引所 / Issuer との比率
networkDiv 7 ユニーク取引相手数の対数
txSuccess 5 tesSUCCESS 比率
holdingValue 7 現在の XRP 残高の対数
dormancy 4 直近取引からの経過日数
dexActivity 5 OfferCreate / OfferCancel の件数
domainSet 4 アカウント Domain フィールドの設定有無(身元検証)
regularKey 4 RegularKey 設定済み(セキュリティベストプラクティス)
destinationTagUsage 3 DestinationTag 設定済み支払いの比率
compliance 16 OFAC SDN + 制裁ウォレット

リスクレベル(5 段階)

ティア 条件
critical selfSanctioned または スコア < 200
high flagged コンプラ または 200 ≤ スコア < 400
medium 400 ≤ スコア < 600
low 600 ≤ スコア < 800
veryLow スコア ≥ 800 かつ コンプラ clean

外部サービス

サービス 用途 認証 レート制限
Etherscan EVM 取引履歴・コントラクト検証・ERC-20 残高 API キー(Secrets Manager) 5 req/秒、100k/日(無料枠)
Blockstream Esplora Bitcoin 取引 + UTXO + 残高 + ホエール検出 なし 公開・公正利用(ESPLORA_BASE で差し替え可)
xrpscan XRP アカウント・取引・TrustLine なし 公開・公正利用
TronGrid TRON アカウント + tx + TRC20(USDT) なし(必要に応じ API キー対応) 公開・公正利用
Solana RPC getSignaturesForAddress / getTransaction なし(SOL_RPC_URL で Helius 等差し替え可) mainnet-beta は厳しいレート制限あり
CoinGecko BTC/ETH/XRP/TRX/SOL → 法定通貨レート(7 通貨) なし 10〜30 req/分(無料枠)
OFAC SDN フィード 制裁リスト更新 なし 米財務省が日次更新

OFAC リストは score-engine/scripts/update-sanctions.ts で更新(手動またはスケジュール実行)。sanctions-data.json を出力し、sanctions.ts がランタイムで読み込みます。


多言語対応

7 言語対応 — 日本語(デフォルト)、英語、中国語(簡体字)、韓国語、スペイン語、ポルトガル語(BR)、ロシア語。

lib/i18n/ ├── messages.ts ← UI コア文字列(MessageKey は ja から自動派生) ├── uiExtra.ts ← フッター / 同意 / ナビ / メトリックラベル / メトリック説明 / 統計 ├── metrics.ts ← key + chain → MessageKey 解決 ├── I18nContext.tsx ← React Provider、useT() フック ├── legal/ │ ├── types.ts │ ├── ja.ts en.ts zh.ts ko.ts es.ts pt.ts ru.ts │ └── index.ts └── methodology/ └── ja.ts en.ts zh.ts ko.ts es.ts pt.ts ru.ts ← 各チェーンのスコアリング方式の詳細

セキュリティとプライバシー

懸念事項 対策
オンチェーンデータの扱い 公開情報として扱う。スコアとメトリック値のみ保存し、PII は保持しない
個人情報を取得しない アカウント・メールフォーム不在。localStorage に書くのは chainscore.localechainscore.consent のみ
CORS chain-score.com + www.chain-score.com の allow-list。ワイルドカードは mock/dev のみ
レート制限 アプリ層(Fastify、IP ごと 60/分、Redis バック) + WAF(全体 2000 req/5分、/api/* は 600 req/5分) + 「最新に更新」ボタンは Redis NX キーで 60 秒/アドレス
WAF マネージドルール AWSManagedRulesCommonRuleSet + AnonymousIpList(Tor / プロキシブロック)
AI スクレイパー対策 robots.ts で GPTBot / Claude-Web / CCBot / PerplexityBot の /score/* 訪問を拒否
スコアページ noindex, nofollow で動的アドレス URL を検索エンジンインデックスから除外
API キー Etherscan キーは AWS Secrets Manager 内、Lambda がコールドスタート時に取得
Cookie / localStorage Cookie 同意バナーで Plausible + AdSense をゲート。検証 meta タグは識別用のみで非トラッキング
PITR DynamoDB Point-in-Time Recovery + scores テーブルに DeletionProtection
CloudFront → ALB Origin Protocol Policy は http-only、Viewer 側で redirect-to-https 強制
コンプライアンス OFAC SDN を 検索アドレスと全取引相手の両方で照合。selfSanctioned はスコア ≤ 100 に強制キャップ

運用

新規プロビジョニング

infra/run.bat └─ bootstrap.bat → chainscore-network スタック (ap-northeast-1) └─ build-push.bat → Docker ビルド + ECR push + Lambda コード更新 └─ deploy.bat → chainscore-cert (us-east-1) + chainscore (ap-northeast-1) └─ build-push.bat → Lambda コード再公開(最終)

合計: 25〜40 分(ElastiCache + CloudFront が長時間要因)。

反復更新

infra/update.bat └─ build-push.bat → イメージ再ビルド + push + Lambda bundle └─ aws ecs update-service --force-new-deployment → ECS に新イメージを引かせる └─ aws cloudfront create-invalidation /* → CDN フラッシュ

合計: 5〜10 分。

環境破棄

infra/destroy.bat -y ├─ DDB DeletionProtection 解除 ├─ Secrets Manager 強制削除 ├─ ECR リポジトリを空に └─ 削除順: chainscore → chainscore-cert → chainscore-network

デプロイパイプライン

  1. ローカル: npm test(Vitest)+ 全 3 パッケージで tsc --noEmit
  2. ビルド: frontend/score-api/ は Dockerfile。score-engine/esbuild --bundle
  3. Push: aws ecr get-login-password | docker logindocker push
  4. 適用:
  5. 検証:

可観測性

シグナル 場所
ECS タスクログ /ecs/chainscore-frontend/ecs/chainscore-score-api(CloudWatch、14 日保持)
Lambda ログ /aws/lambda/chainscore-score-engine
スタックイベント aws cloudformation describe-stack-events
ECS ロールアウト状態 aws ecs describe-services --query "services[].deployments"
Lambda エラー EngineErrorAlarm(CloudWatch アラーム、5 分間で 1 件以上)
キュー深さ QueueDepthAlarm(10 分間で >100 メッセージ)
フロントエンドエラー(任意) Sentry(NEXT_PUBLIC_SENTRY_DSN)
サーバエラー(任意) Sentry(SENTRY_DSN)
訪問者数(任意) Plausible(NEXT_PUBLIC_PLAUSIBLE_DOMAIN)— Cookieless

コスト試算

低トラフィック時(約 100 検索 / 日)の月額概算:

サービス コスト
ECS Fargate(全 4 タスク) 約 $30
ElastiCache t4g.micro 約 $15
ALB 約 $20
NAT ゲートウェイ 約 $35
CloudFront + Route53 約 $2
DynamoDB(PAY_PER_REQUEST) 約 $1
Lambda + SQS <$1(無料枠)
Secrets Manager 約 $0.40
合計 約 $100/月

低トラフィック時は NAT ゲートウェイと ALB がコストを支配します。スケール時は分散されますが、小規模時は Cloud Run / Vercel に置換すれば月額下限を約 $10 まで下げられます。

約 10,000 検索 / 日まで成長すると、Etherscan 有料プラン(約 $200/月)+ Lambda 増(約 $5〜10)が加算されます。Fargate コストはオーバープロビジョニングのため大きく変動しません。


ライセンス・帰属

外部データ:

本サイトは 投資助言ではありません。スコアは推定値であり、詳細は /disclaimer を参照してください。