Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .cursor/rules/blockchain.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ alwaysApply: false
8. **攔截器機制**:使用 [BlockchainProviderInterceptor](mdc:src/chains/interceptors/blockchain-provider.interceptor.ts) 處理提供者選擇
9. **請求級提供者**:使用 [UseBlockchainProvider](mdc:src/chains/decorators/blockchain-provider.decorator.ts) 裝飾器設定提供者
10. **錯誤處理**:使用 [ErrorCode](mdc:src/common/constants/error-codes.ts) 和自定義異常提供統一錯誤處理
11. **價格擴展**:使用 [PriceableChainService](mdc:src/chains/services/core/priceable-chain.service.ts) 與 [PriceableEvmChainService](mdc:src/chains/services/core/priceable-evm-chain.service.ts) 抽象類為鏈服務提供 USD 價格增強功能

## 目錄結構

Expand All @@ -41,6 +42,8 @@ src/chains/
│ │ ├── chain-service.factory.ts # 鏈服務工廠
│ │ ├── chain-router.service.ts # 鏈路由服務
│ │ ├── discovery.service.ts # 裝飾器發現服務
│ │ ├── priceable-chain.service.ts # 可報價鏈服務抽象類
│ │ ├── priceable-evm-chain.service.ts # 可報價EVM鏈服務抽象類
│ │ ├── blockchain.service.ts # 區塊鏈服務
│ │ └── request-context.service.ts # 請求上下文服務
│ ├── ethereum/ # 以太坊服務
Expand Down Expand Up @@ -103,6 +106,7 @@ src/chains/
- **抽象基類更新**:移除 `isTestnet` 標誌,改用 `currentChainId` 屬性
- **提供者工廠升級**:`ProviderFactory.getEvmProvider(chainId)` 基於 chainId 獲取提供者
- **元數據增強**:`CHAIN_INFO_MAP` 擴展包含所有主網和測試網資訊
- **價格能力擴展**:[PriceableEvmChainService](mdc:src/chains/services/core/priceable-evm-chain.service.ts) 在 EVM 服務中提供代幣 USD 價格計算功能

### 4. API 端點增強

Expand Down
310 changes: 55 additions & 255 deletions .github/workflows/gcp-deploy-unified.yml

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
<img src="https://raw.githubusercontent.com/sd0xdev/onekey-balance-kit/main/docs/assets/logo.png" alt="OneKeyBalanceKit Logo" width="200">
</p>

<p align="center">
<a href="https://raw.githubusercontent.com/sd0xdev/onekey-balance-kit/main/docs/assets/demo.mp4">
<img src="https://img.shields.io/badge/查看演示視頻-點擊觀看-blue?style=for-the-badge&logo=github" alt="查看演示視頻">
</a>
</p>

> 一個高性能、可擴展的多鏈資產餘額查詢服務,支持以太坊和 Solana 區塊鏈,提供統一的 API 接口來查詢地址的原生代幣、ERC-20/SPL 代幣和 NFT 資產。

## 📑 目錄
Expand Down Expand Up @@ -274,6 +280,9 @@ A: 參考[區塊鏈提供者](.cursor/rules/blockchain-providers.mdc)文檔,
- [x] ETH Mainnet MVP
- [x] 多鏈抽象(EVM/L2/BTC/Solana/Discovery)
- [x] 服務註冊 / 發現 / 路由
- [x] Price Provider
- [x] Mock Price Provider
- [ ] OKX Price Provider
- [x] EVM 鏈抽象
- [x] Base
- [x] Optimism
Expand Down
74 changes: 42 additions & 32 deletions cloud-run-service.template.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: one-key-balance-kit${ENV_SUFFIX}
name: one-key-balance-kit${env_suffix}
labels:
environment: ${ENVIRONMENT}
environment: ${environment}
annotations:
run.googleapis.com/client-name: 'github-actions'
run.googleapis.com/ingress: 'all'
Expand All @@ -12,31 +12,31 @@ spec:
metadata:
annotations:
autoscaling.knative.dev/minScale: '0'
autoscaling.knative.dev/maxScale: '${MAX_INSTANCES}'
autoscaling.knative.dev/maxScale: '${max_instances}'
run.googleapis.com/cpu-throttling: 'true'
run.googleapis.com/execution-environment: 'gen2'
spec:
containerConcurrency: 80
timeoutSeconds: 300
containers:
- image: ${IMAGE_PATH}:${IMAGE_TAG}
- image: ${image_path}:${img_tag}
resources:
limits:
cpu: '1'
memory: '1Gi'
env:
- name: NODE_ENV
value: ${NODE_ENV}
value: ${node_env}
- name: APP_NAME
value: one-key-balance-kit${ENV_SUFFIX}
value: one-key-balance-kit${env_suffix}
- name: LOG_LEVEL
value: '${LOG_LEVEL}'
value: '${log_level}'
- name: API_BASE_URL
value: ${API_BASE_URL}
value: ${api_base_url}
- name: CORS_ORIGIN
value: '${CORS_ORIGIN}'
value: '${cors_origin}'
- name: WEBHOOK_URL
value: ${WEBHOOK_URL}
value: ${webhook_url}
- name: NETWORK_TIMEOUT
value: '30000'
- name: NETWORK_RETRIES
Expand All @@ -45,112 +45,122 @@ spec:
- name: MONGO_URL
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_MONGO_URL
name: ${secret_prefix}_MONGO_URL
key: latest
- name: MONGO_HOST
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_MONGO_HOST
name: ${secret_prefix}_MONGO_HOST
key: latest
- name: MONGO_PORT
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_MONGO_PORT
name: ${secret_prefix}_MONGO_PORT
key: latest
- name: MONGO_USERNAME
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_MONGO_USERNAME
name: ${secret_prefix}_MONGO_USERNAME
key: latest
- name: MONGO_PASSWORD
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_MONGO_PASSWORD
name: ${secret_prefix}_MONGO_PASSWORD
key: latest
- name: MONGO_DATABASE
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_MONGO_DATABASE
name: ${secret_prefix}_MONGO_DATABASE
key: latest
- name: REDIS_HOST
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_REDIS_HOST
name: ${secret_prefix}_REDIS_HOST
key: latest
- name: REDIS_PORT
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_REDIS_PORT
name: ${secret_prefix}_REDIS_PORT
key: latest
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_REDIS_PASSWORD
name: ${secret_prefix}_REDIS_PASSWORD
key: latest
- name: REDIS_DB
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_REDIS_DB
name: ${secret_prefix}_REDIS_DB
key: latest
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: ${secret_prefix}_REDIS_URL
key: latest
- name: RPC_URL
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_RPC_URL
name: ${secret_prefix}_RPC_URL
key: latest
- name: CHAIN_ID
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_CHAIN_ID
name: ${secret_prefix}_CHAIN_ID
key: latest
- name: ALCHEMY_API_KEY_ETH
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_ALCHEMY_API_KEY_ETH
name: ${secret_prefix}_ALCHEMY_API_KEY_ETH
key: latest
- name: ALCHEMY_API_KEY_SOL
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_ALCHEMY_API_KEY_SOL
name: ${secret_prefix}_ALCHEMY_API_KEY_SOL
key: latest
- name: ALCHEMY_API_KEY
valueFrom:
secretKeyRef:
name: ${secret_prefix}_ALCHEMY_API_KEY
key: latest
- name: ALCHEMY_TOKEN
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_ALCHEMY_TOKEN
name: ${secret_prefix}_ALCHEMY_TOKEN
key: latest
- name: QUICKNODE_ETH_MAINNET_URL
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_QUICKNODE_ETH_MAINNET_URL
name: ${secret_prefix}_QUICKNODE_ETH_MAINNET_URL
key: latest
- name: QUICKNODE_ETH_TESTNET_URL
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_QUICKNODE_ETH_TESTNET_URL
name: ${secret_prefix}_QUICKNODE_ETH_TESTNET_URL
key: latest
- name: OKX_API_KEY
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_OKX_API_KEY
name: ${secret_prefix}_OKX_API_KEY
key: latest
- name: OKX_SECRET_KEY
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_OKX_SECRET_KEY
name: ${secret_prefix}_OKX_SECRET_KEY
key: latest
- name: OKX_API_PASSPHRASE
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_OKX_API_PASSPHRASE
name: ${secret_prefix}_OKX_API_PASSPHRASE
key: latest
- name: OKX_PROJECT_ID
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_OKX_PROJECT_ID
name: ${secret_prefix}_OKX_PROJECT_ID
key: latest
- name: API_KEY
valueFrom:
secretKeyRef:
name: ${SECRET_PREFIX}_API_KEY
name: ${secret_prefix}_API_KEY
key: latest
# ---- Health checks ----
startupProbe:
Expand Down
105 changes: 105 additions & 0 deletions cloudbuild.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# cloudbuild.yaml ── Build → Deploy
substitutions:
_IMAGE_PATH: '${_REGION}-docker.pkg.dev/$PROJECT_ID/one-key-balance-kit/api'
_ENV: 'staging' # GitHub Action 會覆寫
_MAX_INSTANCES: '1'
_REGION: 'asia-east1' # 預設值,GitHub Action 會覆寫
_GIT_SHA: 'latest' # 預設值,GitHub Action 會覆寫
timeout: '1200s'
options:
machineType: 'E2_HIGHCPU_8' # 免費額度同享
logging: 'CLOUD_LOGGING_ONLY'
steps:
# --- 1) Build multi-arch image ------------------------------------------
- id: build-image
name: gcr.io/kaniko-project/executor:latest
entrypoint: ''
args:
# context 與 Dockerfile (預設為 ./Dockerfile)
- '--context=.'
- '--dockerfile=Dockerfile'

# 組合 tag ── 使用 _GIT_SHA,如空則 latest
- '--destination=${_IMAGE_PATH}:${_GIT_SHA}'
- '--destination=${_IMAGE_PATH}:${_ENV}'
- '--destination=${_IMAGE_PATH}:latest'

# 建置引數
- '--build-arg=NODE_ENV=$(_ENV==production?"production":"staging")'

# Kaniko layer cache(存放於同一 Registry)
- '--cache=true'
- '--cache-ttl=48h'

# --- 2) 產生 manifest & 部署 -------------------------------------------
- id: deploy
name: gcr.io/google.com/cloudsdktool/cloud-sdk
entrypoint: bash
args:
- -ceu
- |

# 使用條件語句決定後綴 (避免 Cloud Build 解析變數)
if [ "${_ENV}" = "production" ]; then
suffix=""
else
suffix="-dev"
fi

# 使用 _GIT_SHA 或 latest 作為映像標籤
tag="$_GIT_SHA"
if [ -z "$tag" ]; then
tag="latest"
fi

# 避免使用可能被 Cloud Build 誤解的變數名稱
export env_suffix=$([ "${_ENV}" = "production" ] && echo "" || echo "-dev")
export environment="${_ENV}"
export max_instances="${_MAX_INSTANCES}"
export region="${_REGION}"
export project_id="$PROJECT_ID"
export img_tag="$tag"
export node_env=$([ "${_ENV}" = "production" ] && echo "production" || echo "staging")
export log_level=$([ "${_ENV}" = "production" ] && echo "" || echo "debug")
export api_base_url=$([ "${_ENV}" = "production" ] && echo "https://api-onekeybalance.sd0.tech" || echo "https://staging-api-onekeybalance.sd0.tech")
export cors_origin=$([ "${_ENV}" = "production" ] && echo "https://onekeybalance.sd0.tech" || echo '"*"')
export webhook_url=$([ "${_ENV}" = "production" ] && echo "https://api-onekeybalance.sd0.tech/v1/api/webhook" || echo "https://staging-api-onekeybalance.sd0.tech/v1/api/webhook")
export secret_prefix=$([ "${_ENV}" = "production" ] && echo "production" || echo "staging")

# 構建映像路徑
export image_path="${_IMAGE_PATH}"

# 顯示將用於部署的映像
echo "部署使用的映像: $image_path:$tag"

# 使用 sed 替換環境變數,而不是 envsubst
cp cloud-run-service.template.yaml cloud-run-service.generated.yaml
sed -i "s|\${env_suffix}|$env_suffix|g" cloud-run-service.generated.yaml
sed -i "s|\${environment}|$environment|g" cloud-run-service.generated.yaml
sed -i "s|\${max_instances}|$max_instances|g" cloud-run-service.generated.yaml
sed -i "s|\${region}|$region|g" cloud-run-service.generated.yaml
sed -i "s|\${project_id}|$project_id|g" cloud-run-service.generated.yaml
sed -i "s|\${image_path}|$image_path|g" cloud-run-service.generated.yaml
sed -i "s|\${img_tag}|$img_tag|g" cloud-run-service.generated.yaml
sed -i "s|\${node_env}|$node_env|g" cloud-run-service.generated.yaml
sed -i "s|\${log_level}|$log_level|g" cloud-run-service.generated.yaml
sed -i "s|\${api_base_url}|$api_base_url|g" cloud-run-service.generated.yaml
sed -i "s|\${cors_origin}|$cors_origin|g" cloud-run-service.generated.yaml
sed -i "s|\${webhook_url}|$webhook_url|g" cloud-run-service.generated.yaml
sed -i "s|\${secret_prefix}|$secret_prefix|g" cloud-run-service.generated.yaml

# 檢查是否有未替換的變數
echo "檢查是否有未替換的變數 (應該不顯示任何內容):"
grep -o '\${[^}]*}' cloud-run-service.generated.yaml || echo "全部變數已成功替換!"

# 檢查生成的 manifest
echo "生成 manifest 完成,檢查映像路徑:"
grep -A 2 "containers:" cloud-run-service.generated.yaml

# 部署至 Cloud Run
gcloud run services replace cloud-run-service.generated.yaml \
--region=${_REGION} --project=$PROJECT_ID

# 注意: 此處不使用獨立的 images 區段
# 我們已經在 build-image 步驟中使用 docker buildx 構建並推送了映像
# 這樣可以避免 Cloud Build 解析變數時出錯
Binary file added docs/assets/demo.mp4
Binary file not shown.
Loading
Loading