Compare commits
15 Commits
feat/canon
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48d3e3f761 | ||
|
|
bed94a3ad4 | ||
|
|
7be2190441 | ||
|
|
19cb7fe8b5 | ||
|
|
f1715fb684 | ||
|
|
6a9f5dead0 | ||
|
|
770a1db99a | ||
|
|
8868a3501f | ||
|
|
cf96e9d821 | ||
|
|
4c4aa28c95 | ||
|
|
9306a65186 | ||
|
|
7ab231c4ce | ||
|
|
b58c3a0342 | ||
|
|
b8e735dcac | ||
|
|
3bea587e12 |
@@ -6,6 +6,10 @@
|
||||
2. Make changes, ensure tests pass
|
||||
3. Open a pull request
|
||||
|
||||
Deploy workflow policy:
|
||||
`main` and `master` are both deploy-triggering branches, so `.gitea/workflow-sources/deploy-to-phoenix.yml` and `.gitea/workflow-sources/validate-on-pr.yml` must stay identical across both branches.
|
||||
Use `bash scripts/verify/sync-gitea-workflows.sh` after editing workflow-source files, and `bash scripts/verify/run-all-validation.sh --skip-genesis` to catch workflow drift before push.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
- Use the PR template when opening a PR
|
||||
|
||||
125
.gitea/workflow-sources/deploy-to-phoenix.yml
Normal file
125
.gitea/workflow-sources/deploy-to-phoenix.yml
Normal file
@@ -0,0 +1,125 @@
|
||||
# Canonical deploy workflow. Keep source and checked-in workflow copies byte-identical.
|
||||
# Validation checks both file sync and main/master parity.
|
||||
name: Deploy to Phoenix
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch deploy branches for workflow parity check
|
||||
run: |
|
||||
REMOTE="${GITEA_WORKFLOW_REMOTE:-origin}"
|
||||
if git remote | grep -qx gitea; then
|
||||
REMOTE="${GITEA_WORKFLOW_REMOTE:-gitea}"
|
||||
fi
|
||||
git fetch --depth=1 "$REMOTE" main master
|
||||
|
||||
- name: Install validation dependencies
|
||||
run: |
|
||||
corepack enable
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
# The cW* mesh matrix and deployment-status validators read
|
||||
# cross-chain-pmm-lps/config/*.json. The parent checkout does not
|
||||
# materialize submodules by default, and .gitmodules mixes public HTTPS
|
||||
# with SSH URLs, so clone only the required public validation dependency.
|
||||
- name: Materialize cross-chain-pmm-lps
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ ! -f cross-chain-pmm-lps/config/deployment-status.json ]; then
|
||||
rm -rf cross-chain-pmm-lps
|
||||
git clone --depth=1 \
|
||||
https://gitea.d-bis.org/d-bis/cross-chain-pmm-lps.git \
|
||||
cross-chain-pmm-lps
|
||||
fi
|
||||
|
||||
- name: Run repo validation gate
|
||||
run: |
|
||||
bash scripts/verify/run-all-validation.sh --skip-genesis
|
||||
|
||||
deploy:
|
||||
needs: validate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Trigger Phoenix deployment
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHA="$(git rev-parse HEAD)"
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||
set +e
|
||||
curl -sSf --retry 3 --retry-connrefused --retry-delay 10 --retry-max-time 180 \
|
||||
--connect-timeout 10 --max-time 120 \
|
||||
-X POST "${{ secrets.PHOENIX_DEPLOY_URL }}" \
|
||||
-H "Authorization: Bearer ${{ secrets.PHOENIX_DEPLOY_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"repo\":\"${{ gitea.repository }}\",\"sha\":\"${SHA}\",\"branch\":\"${BRANCH}\",\"target\":\"default\"}"
|
||||
rc="$?"
|
||||
set -e
|
||||
if [ "$rc" -eq 52 ]; then
|
||||
HEALTH_URL="${{ secrets.PHOENIX_DEPLOY_URL }}"
|
||||
HEALTH_URL="${HEALTH_URL%/api/deploy}/health"
|
||||
echo "Phoenix deploy API restarted during self-deploy; verifying ${HEALTH_URL}"
|
||||
for i in $(seq 1 12); do
|
||||
if curl -fsS --max-time 5 "$HEALTH_URL"; then
|
||||
exit 0
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
exit "$rc"
|
||||
|
||||
deploy-atomic-swap-dapp:
|
||||
needs: deploy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Trigger Atomic Swap dApp deployment (Phoenix)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHA="$(git rev-parse HEAD)"
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||
curl -sSf \
|
||||
--connect-timeout 10 --max-time 900 \
|
||||
-X POST "${{ secrets.PHOENIX_DEPLOY_URL }}" \
|
||||
-H "Authorization: Bearer ${{ secrets.PHOENIX_DEPLOY_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"repo\":\"${{ gitea.repository }}\",\"sha\":\"${SHA}\",\"branch\":\"${BRANCH}\",\"target\":\"atomic-swap-dapp-live\"}"
|
||||
|
||||
# After app deploy, ask Phoenix to run path-gated Cloudflare DNS sync on the host that has
|
||||
# PHOENIX_REPO_ROOT + .env (not on this runner). Skips unless PHOENIX_CLOUDFLARE_SYNC=1 on that host.
|
||||
# continue-on-error: first-time or missing opt-in should not block the main deploy.
|
||||
cloudflare:
|
||||
needs:
|
||||
- deploy
|
||||
- deploy-atomic-swap-dapp
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Request Cloudflare DNS sync (Phoenix)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHA="$(git rev-parse HEAD)"
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||
curl -sSf --retry 5 --retry-all-errors --retry-connrefused --retry-delay 10 --retry-max-time 300 \
|
||||
--connect-timeout 10 --max-time 120 \
|
||||
-X POST "${{ secrets.PHOENIX_DEPLOY_URL }}" \
|
||||
-H "Authorization: Bearer ${{ secrets.PHOENIX_DEPLOY_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"repo\":\"${{ gitea.repository }}\",\"sha\":\"${SHA}\",\"branch\":\"${BRANCH}\",\"target\":\"cloudflare-sync\"}" \
|
||||
|| { echo "Cloudflare DNS sync request failed; optional sync is non-blocking."; exit 0; }
|
||||
33
.gitea/workflow-sources/validate-on-pr.yml
Normal file
33
.gitea/workflow-sources/validate-on-pr.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
# Canonical PR validation workflow. Keep source and checked-in workflow copies byte-identical.
|
||||
# Validation checks both file sync and main/master parity.
|
||||
# PR-only: push validation already runs in deploy-to-phoenix.yml; this gives PRs the same
|
||||
# no-LAN checks without the deploy job (and without deploy secrets).
|
||||
name: Validate (PR)
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: [main, master]
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
run-all-validation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Fetch deploy branches for workflow parity check
|
||||
run: |
|
||||
REMOTE="${GITEA_WORKFLOW_REMOTE:-origin}"
|
||||
if git remote | grep -qx gitea; then
|
||||
REMOTE="${GITEA_WORKFLOW_REMOTE:-gitea}"
|
||||
fi
|
||||
git fetch --depth=1 "$REMOTE" main master
|
||||
- name: Install validation dependencies
|
||||
run: |
|
||||
corepack enable
|
||||
pnpm install --frozen-lockfile
|
||||
# Optional: set org/repo variable URA_STRICT_CLOSURE=1 to fail PRs while pilot placeholders
|
||||
# remain in manifest (see scripts/ura/validate-manifest-closure.mjs). Not enabled by default.
|
||||
- name: run-all-validation (no LAN, no genesis)
|
||||
env:
|
||||
URA_STRICT_CLOSURE: ${{ vars.URA_STRICT_CLOSURE }}
|
||||
run: bash scripts/verify/run-all-validation.sh --skip-genesis
|
||||
@@ -1,11 +1,52 @@
|
||||
# Canonical deploy workflow. Keep source and checked-in workflow copies byte-identical.
|
||||
# Validation checks both file sync and main/master parity.
|
||||
name: Deploy to Phoenix
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch deploy branches for workflow parity check
|
||||
run: |
|
||||
REMOTE="${GITEA_WORKFLOW_REMOTE:-origin}"
|
||||
if git remote | grep -qx gitea; then
|
||||
REMOTE="${GITEA_WORKFLOW_REMOTE:-gitea}"
|
||||
fi
|
||||
git fetch --depth=1 "$REMOTE" main master
|
||||
|
||||
- name: Install validation dependencies
|
||||
run: |
|
||||
corepack enable
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
# The cW* mesh matrix and deployment-status validators read
|
||||
# cross-chain-pmm-lps/config/*.json. The parent checkout does not
|
||||
# materialize submodules by default, and .gitmodules mixes public HTTPS
|
||||
# with SSH URLs, so clone only the required public validation dependency.
|
||||
- name: Materialize cross-chain-pmm-lps
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ ! -f cross-chain-pmm-lps/config/deployment-status.json ]; then
|
||||
rm -rf cross-chain-pmm-lps
|
||||
git clone --depth=1 \
|
||||
https://gitea.d-bis.org/d-bis/cross-chain-pmm-lps.git \
|
||||
cross-chain-pmm-lps
|
||||
fi
|
||||
|
||||
- name: Run repo validation gate
|
||||
run: |
|
||||
bash scripts/verify/run-all-validation.sh --skip-genesis
|
||||
|
||||
deploy:
|
||||
needs: validate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -13,8 +54,72 @@ jobs:
|
||||
|
||||
- name: Trigger Phoenix deployment
|
||||
run: |
|
||||
curl -sSf -X POST "${{ secrets.PHOENIX_DEPLOY_URL }}" \
|
||||
set -euo pipefail
|
||||
SHA="$(git rev-parse HEAD)"
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||
set +e
|
||||
curl -sSf --retry 3 --retry-connrefused --retry-delay 10 --retry-max-time 180 \
|
||||
--connect-timeout 10 --max-time 120 \
|
||||
-X POST "${{ secrets.PHOENIX_DEPLOY_URL }}" \
|
||||
-H "Authorization: Bearer ${{ secrets.PHOENIX_DEPLOY_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"repo\":\"${{ gitea.repository }}\",\"sha\":\"${{ gitea.sha }}\",\"branch\":\"${{ gitea.ref_name }}\"}"
|
||||
continue-on-error: true
|
||||
-d "{\"repo\":\"${{ gitea.repository }}\",\"sha\":\"${SHA}\",\"branch\":\"${BRANCH}\",\"target\":\"default\"}"
|
||||
rc="$?"
|
||||
set -e
|
||||
if [ "$rc" -eq 52 ]; then
|
||||
HEALTH_URL="${{ secrets.PHOENIX_DEPLOY_URL }}"
|
||||
HEALTH_URL="${HEALTH_URL%/api/deploy}/health"
|
||||
echo "Phoenix deploy API restarted during self-deploy; verifying ${HEALTH_URL}"
|
||||
for i in $(seq 1 12); do
|
||||
if curl -fsS --max-time 5 "$HEALTH_URL"; then
|
||||
exit 0
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
exit "$rc"
|
||||
|
||||
deploy-atomic-swap-dapp:
|
||||
needs: deploy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Trigger Atomic Swap dApp deployment (Phoenix)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHA="$(git rev-parse HEAD)"
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||
curl -sSf \
|
||||
--connect-timeout 10 --max-time 900 \
|
||||
-X POST "${{ secrets.PHOENIX_DEPLOY_URL }}" \
|
||||
-H "Authorization: Bearer ${{ secrets.PHOENIX_DEPLOY_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"repo\":\"${{ gitea.repository }}\",\"sha\":\"${SHA}\",\"branch\":\"${BRANCH}\",\"target\":\"atomic-swap-dapp-live\"}"
|
||||
|
||||
# After app deploy, ask Phoenix to run path-gated Cloudflare DNS sync on the host that has
|
||||
# PHOENIX_REPO_ROOT + .env (not on this runner). Skips unless PHOENIX_CLOUDFLARE_SYNC=1 on that host.
|
||||
# continue-on-error: first-time or missing opt-in should not block the main deploy.
|
||||
cloudflare:
|
||||
needs:
|
||||
- deploy
|
||||
- deploy-atomic-swap-dapp
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Request Cloudflare DNS sync (Phoenix)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHA="$(git rev-parse HEAD)"
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||
curl -sSf --retry 5 --retry-all-errors --retry-connrefused --retry-delay 10 --retry-max-time 300 \
|
||||
--connect-timeout 10 --max-time 120 \
|
||||
-X POST "${{ secrets.PHOENIX_DEPLOY_URL }}" \
|
||||
-H "Authorization: Bearer ${{ secrets.PHOENIX_DEPLOY_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"repo\":\"${{ gitea.repository }}\",\"sha\":\"${SHA}\",\"branch\":\"${BRANCH}\",\"target\":\"cloudflare-sync\"}" \
|
||||
|| { echo "Cloudflare DNS sync request failed; optional sync is non-blocking."; exit 0; }
|
||||
|
||||
33
.gitea/workflows/validate-on-pr.yml
Normal file
33
.gitea/workflows/validate-on-pr.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
# Canonical PR validation workflow. Keep source and checked-in workflow copies byte-identical.
|
||||
# Validation checks both file sync and main/master parity.
|
||||
# PR-only: push validation already runs in deploy-to-phoenix.yml; this gives PRs the same
|
||||
# no-LAN checks without the deploy job (and without deploy secrets).
|
||||
name: Validate (PR)
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: [main, master]
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
run-all-validation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Fetch deploy branches for workflow parity check
|
||||
run: |
|
||||
REMOTE="${GITEA_WORKFLOW_REMOTE:-origin}"
|
||||
if git remote | grep -qx gitea; then
|
||||
REMOTE="${GITEA_WORKFLOW_REMOTE:-gitea}"
|
||||
fi
|
||||
git fetch --depth=1 "$REMOTE" main master
|
||||
- name: Install validation dependencies
|
||||
run: |
|
||||
corepack enable
|
||||
pnpm install --frozen-lockfile
|
||||
# Optional: set org/repo variable URA_STRICT_CLOSURE=1 to fail PRs while pilot placeholders
|
||||
# remain in manifest (see scripts/ura/validate-manifest-closure.mjs). Not enabled by default.
|
||||
- name: run-all-validation (no LAN, no genesis)
|
||||
env:
|
||||
URA_STRICT_CLOSURE: ${{ vars.URA_STRICT_CLOSURE }}
|
||||
run: bash scripts/verify/run-all-validation.sh --skip-genesis
|
||||
@@ -2076,10 +2076,10 @@
|
||||
"baseSymbol": "cWETH",
|
||||
"quoteSymbol": "USDC",
|
||||
"poolAddress": "0xd012000000000000000000000000000000000001",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_mainnet",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2091,10 +2091,10 @@
|
||||
"baseSymbol": "cWETH",
|
||||
"quoteSymbol": "WETH",
|
||||
"poolAddress": "0xd011000000000000000000000000000000000001",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_mainnet",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2150,10 +2150,10 @@
|
||||
"baseSymbol": "cWETHL2",
|
||||
"quoteSymbol": "USDC",
|
||||
"poolAddress": "0xd02200000000000000000000000000000000000a",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_l2",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2165,10 +2165,10 @@
|
||||
"baseSymbol": "cWETHL2",
|
||||
"quoteSymbol": "WETH",
|
||||
"poolAddress": "0xd02100000000000000000000000000000000000a",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_l2",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2246,10 +2246,10 @@
|
||||
"baseSymbol": "cWXDAI",
|
||||
"quoteSymbol": "USDC",
|
||||
"poolAddress": "0xd072000000000000000000000000000000000064",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "xdai",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2261,10 +2261,10 @@
|
||||
"baseSymbol": "cWXDAI",
|
||||
"quoteSymbol": "WXDAI",
|
||||
"poolAddress": "0xd071000000000000000000000000000000000064",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "xdai",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2276,10 +2276,10 @@
|
||||
"baseSymbol": "cWWEMIX",
|
||||
"quoteSymbol": "USDC",
|
||||
"poolAddress": "0xd092000000000000000000000000000000000457",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "wemix",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2291,10 +2291,10 @@
|
||||
"baseSymbol": "cWWEMIX",
|
||||
"quoteSymbol": "WWEMIX",
|
||||
"poolAddress": "0xd091000000000000000000000000000000000457",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "wemix",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2339,10 +2339,10 @@
|
||||
"baseSymbol": "cWPOL",
|
||||
"quoteSymbol": "USDC",
|
||||
"poolAddress": "0xd042000000000000000000000000000000000089",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "pol",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2354,10 +2354,10 @@
|
||||
"baseSymbol": "cWPOL",
|
||||
"quoteSymbol": "WPOL",
|
||||
"poolAddress": "0xd041000000000000000000000000000000000089",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "pol",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2413,10 +2413,10 @@
|
||||
"baseSymbol": "cWCRO",
|
||||
"quoteSymbol": "USDT",
|
||||
"poolAddress": "0xd062000000000000000000000000000000000019",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "cro",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2428,10 +2428,10 @@
|
||||
"baseSymbol": "cWCRO",
|
||||
"quoteSymbol": "WCRO",
|
||||
"poolAddress": "0xd061000000000000000000000000000000000019",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "cro",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2487,10 +2487,10 @@
|
||||
"baseSymbol": "cWETHL2",
|
||||
"quoteSymbol": "USDC",
|
||||
"poolAddress": "0xd02200000000000000000000000000000000a4b1",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_l2",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2502,10 +2502,10 @@
|
||||
"baseSymbol": "cWETHL2",
|
||||
"quoteSymbol": "WETH",
|
||||
"poolAddress": "0xd02100000000000000000000000000000000a4b1",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_l2",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2572,10 +2572,10 @@
|
||||
"baseSymbol": "cWCELO",
|
||||
"quoteSymbol": "USDC",
|
||||
"poolAddress": "0xd08200000000000000000000000000000000a4ec",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "celo",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2587,10 +2587,10 @@
|
||||
"baseSymbol": "cWCELO",
|
||||
"quoteSymbol": "WCELO",
|
||||
"poolAddress": "0xd08100000000000000000000000000000000a4ec",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "celo",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2635,10 +2635,10 @@
|
||||
"baseSymbol": "cWAVAX",
|
||||
"quoteSymbol": "USDC",
|
||||
"poolAddress": "0xd05200000000000000000000000000000000a86a",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "avax",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2650,10 +2650,10 @@
|
||||
"baseSymbol": "cWAVAX",
|
||||
"quoteSymbol": "WAVAX",
|
||||
"poolAddress": "0xd05100000000000000000000000000000000a86a",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "avax",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2720,10 +2720,10 @@
|
||||
"baseSymbol": "cWBNB",
|
||||
"quoteSymbol": "USDT",
|
||||
"poolAddress": "0xd032000000000000000000000000000000000038",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "bnb",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2735,10 +2735,10 @@
|
||||
"baseSymbol": "cWBNB",
|
||||
"quoteSymbol": "WBNB",
|
||||
"poolAddress": "0xd031000000000000000000000000000000000038",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "bnb",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2816,10 +2816,10 @@
|
||||
"baseSymbol": "cWETHL2",
|
||||
"quoteSymbol": "USDC",
|
||||
"poolAddress": "0xd022000000000000000000000000000000002105",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_l2",
|
||||
"venue": "dodo_pmm",
|
||||
@@ -2831,10 +2831,10 @@
|
||||
"baseSymbol": "cWETHL2",
|
||||
"quoteSymbol": "WETH",
|
||||
"poolAddress": "0xd021000000000000000000000000000000002105",
|
||||
"active": true,
|
||||
"routingEnabled": true,
|
||||
"mcpVisible": true,
|
||||
"phase": "wave1",
|
||||
"active": false,
|
||||
"routingEnabled": false,
|
||||
"mcpVisible": false,
|
||||
"phase": "wave1-staged",
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_l2",
|
||||
"venue": "dodo_pmm",
|
||||
|
||||
@@ -1936,7 +1936,7 @@
|
||||
"key": "Compliant_WEMIX_cW",
|
||||
"name": "cWEMIX->cWWEMIX",
|
||||
"addressFrom": "0x4d82206bec5b4dfa17759ffede07e35f4f63a050",
|
||||
"addressTo": "0xc111000000000000000000000000000000000457",
|
||||
"addressTo": "0x4c38f9a5ed68a04cd28a72e8c68c459ec34576f3",
|
||||
"notes": "Wave 1 gas-family lane wemix: Chain 138 cWEMIX -> Wemix cWWEMIX. hybrid_cap backing with uniswap_v3 reference pricing and DODO PMM edge liquidity."
|
||||
}
|
||||
]
|
||||
|
||||
217
docs/04-configuration/DEVIN_GITEA_PROXMOX_CICD.md
Normal file
217
docs/04-configuration/DEVIN_GITEA_PROXMOX_CICD.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# Devin → Gitea → Proxmox CI/CD
|
||||
|
||||
**Status:** Working baseline for this repo
|
||||
**Last Updated:** 2026-04-20
|
||||
|
||||
## Goal
|
||||
|
||||
Create a repeatable path where:
|
||||
|
||||
1. Devin lands code in Gitea.
|
||||
2. Gitea Actions validates the repo on the site-wide `act_runner`.
|
||||
3. A successful workflow calls `phoenix-deploy-api`.
|
||||
4. `phoenix-deploy-api` resolves the repo/branch to a deploy target and runs the matching Proxmox publish command.
|
||||
5. The deploy service checks the target health URL before it reports success.
|
||||
|
||||
## Current baseline in this repo
|
||||
|
||||
The path now exists for **`d-bis/proxmox`** on **`main`** and **`master`**:
|
||||
|
||||
- Canonical workflow sources: [.gitea/workflow-sources/deploy-to-phoenix.yml](/home/intlc/projects/proxmox/.gitea/workflow-sources/deploy-to-phoenix.yml) and [.gitea/workflow-sources/validate-on-pr.yml](/home/intlc/projects/proxmox/.gitea/workflow-sources/validate-on-pr.yml)
|
||||
- Workflow: [deploy-to-phoenix.yml](/home/intlc/projects/proxmox/.gitea/workflows/deploy-to-phoenix.yml)
|
||||
- Manual app workflow: [deploy-portal-live.yml](/home/intlc/projects/proxmox/.gitea/workflows/deploy-portal-live.yml)
|
||||
- Deploy service: [server.js](/home/intlc/projects/proxmox/phoenix-deploy-api/server.js)
|
||||
- Target map: [deploy-targets.json](/home/intlc/projects/proxmox/phoenix-deploy-api/deploy-targets.json)
|
||||
- Current live publish script: [deploy-phoenix-deploy-api-to-dev-vm.sh](/home/intlc/projects/proxmox/scripts/deployment/deploy-phoenix-deploy-api-to-dev-vm.sh)
|
||||
- Manual smoke trigger: [trigger-phoenix-deploy.sh](/home/intlc/projects/proxmox/scripts/dev-vm/trigger-phoenix-deploy.sh)
|
||||
- Target validator: [validate-phoenix-deploy-targets.sh](/home/intlc/projects/proxmox/scripts/validation/validate-phoenix-deploy-targets.sh)
|
||||
- Bootstrap helper: [bootstrap-phoenix-cicd.sh](/home/intlc/projects/proxmox/scripts/dev-vm/bootstrap-phoenix-cicd.sh)
|
||||
|
||||
That default target publishes the `phoenix-deploy-api` bundle to **VMID 5700** on the correct Proxmox node and starts the CT if needed.
|
||||
|
||||
A second target is now available:
|
||||
|
||||
- `portal-live` → runs [sync-sankofa-portal-7801.sh](/home/intlc/projects/proxmox/scripts/deployment/sync-sankofa-portal-7801.sh) and then checks `http://192.168.11.51:3000/`
|
||||
|
||||
## Workflow lockstep
|
||||
|
||||
Because both `main` and `master` can trigger deploys, deploy behavior is now defined from canonical source files and checked for branch parity.
|
||||
|
||||
- Edit only the source files under [.gitea/workflow-sources](/home/intlc/projects/proxmox/.gitea/workflow-sources:1)
|
||||
- Sync the checked-in workflow copies with:
|
||||
|
||||
```bash
|
||||
bash scripts/verify/sync-gitea-workflows.sh
|
||||
```
|
||||
|
||||
- Validate source sync plus `main`/`master` parity with:
|
||||
|
||||
```bash
|
||||
bash scripts/verify/run-all-validation.sh --skip-genesis
|
||||
```
|
||||
|
||||
The deploy and PR workflows both fetch `origin/main` and `origin/master` before validation, so branch drift now fails CI instead of silently changing deploy behavior.
|
||||
|
||||
## Flow
|
||||
|
||||
```text
|
||||
Devin
|
||||
-> push to Gitea
|
||||
-> Gitea Actions on act_runner (5700)
|
||||
-> bash scripts/verify/run-all-validation.sh --skip-genesis
|
||||
-> validates deploy-targets.json structure
|
||||
-> POST /api/deploy to phoenix-deploy-api
|
||||
-> match repo + branch + target in deploy-targets.json
|
||||
-> run deploy command
|
||||
-> verify target health URL
|
||||
-> update Gitea commit status success/failure
|
||||
```
|
||||
|
||||
## Required setup
|
||||
|
||||
### 1. Runner
|
||||
|
||||
Bring up the site-wide Gitea runner on VMID **5700**:
|
||||
|
||||
```bash
|
||||
bash scripts/dev-vm/bootstrap-gitea-act-runner-site-wide.sh
|
||||
```
|
||||
|
||||
Reference: [GITEA_ACT_RUNNER_SETUP.md](GITEA_ACT_RUNNER_SETUP.md)
|
||||
|
||||
### 0. One-command bootstrap
|
||||
|
||||
If root `.env` already contains the needed values, use:
|
||||
|
||||
```bash
|
||||
bash scripts/dev-vm/bootstrap-phoenix-cicd.sh --repo d-bis/proxmox
|
||||
```
|
||||
|
||||
This runs the validation gate, deploys `phoenix-deploy-api`, and smoke-checks the service.
|
||||
|
||||
### 2. Deploy API service
|
||||
|
||||
Deploy the API to the dev VM:
|
||||
|
||||
```bash
|
||||
./scripts/deployment/deploy-phoenix-deploy-api-to-dev-vm.sh --dry-run
|
||||
./scripts/deployment/deploy-phoenix-deploy-api-to-dev-vm.sh --apply --start-ct
|
||||
```
|
||||
|
||||
On the target VM, set at least:
|
||||
|
||||
```bash
|
||||
PORT=4001
|
||||
GITEA_URL=https://gitea.d-bis.org
|
||||
GITEA_TOKEN=<token with repo status access>
|
||||
PHOENIX_DEPLOY_SECRET=<shared secret>
|
||||
PHOENIX_REPO_ROOT=/home/intlc/projects/proxmox
|
||||
```
|
||||
|
||||
Optional:
|
||||
|
||||
```bash
|
||||
DEPLOY_TARGETS_PATH=/opt/phoenix-deploy-api/deploy-targets.json
|
||||
```
|
||||
|
||||
For the `portal-live` target, also set:
|
||||
|
||||
```bash
|
||||
SANKOFA_PORTAL_SRC=/home/intlc/projects/Sankofa/portal
|
||||
```
|
||||
|
||||
### 3. Gitea repo secrets
|
||||
|
||||
Set these in the Gitea repository that should deploy:
|
||||
|
||||
- `PHOENIX_DEPLOY_URL`
|
||||
- `PHOENIX_DEPLOY_TOKEN`
|
||||
|
||||
Example:
|
||||
|
||||
- `PHOENIX_DEPLOY_URL=http://192.168.11.59:4001/api/deploy`
|
||||
- `PHOENIX_DEPLOY_TOKEN=<same value as PHOENIX_DEPLOY_SECRET>`
|
||||
|
||||
For webhook signing, the bootstrap/helper path also expects:
|
||||
|
||||
- `PHOENIX_DEPLOY_SECRET`
|
||||
- `PHOENIX_WEBHOOK_DEPLOY_ENABLED=1` only if you want webhook events themselves to execute deploys
|
||||
|
||||
Do not enable both repo Actions deploys and webhook deploys for the same repo unless you intentionally want duplicate deploy attempts.
|
||||
|
||||
## Adding more repos or VM targets
|
||||
|
||||
Extend [deploy-targets.json](/home/intlc/projects/proxmox/phoenix-deploy-api/deploy-targets.json) with another entry.
|
||||
|
||||
Each target is keyed by:
|
||||
|
||||
- `repo`
|
||||
- `branch`
|
||||
- `target`
|
||||
|
||||
Each target defines:
|
||||
|
||||
- `cwd`
|
||||
- `command`
|
||||
- `required_env`
|
||||
- optional `healthcheck`
|
||||
- optional `timeout_sec`
|
||||
|
||||
Example shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"repo": "d-bis/another-service",
|
||||
"branch": "main",
|
||||
"target": "portal-live",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": ["bash", "scripts/deployment/sync-sankofa-portal-7801.sh"],
|
||||
"required_env": ["PHOENIX_REPO_ROOT"]
|
||||
}
|
||||
```
|
||||
|
||||
Use separate `target` names when the same repo can publish to different VMIDs or environments.
|
||||
|
||||
Target-map validation is already part of:
|
||||
|
||||
```bash
|
||||
bash scripts/verify/run-all-validation.sh --skip-genesis
|
||||
```
|
||||
|
||||
and can also be run directly:
|
||||
|
||||
```bash
|
||||
bash scripts/validation/validate-phoenix-deploy-targets.sh
|
||||
```
|
||||
|
||||
## Manual testing
|
||||
|
||||
Before trusting a new Gitea workflow, trigger the deploy service directly:
|
||||
|
||||
```bash
|
||||
bash scripts/dev-vm/trigger-phoenix-deploy.sh
|
||||
```
|
||||
|
||||
Trigger the live portal deployment target directly:
|
||||
|
||||
```bash
|
||||
bash scripts/dev-vm/trigger-phoenix-deploy.sh d-bis/proxmox main portal-live
|
||||
```
|
||||
|
||||
Inspect configured targets:
|
||||
|
||||
```bash
|
||||
curl -s http://192.168.11.59:4001/api/deploy-targets | jq .
|
||||
```
|
||||
|
||||
## Recommended next expansions
|
||||
|
||||
- Add a Phoenix API target for the repo that owns VMID **7800** or **8600**, depending on which deployment line is canonical.
|
||||
- Add repo-specific workflows once the Sankofa source repos themselves are mirrored into Gitea Actions.
|
||||
- Move secret values from ad hoc `.env` files into the final operator-managed secret source once you settle the production host for `phoenix-deploy-api`.
|
||||
|
||||
## Notes
|
||||
|
||||
- The Gitea workflow is gated by `scripts/verify/run-all-validation.sh --skip-genesis` before deploy.
|
||||
- `phoenix-deploy-api` now returns `404` when no matching target exists and `500` when the deploy command fails.
|
||||
- Commit status updates are written back to Gitea from the deploy service itself.
|
||||
@@ -1,34 +1,18 @@
|
||||
# Token Lists E2E Verification Report
|
||||
|
||||
**Date:** 2026-04-14
|
||||
**Status:** Repo validation current; live E2E still depends on explorer VM / NPM
|
||||
**Date:** 2026-02-28
|
||||
**Status:** Verification complete
|
||||
|
||||
---
|
||||
|
||||
## 1. Token List Validation (repo)
|
||||
## 1. Token List Validation
|
||||
|
||||
| List | Status | Tokens | Notes |
|
||||
|------|--------|--------|------|
|
||||
| `token-lists/lists/dbis-138.tokenlist.json` | ✅ Schema + checksums + logos + on-chain verify | **29** | Uniswap Token Lists schema; EIP-55; `verify-on-chain.js` against public Chain 138 RPC |
|
||||
| `token-lists/lists/cronos.tokenlist.json` | ✅ `validate-token-list.js` | **11** | Tag **`gasnative`** (schema-safe) |
|
||||
| `token-lists/lists/avalanche.tokenlist.json` | ✅ | **3** | |
|
||||
| `token-lists/lists/arbitrum.tokenlist.json` | ✅ | **3** | |
|
||||
| `token-lists/lists/ethereum-mainnet.tokenlist.json` | ✅ | **17** | |
|
||||
| `token-lists/lists/all-mainnet.tokenlist.json` | ✅ | **9** | |
|
||||
| `explorer-monorepo/.../DUAL_CHAIN_TOKEN_LIST.tokenlist.json` (canonical) | Not run in CI as strict Uniswap schema | **190** | Same bytes: `backend/config/metamask/`, `backend/api/rest/config/metamask/`, `metamask-integration/provider/config/` |
|
||||
| `docs/04-configuration/metamask/DUAL_CHAIN_TOKEN_LIST.tokenlist.json` | Mirror | **190** | **Synced from** explorer `backend/config/metamask/` copy (this commit) |
|
||||
|
||||
Run locally:
|
||||
|
||||
```bash
|
||||
cd token-lists
|
||||
for f in lists/dbis-138.tokenlist.json lists/cronos.tokenlist.json lists/avalanche.tokenlist.json lists/arbitrum.tokenlist.json lists/ethereum-mainnet.tokenlist.json lists/all-mainnet.tokenlist.json; do
|
||||
node scripts/validate-token-list.js "$f" || exit 1
|
||||
done
|
||||
node scripts/checksum-addresses.js lists/dbis-138.tokenlist.json
|
||||
node scripts/validate-logos.js lists/dbis-138.tokenlist.json
|
||||
node scripts/verify-on-chain.js lists/dbis-138.tokenlist.json
|
||||
```
|
||||
|------|--------|--------|-------|
|
||||
| dbis-138.tokenlist.json | ✅ Valid | 6 | All IPFS logos, EIP-55 checksummed |
|
||||
| cronos.tokenlist.json | ✅ Valid | 10 | All IPFS logos, ISO-4217 W tokens |
|
||||
| all-mainnet.tokenlist.json | ✅ Valid | 9 | All 9 tokens have logoURI (IPFS) |
|
||||
| DUAL_CHAIN_TOKEN_LIST | ✅ Valid | 22 | Chains 138, 1, 25, 651940 |
|
||||
|
||||
---
|
||||
|
||||
@@ -36,15 +20,17 @@ node scripts/verify-on-chain.js lists/dbis-138.tokenlist.json
|
||||
|
||||
| Check | Result |
|
||||
|-------|--------|
|
||||
| Chain 138 addresses (`dbis-138` vs `DUAL_CHAIN` 138 subset) | ✅ Align with [EXPLORER_TOKEN_LIST_CROSSCHECK.md](../11-references/EXPLORER_TOKEN_LIST_CROSSCHECK.md) §5 |
|
||||
| `docs/.../metamask/DUAL_CHAIN_TOKEN_LIST.tokenlist.json` | ✅ Byte-synced to explorer canonical path |
|
||||
| Chain 138 addresses (dbis-138 vs DUAL_CHAIN) | ✅ Consistent |
|
||||
| Cronos addresses (cronos vs DUAL_CHAIN) | ✅ Consistent |
|
||||
| DUAL_CHAIN synced to backend/config, docs, metamask | ✅ Done |
|
||||
|
||||
---
|
||||
|
||||
## 3. IPFS manifest / logos
|
||||
## 3. IPFS Manifest
|
||||
|
||||
- **dbis-138:** Mix of HTTPS (explorer icons, TrustWallet) and IPFS `logoURI` where applicable; `validate-logos.js` passes.
|
||||
- **DUAL_CHAIN:** Large multichain list; logo coverage varies by chain (external URLs on mainnet staples are normal).
|
||||
- **addressToUrl:** 14 of 22 DUAL_CHAIN addresses covered
|
||||
- **Missing:** Chain 1 (USDT, USDC, DAI, ETH-USD, WETH) and Chain 651940 USDC, Cronos WETH9/LINK — these use external logo URLs in token lists (TrustWallet, ethereum.org)
|
||||
- **Our tokens (138, 25):** All covered
|
||||
|
||||
---
|
||||
|
||||
@@ -52,46 +38,57 @@ node scripts/verify-on-chain.js lists/dbis-138.tokenlist.json
|
||||
|
||||
| Check | Result |
|
||||
|-------|--------|
|
||||
| **Source of truth in repo** | `explorer-monorepo/backend/config/metamask/DUAL_CHAIN_TOKEN_LIST.tokenlist.json` |
|
||||
| **Public URL** | **2026-04-14:** Live probe returned **190** tokens from `https://explorer.d-bis.org/api/config/token-list`. If you see **400**, fix nginx/deploy per explorer runbooks (VMID **5000** or current explorer CT). |
|
||||
| Public URL | ❌ Returns 400 (Blockscout error) |
|
||||
| Cause | Nginx config not yet applied on VMID 5000 |
|
||||
| Fix | Run on Proxmox host: |
|
||||
| | 1. `fix-nginx-conflicts-vmid5000.sh` (inside VMID 5000) |
|
||||
| | 2. `deploy-explorer-config-to-vmid5000.sh` (from repo root, with pct or EXEC_MODE=ssh) |
|
||||
|
||||
---
|
||||
|
||||
## 5. Deploy script
|
||||
## 5. Deploy Script
|
||||
|
||||
| Check | Result |
|
||||
|-------|--------|
|
||||
| Config source path | ✅ `explorer-monorepo/scripts/deploy-explorer-config-to-vmid5000.sh` (paths may vary by deployment) |
|
||||
| `pct` / `ssh` | Operator LAN; see explorer deployment docs |
|
||||
| Config source path | ✅ Correct |
|
||||
| pct mode | Requires Proxmox host |
|
||||
| ssh mode | `EXEC_MODE=ssh EXPLORER_IP=192.168.11.140 ./explorer-monorepo/scripts/deploy-explorer-config-to-vmid5000.sh` |
|
||||
|
||||
---
|
||||
|
||||
## 6. Nginx location order (reference)
|
||||
## 6. Nginx Location Order
|
||||
|
||||
Exact-match `/api/config/token-list` should win over generic `/api/` Blockscout proxy. Confirm on live nginx for the explorer host.
|
||||
```
|
||||
location = /api/config/token-list # Exact match (highest priority)
|
||||
location = /api/config/networks
|
||||
location /api/v1/ # Token-aggregation
|
||||
location /api/ # Blockscout (fallback)
|
||||
```
|
||||
|
||||
✅ Correct — `/api/config/*` takes precedence over `/api/`
|
||||
|
||||
---
|
||||
|
||||
## 7. Frontend `tokenIcons.ts`
|
||||
## 7. Frontend tokenIcons.ts
|
||||
|
||||
Reconcile icon map counts with **§5** + DUAL 138 entries whenever tokens change (see explorer frontend paths).
|
||||
- Chain 138: 6 addresses ✅
|
||||
- Cronos WETH9, LINK, ISO-4217 W tokens: 9 addresses ✅
|
||||
|
||||
---
|
||||
|
||||
## 8. E2E checklist (operator)
|
||||
## 8. E2E Checklist (Operator)
|
||||
|
||||
1. [ ] Deploy/sync explorer config when you change **`explorer-monorepo/.../DUAL_CHAIN_TOKEN_LIST.tokenlist.json`** (so live matches git).
|
||||
2. [x] **2026-04-14:** `curl -sS 'https://explorer.d-bis.org/api/config/token-list' | jq '.tokens | length'` → **190** (live matches current canonical DUAL size).
|
||||
3. [ ] Optional: publish **`token-lists/lists/dbis-138.tokenlist.json`** to public list URL (O-5) and refresh **minisign** signature if your release process signs lists (`token-lists/scripts/sign-list.sh`; requires private key / CI secret).
|
||||
To achieve full E2E:
|
||||
|
||||
1. [ ] SSH to Proxmox host (or VMID 5000)
|
||||
2. [ ] Run `fix-nginx-conflicts-vmid5000.sh` inside VMID 5000
|
||||
3. [ ] Run `deploy-explorer-config-to-vmid5000.sh` from repo root (pct or ssh)
|
||||
4. [ ] Verify: `curl -s https://explorer.d-bis.org/api/config/token-list | jq '.tokens | length'` → 22
|
||||
5. [ ] Verify: `curl -s https://explorer.d-bis.org/api/config/networks | jq '.chains | length'` → 4
|
||||
|
||||
---
|
||||
|
||||
## 9. Release signing (dbis-138)
|
||||
## 9. Completed Improvements
|
||||
|
||||
- **`dbis-138.tokenlist.json.sig`** is not committed by default; generate with `bash token-lists/scripts/sign-list.sh sign` when **`MINISIGN_PRIVATE_KEY`** or **`minisign.key`** is available (see `release.yml`).
|
||||
|
||||
---
|
||||
|
||||
## 10. Historical note
|
||||
|
||||
Older completion markdown under `token-lists/` may cite legacy token counts; canonical inventory is **`EXPLORER_TOKEN_LIST_CROSSCHECK.md` §5** and **`lists/dbis-138.tokenlist.json`**.
|
||||
- ✅ all-mainnet: logoURI added for HYDX, HYBX, CHT, AUDA
|
||||
- ✅ tokenIcons.ts: Cronos WETH9, LINK added to TOKEN_ICON_BY_ADDRESS
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
"version": {
|
||||
"major": 1,
|
||||
"minor": 3,
|
||||
"patch": 4
|
||||
"patch": 5
|
||||
},
|
||||
"timestamp": "2026-04-04T04:23:46.263Z",
|
||||
"timestamp": "2026-04-05T23:30:00.000Z",
|
||||
"logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/chain138-list.svg",
|
||||
"keywords": [
|
||||
"chain138",
|
||||
@@ -46,6 +46,10 @@
|
||||
"iso4217w": {
|
||||
"name": "ISO4217W",
|
||||
"description": "ISO 4217 compliant wrapped fiat tokens"
|
||||
},
|
||||
"gas-native": {
|
||||
"name": "Gas Native",
|
||||
"description": "Gas-native compliant families and wrapped gas mirrors"
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
@@ -2560,6 +2564,510 @@
|
||||
"defi",
|
||||
"bridge"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xf8e9802a1766422e33a269556ad5fc032338eefc",
|
||||
"name": "Ethereum Mainnet Gas (Compliant)",
|
||||
"symbol": "cETH",
|
||||
"familyKey": "eth_mainnet",
|
||||
"mirroredSymbol": "cWETH",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
|
||||
"backingMode": "strict_escrow",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_mainnet",
|
||||
"mirroredSymbol": "cWETH",
|
||||
"backingMode": "strict_escrow",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x18a6b163d255cc0cb32b99697843b487d059907d",
|
||||
"name": "Ethereum L2 Gas (Compliant)",
|
||||
"symbol": "cETHL2",
|
||||
"familyKey": "eth_l2",
|
||||
"mirroredSymbol": "cWETHL2",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_fungible_inventory_gated",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_l2",
|
||||
"mirroredSymbol": "cWETHL2",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_fungible_inventory_gated",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x94517875f32680984a5921da7abf78e22f65d70f",
|
||||
"name": "BNB Gas (Compliant)",
|
||||
"symbol": "cBNB",
|
||||
"familyKey": "bnb",
|
||||
"mirroredSymbol": "cWBNB",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/smartchain/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "bnb",
|
||||
"mirroredSymbol": "cWBNB",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x19fec21edede9edb91d1fbec7c4095c5ac1fc10c",
|
||||
"name": "Polygon Gas (Compliant)",
|
||||
"symbol": "cPOL",
|
||||
"familyKey": "pol",
|
||||
"mirroredSymbol": "cWPOL",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "pol",
|
||||
"mirroredSymbol": "cWPOL",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x9c8f6f4fe1e535e6f85a78ee95249ded5540a2d0",
|
||||
"name": "Avalanche Gas (Compliant)",
|
||||
"symbol": "cAVAX",
|
||||
"familyKey": "avax",
|
||||
"mirroredSymbol": "cWAVAX",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/avalanchec/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "avax",
|
||||
"mirroredSymbol": "cWAVAX",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xfb48bb6c623571f9795b535b73f0d660e8643fd4",
|
||||
"name": "Cronos Gas (Compliant)",
|
||||
"symbol": "cCRO",
|
||||
"familyKey": "cro",
|
||||
"mirroredSymbol": "cWCRO",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/cronos/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "cro",
|
||||
"mirroredSymbol": "cWCRO",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xa39a59138f8a45b4a31455875b8a29ff31e76ec8",
|
||||
"name": "Gnosis Gas (Compliant)",
|
||||
"symbol": "cXDAI",
|
||||
"familyKey": "xdai",
|
||||
"mirroredSymbol": "cWXDAI",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/xdai/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "xdai",
|
||||
"mirroredSymbol": "cWXDAI",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x1814661dae0933bf397b1274f5a62411b94195c7",
|
||||
"name": "Celo Gas (Compliant)",
|
||||
"symbol": "cCELO",
|
||||
"familyKey": "celo",
|
||||
"mirroredSymbol": "cWCELO",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/celo/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "celo",
|
||||
"mirroredSymbol": "cWCELO",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x4d82206bec5b4dfa17759ffede07e35f4f63a050",
|
||||
"name": "Wemix Gas (Compliant)",
|
||||
"symbol": "cWEMIX",
|
||||
"familyKey": "wemix",
|
||||
"mirroredSymbol": "cWWEMIX",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/wemix/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "wemix",
|
||||
"mirroredSymbol": "cWWEMIX",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 1,
|
||||
"address": "0xf6dc5587e18f27adff60e303fdd98f35b50fa8a5",
|
||||
"name": "Wrapped Ethereum Mainnet Gas (Compliant)",
|
||||
"symbol": "cWETH",
|
||||
"familyKey": "eth_mainnet",
|
||||
"canonicalSymbol": "cETH",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
|
||||
"backingMode": "strict_escrow",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_mainnet",
|
||||
"canonicalSymbol": "cETH",
|
||||
"backingMode": "strict_escrow",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 10,
|
||||
"address": "0x95007ec50d0766162f77848edf7bdc4eba147fb4",
|
||||
"name": "Wrapped Ethereum L2 Gas (Compliant)",
|
||||
"symbol": "cWETHL2",
|
||||
"familyKey": "eth_l2",
|
||||
"canonicalSymbol": "cETHL2",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_fungible_inventory_gated",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_l2",
|
||||
"canonicalSymbol": "cETHL2",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_fungible_inventory_gated",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 25,
|
||||
"address": "0x9b10eb0f77c45322dbd1fcb07176fd9a7609c164",
|
||||
"name": "Wrapped CRO Gas (Compliant)",
|
||||
"symbol": "cWCRO",
|
||||
"familyKey": "cro",
|
||||
"canonicalSymbol": "cCRO",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/cronos/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "cro",
|
||||
"canonicalSymbol": "cCRO",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 56,
|
||||
"address": "0x179034a08ac2c9c35d2e41239f68c79dca6f18fa",
|
||||
"name": "Wrapped BNB Gas (Compliant)",
|
||||
"symbol": "cWBNB",
|
||||
"familyKey": "bnb",
|
||||
"canonicalSymbol": "cBNB",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/smartchain/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "bnb",
|
||||
"canonicalSymbol": "cBNB",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 100,
|
||||
"address": "0x9f833b4f1012f52eb3317b09922a79c6edfca77d",
|
||||
"name": "Wrapped XDAI Gas (Compliant)",
|
||||
"symbol": "cWXDAI",
|
||||
"familyKey": "xdai",
|
||||
"canonicalSymbol": "cXDAI",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/xdai/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "xdai",
|
||||
"canonicalSymbol": "cXDAI",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 137,
|
||||
"address": "0x25980244aacecb6d8c4b887261ed27f87cb2fc73",
|
||||
"name": "Wrapped POL Gas (Compliant)",
|
||||
"symbol": "cWPOL",
|
||||
"familyKey": "pol",
|
||||
"canonicalSymbol": "cPOL",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "pol",
|
||||
"canonicalSymbol": "cPOL",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 42161,
|
||||
"address": "0xe27be001bc55cb2a8ed5ba5a62c834ca135244a3",
|
||||
"name": "Wrapped Ethereum L2 Gas (Compliant)",
|
||||
"symbol": "cWETHL2",
|
||||
"familyKey": "eth_l2",
|
||||
"canonicalSymbol": "cETHL2",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_fungible_inventory_gated",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_l2",
|
||||
"canonicalSymbol": "cETHL2",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_fungible_inventory_gated",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 42220,
|
||||
"address": "0xb0fa7ec4123c7c275b3a89d9239569707ea3c66a",
|
||||
"name": "Wrapped CELO Gas (Compliant)",
|
||||
"symbol": "cWCELO",
|
||||
"familyKey": "celo",
|
||||
"canonicalSymbol": "cCELO",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/celo/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "celo",
|
||||
"canonicalSymbol": "cCELO",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 43114,
|
||||
"address": "0xe1d4aee2ef8f48a20338935188a8fe7f7c7de7d0",
|
||||
"name": "Wrapped AVAX Gas (Compliant)",
|
||||
"symbol": "cWAVAX",
|
||||
"familyKey": "avax",
|
||||
"canonicalSymbol": "cAVAX",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/avalanchec/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "avax",
|
||||
"canonicalSymbol": "cAVAX",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 8453,
|
||||
"address": "0x2a0840e5117683b11682ac46f5cf5621e67269e3",
|
||||
"name": "Wrapped Ethereum L2 Gas (Compliant)",
|
||||
"symbol": "cWETHL2",
|
||||
"familyKey": "eth_l2",
|
||||
"canonicalSymbol": "cETHL2",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_fungible_inventory_gated",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_l2",
|
||||
"canonicalSymbol": "cETHL2",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_fungible_inventory_gated",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
247
phoenix-deploy-api/deploy-targets.json
Normal file
247
phoenix-deploy-api/deploy-targets.json
Normal file
@@ -0,0 +1,247 @@
|
||||
{
|
||||
"defaults": {
|
||||
"timeout_sec": 1800
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"repo": "d-bis/proxmox",
|
||||
"branch": "main",
|
||||
"target": "default",
|
||||
"description": "Install the Phoenix deploy API locally on the dev VM from the synced repo workspace.",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": [
|
||||
"bash",
|
||||
"phoenix-deploy-api/scripts/install-systemd.sh"
|
||||
],
|
||||
"required_env": [
|
||||
"PHOENIX_REPO_ROOT"
|
||||
],
|
||||
"healthcheck": {
|
||||
"url": "http://192.168.11.59:4001/health",
|
||||
"expect_status": 200,
|
||||
"expect_body_includes": "phoenix-deploy-api",
|
||||
"attempts": 8,
|
||||
"delay_ms": 3000,
|
||||
"timeout_ms": 10000
|
||||
}
|
||||
},
|
||||
{
|
||||
"repo": "d-bis/proxmox",
|
||||
"branch": "main",
|
||||
"target": "cloudflare-sync",
|
||||
"description": "Optional: sync Cloudflare DNS from repo .env (path-gated; set PHOENIX_CLOUDFLARE_SYNC=1 on host).",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": [
|
||||
"bash",
|
||||
"scripts/deployment/gitea-cloudflare-sync.sh"
|
||||
],
|
||||
"required_env": [
|
||||
"PHOENIX_REPO_ROOT"
|
||||
],
|
||||
"timeout_sec": 600
|
||||
},
|
||||
{
|
||||
"repo": "d-bis/proxmox",
|
||||
"branch": "main",
|
||||
"target": "cloudflare-sync-force",
|
||||
"description": "Same as cloudflare-sync but skips path filter (operator / manual).",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": [
|
||||
"bash",
|
||||
"scripts/deployment/gitea-cloudflare-sync.sh"
|
||||
],
|
||||
"required_env": [
|
||||
"PHOENIX_REPO_ROOT"
|
||||
],
|
||||
"timeout_sec": 600
|
||||
},
|
||||
{
|
||||
"repo": "d-bis/proxmox",
|
||||
"branch": "main",
|
||||
"target": "portal-live",
|
||||
"description": "Deploy the Sankofa portal to CT 7801 on Proxmox.",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": [
|
||||
"bash",
|
||||
"scripts/deployment/sync-sankofa-portal-7801.sh"
|
||||
],
|
||||
"required_env": [
|
||||
"PHOENIX_REPO_ROOT",
|
||||
"SANKOFA_PORTAL_SRC"
|
||||
],
|
||||
"healthcheck": {
|
||||
"url": "http://192.168.11.51:3000/",
|
||||
"expect_status": 200,
|
||||
"expect_body_includes": "<html",
|
||||
"attempts": 10,
|
||||
"delay_ms": 5000,
|
||||
"timeout_ms": 10000
|
||||
}
|
||||
},
|
||||
{
|
||||
"repo": "d-bis/CurrenciCombo",
|
||||
"branch": "main",
|
||||
"target": "default",
|
||||
"description": "Deploy CurrenciCombo from the staged Gitea workspace into Phoenix CT 8604 and verify the public hostname end to end.",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": [
|
||||
"bash",
|
||||
"scripts/deployment/phoenix-deploy-currencicombo-from-workspace.sh"
|
||||
],
|
||||
"required_env": [
|
||||
"PHOENIX_REPO_ROOT",
|
||||
"PHOENIX_DEPLOY_WORKSPACE"
|
||||
],
|
||||
"healthcheck": {
|
||||
"url": "https://curucombo.xn--vov0g.com/api/ready",
|
||||
"expect_status": 200,
|
||||
"expect_body_includes": "\"ready\":true",
|
||||
"attempts": 12,
|
||||
"delay_ms": 5000,
|
||||
"timeout_ms": 15000
|
||||
}
|
||||
},
|
||||
{
|
||||
"repo": "d-bis/proxmox",
|
||||
"branch": "main",
|
||||
"target": "atomic-swap-dapp-live",
|
||||
"description": "Deploy the Atomic Swap dApp to VMID 5801 on Proxmox.",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": [
|
||||
"bash",
|
||||
"scripts/deployment/deploy-atomic-swap-dapp-5801.sh"
|
||||
],
|
||||
"required_env": [
|
||||
"PHOENIX_REPO_ROOT"
|
||||
],
|
||||
"healthcheck": {
|
||||
"url": "https://atomic-swap.defi-oracle.io/data/live-route-registry.json",
|
||||
"expect_status": 200,
|
||||
"expect_body_includes": "\"liveBridgeRoutes\"",
|
||||
"attempts": 10,
|
||||
"delay_ms": 5000,
|
||||
"timeout_ms": 15000
|
||||
}
|
||||
},
|
||||
{
|
||||
"repo": "d-bis/proxmox",
|
||||
"branch": "master",
|
||||
"target": "default",
|
||||
"description": "Install the Phoenix deploy API locally on the dev VM from the synced repo workspace.",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": [
|
||||
"bash",
|
||||
"phoenix-deploy-api/scripts/install-systemd.sh"
|
||||
],
|
||||
"required_env": [
|
||||
"PHOENIX_REPO_ROOT"
|
||||
],
|
||||
"healthcheck": {
|
||||
"url": "http://192.168.11.59:4001/health",
|
||||
"expect_status": 200,
|
||||
"expect_body_includes": "phoenix-deploy-api",
|
||||
"attempts": 8,
|
||||
"delay_ms": 3000,
|
||||
"timeout_ms": 10000
|
||||
}
|
||||
},
|
||||
{
|
||||
"repo": "d-bis/proxmox",
|
||||
"branch": "master",
|
||||
"target": "atomic-swap-dapp-live",
|
||||
"description": "Deploy the Atomic Swap dApp to VMID 5801 on Proxmox.",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": [
|
||||
"bash",
|
||||
"scripts/deployment/deploy-atomic-swap-dapp-5801.sh"
|
||||
],
|
||||
"required_env": [
|
||||
"PHOENIX_REPO_ROOT"
|
||||
],
|
||||
"healthcheck": {
|
||||
"url": "https://atomic-swap.defi-oracle.io/data/live-route-registry.json",
|
||||
"expect_status": 200,
|
||||
"expect_body_includes": "\"liveBridgeRoutes\"",
|
||||
"attempts": 10,
|
||||
"delay_ms": 5000,
|
||||
"timeout_ms": 15000
|
||||
}
|
||||
},
|
||||
{
|
||||
"repo": "d-bis/proxmox",
|
||||
"branch": "master",
|
||||
"target": "cloudflare-sync",
|
||||
"description": "Optional: sync Cloudflare DNS from repo .env (path-gated; set PHOENIX_CLOUDFLARE_SYNC=1 on host).",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": [
|
||||
"bash",
|
||||
"scripts/deployment/gitea-cloudflare-sync.sh"
|
||||
],
|
||||
"required_env": [
|
||||
"PHOENIX_REPO_ROOT"
|
||||
],
|
||||
"timeout_sec": 600
|
||||
},
|
||||
{
|
||||
"repo": "d-bis/proxmox",
|
||||
"branch": "master",
|
||||
"target": "cloudflare-sync-force",
|
||||
"description": "Same as cloudflare-sync but skips path filter (operator / manual).",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": [
|
||||
"bash",
|
||||
"scripts/deployment/gitea-cloudflare-sync.sh"
|
||||
],
|
||||
"required_env": [
|
||||
"PHOENIX_REPO_ROOT"
|
||||
],
|
||||
"timeout_sec": 600
|
||||
},
|
||||
{
|
||||
"repo": "d-bis/proxmox",
|
||||
"branch": "master",
|
||||
"target": "portal-live",
|
||||
"description": "Deploy the Sankofa portal to CT 7801 on Proxmox.",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": [
|
||||
"bash",
|
||||
"scripts/deployment/sync-sankofa-portal-7801.sh"
|
||||
],
|
||||
"required_env": [
|
||||
"PHOENIX_REPO_ROOT",
|
||||
"SANKOFA_PORTAL_SRC"
|
||||
],
|
||||
"healthcheck": {
|
||||
"url": "http://192.168.11.51:3000/",
|
||||
"expect_status": 200,
|
||||
"expect_body_includes": "<html",
|
||||
"attempts": 10,
|
||||
"delay_ms": 5000,
|
||||
"timeout_ms": 10000
|
||||
}
|
||||
},
|
||||
{
|
||||
"repo": "d-bis/CurrenciCombo",
|
||||
"branch": "master",
|
||||
"target": "default",
|
||||
"description": "Deploy CurrenciCombo from the staged Gitea workspace into Phoenix CT 8604 and verify the public hostname end to end.",
|
||||
"cwd": "${PHOENIX_REPO_ROOT}",
|
||||
"command": [
|
||||
"bash",
|
||||
"scripts/deployment/phoenix-deploy-currencicombo-from-workspace.sh"
|
||||
],
|
||||
"required_env": [
|
||||
"PHOENIX_REPO_ROOT",
|
||||
"PHOENIX_DEPLOY_WORKSPACE"
|
||||
],
|
||||
"healthcheck": {
|
||||
"url": "https://curucombo.xn--vov0g.com/api/ready",
|
||||
"expect_status": 200,
|
||||
"expect_body_includes": "\"ready\":true",
|
||||
"attempts": 12,
|
||||
"delay_ms": 5000,
|
||||
"timeout_ms": 15000
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -25,7 +25,70 @@ if [[ -f "$REPO_ROOT/config/public-sector-program-manifest.json" ]]; then
|
||||
else
|
||||
echo "WARN: $REPO_ROOT/config/public-sector-program-manifest.json missing — set PUBLIC_SECTOR_MANIFEST_PATH in .env"
|
||||
fi
|
||||
[ -f "$APP_DIR/.env" ] && cp "$APP_DIR/.env" "$TARGET/.env" || [ -f "$APP_DIR/.env.example" ] && cp "$APP_DIR/.env.example" "$TARGET/.env" || true
|
||||
if [[ -f "$TARGET/.env" ]]; then
|
||||
echo "Preserving existing $TARGET/.env"
|
||||
elif [[ -f "$APP_DIR/.env" ]]; then
|
||||
cp "$APP_DIR/.env" "$TARGET/.env"
|
||||
elif [[ -f "$APP_DIR/.env.example" ]]; then
|
||||
cp "$APP_DIR/.env.example" "$TARGET/.env"
|
||||
fi
|
||||
|
||||
ensure_env_value() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local file="$TARGET/.env"
|
||||
[[ -n "$value" && -f "$file" ]] || return 0
|
||||
|
||||
local current=""
|
||||
if grep -qE "^${key}=" "$file"; then
|
||||
current="$(grep -E "^${key}=" "$file" | tail -n 1 | cut -d= -f2-)"
|
||||
fi
|
||||
[[ -z "$current" ]] || return 0
|
||||
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
awk -v key="$key" -v value="$value" '
|
||||
BEGIN { found = 0 }
|
||||
$0 ~ "^" key "=" {
|
||||
print key "=" value
|
||||
found = 1
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
END {
|
||||
if (!found) print key "=" value
|
||||
}
|
||||
' "$file" > "$tmp"
|
||||
cat "$tmp" > "$file"
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
repo_env_value() {
|
||||
local key="$1"
|
||||
local file="$REPO_ROOT/.env"
|
||||
[[ -f "$file" ]] || return 0
|
||||
grep -E "^${key}=" "$file" | tail -n 1 | cut -d= -f2-
|
||||
}
|
||||
|
||||
if [[ -f "$TARGET/.env" ]]; then
|
||||
ensure_env_value PHOENIX_REPO_ROOT "$REPO_ROOT"
|
||||
for key in \
|
||||
GITEA_TOKEN \
|
||||
PHOENIX_DEPLOY_SECRET \
|
||||
PROXMOX_HOST \
|
||||
PROXMOX_PORT \
|
||||
PROXMOX_USER \
|
||||
PROXMOX_TOKEN_NAME \
|
||||
PROXMOX_TOKEN_VALUE \
|
||||
PROXMOX_TLS_VERIFY \
|
||||
PUBLIC_IP \
|
||||
CLOUDFLARE_API_TOKEN \
|
||||
CLOUDFLARE_GITEA_SYNC_ZONE \
|
||||
PHOENIX_CLOUDFLARE_SYNC
|
||||
do
|
||||
ensure_env_value "$key" "$(repo_env_value "$key")"
|
||||
done
|
||||
fi
|
||||
chown -R root:root "$TARGET"
|
||||
cd "$TARGET" && npm install --omit=dev
|
||||
cp "$APP_DIR/phoenix-deploy-api.service" /etc/systemd/system/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Phoenix Deploy API — Gitea webhook receiver, deploy stub, and Phoenix API Railing (Infra/VE)
|
||||
* Phoenix Deploy API — Gitea webhook receiver, deploy execution API, and Phoenix API Railing (Infra/VE)
|
||||
*
|
||||
* Endpoints:
|
||||
* POST /webhook/gitea — Receives Gitea push/tag/PR webhooks
|
||||
@@ -19,7 +19,9 @@
|
||||
import crypto from 'crypto';
|
||||
import https from 'https';
|
||||
import path from 'path';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { execFile as execFileCallback } from 'child_process';
|
||||
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import express from 'express';
|
||||
|
||||
@@ -29,6 +31,13 @@ const PORT = parseInt(process.env.PORT || '4001', 10);
|
||||
const GITEA_URL = (process.env.GITEA_URL || 'https://gitea.d-bis.org').replace(/\/$/, '');
|
||||
const GITEA_TOKEN = process.env.GITEA_TOKEN || '';
|
||||
const WEBHOOK_SECRET = process.env.PHOENIX_DEPLOY_SECRET || '';
|
||||
const PHOENIX_REPO_ROOT_DEFAULT = (process.env.PHOENIX_REPO_ROOT_DEFAULT || '/srv/projects/proxmox').trim();
|
||||
const ATOMIC_SWAP_REPO = (process.env.PHOENIX_ATOMIC_SWAP_REPO || 'd-bis/atomic-swap-dapp').trim();
|
||||
const ATOMIC_SWAP_REF = (process.env.PHOENIX_ATOMIC_SWAP_REF || 'main').trim();
|
||||
const CROSS_CHAIN_PMM_LPS_REPO = (process.env.PHOENIX_CROSS_CHAIN_PMM_LPS_REPO || '').trim();
|
||||
const CROSS_CHAIN_PMM_LPS_REF = (process.env.PHOENIX_CROSS_CHAIN_PMM_LPS_REF || 'main').trim();
|
||||
const SMOM_DBIS_138_REPO = (process.env.PHOENIX_SMOM_DBIS_138_REPO || '').trim();
|
||||
const SMOM_DBIS_138_REF = (process.env.PHOENIX_SMOM_DBIS_138_REF || 'main').trim();
|
||||
|
||||
const PROXMOX_HOST = process.env.PROXMOX_HOST || '';
|
||||
const PROXMOX_PORT = parseInt(process.env.PROXMOX_PORT || '8006', 10);
|
||||
@@ -42,6 +51,17 @@ const PROMETHEUS_URL = (process.env.PROMETHEUS_URL || 'http://localhost:9090').r
|
||||
const PHOENIX_WEBHOOK_URL = process.env.PHOENIX_WEBHOOK_URL || '';
|
||||
const PHOENIX_WEBHOOK_SECRET = process.env.PHOENIX_WEBHOOK_SECRET || '';
|
||||
const PARTNER_KEYS = (process.env.PHOENIX_PARTNER_KEYS || '').split(',').map((k) => k.trim()).filter(Boolean);
|
||||
const WEBHOOK_DEPLOY_ENABLED = process.env.PHOENIX_WEBHOOK_DEPLOY_ENABLED === '1' || process.env.PHOENIX_WEBHOOK_DEPLOY_ENABLED === 'true';
|
||||
const execFile = promisify(execFileCallback);
|
||||
|
||||
function expandEnvTokens(value, env = process.env) {
|
||||
if (typeof value !== 'string') return value;
|
||||
return value.replace(/\$\{([A-Z0-9_]+)\}/gi, (_, key) => env[key] || '');
|
||||
}
|
||||
|
||||
function resolvePhoenixRepoRoot() {
|
||||
return (process.env.PHOENIX_REPO_ROOT || PHOENIX_REPO_ROOT_DEFAULT || '').trim().replace(/\/$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Manifest resolution order:
|
||||
@@ -63,15 +83,395 @@ function resolvePublicSectorManifestPath() {
|
||||
return path.join(__dirname, '..', 'config', 'public-sector-program-manifest.json');
|
||||
}
|
||||
|
||||
function resolveDeployTargetsPath() {
|
||||
const override = (process.env.DEPLOY_TARGETS_PATH || '').trim();
|
||||
if (override && existsSync(override)) return override;
|
||||
const bundled = path.join(__dirname, 'deploy-targets.json');
|
||||
if (existsSync(bundled)) return bundled;
|
||||
return bundled;
|
||||
}
|
||||
|
||||
function loadDeployTargetsConfig() {
|
||||
const configPath = resolveDeployTargetsPath();
|
||||
if (!existsSync(configPath)) {
|
||||
return {
|
||||
path: configPath,
|
||||
defaults: {},
|
||||
targets: [],
|
||||
};
|
||||
}
|
||||
const raw = readFileSync(configPath, 'utf8');
|
||||
const parsed = JSON.parse(raw);
|
||||
return {
|
||||
path: configPath,
|
||||
defaults: parsed.defaults || {},
|
||||
targets: Array.isArray(parsed.targets) ? parsed.targets : [],
|
||||
};
|
||||
}
|
||||
|
||||
function findDeployTarget(repo, branch, requestedTarget) {
|
||||
const config = loadDeployTargetsConfig();
|
||||
const wantedTarget = requestedTarget || 'default';
|
||||
const match = config.targets.find((entry) => {
|
||||
if (entry.repo !== repo) return false;
|
||||
if ((entry.branch || 'main') !== branch) return false;
|
||||
return (entry.target || 'default') === wantedTarget;
|
||||
});
|
||||
return { config, match, wantedTarget };
|
||||
}
|
||||
|
||||
async function sleep(ms) {
|
||||
await new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function verifyHealthCheck(healthcheck) {
|
||||
if (!healthcheck || !healthcheck.url) return null;
|
||||
|
||||
const attempts = Math.max(1, Number(healthcheck.attempts || 1));
|
||||
const delayMs = Math.max(0, Number(healthcheck.delay_ms || 0));
|
||||
const timeoutMs = Math.max(1000, Number(healthcheck.timeout_ms || 10000));
|
||||
const expectedStatus = Number(healthcheck.expect_status || 200);
|
||||
const expectBodyIncludes = healthcheck.expect_body_includes || '';
|
||||
|
||||
let lastError = null;
|
||||
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
||||
const res = await fetch(healthcheck.url, { signal: controller.signal });
|
||||
const body = await res.text();
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (res.status !== expectedStatus) {
|
||||
throw new Error(`Expected HTTP ${expectedStatus}, got ${res.status}`);
|
||||
}
|
||||
if (expectBodyIncludes && !body.includes(expectBodyIncludes)) {
|
||||
throw new Error(`Health body missing expected text: ${expectBodyIncludes}`);
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
url: healthcheck.url,
|
||||
status: res.status,
|
||||
attempt,
|
||||
};
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
if (attempt < attempts && delayMs > 0) {
|
||||
await sleep(delayMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Health check failed for ${healthcheck.url}: ${lastError?.message || 'unknown error'}`);
|
||||
}
|
||||
|
||||
async function downloadRepoArchive({ owner, repo, ref, archivePath, authToken }) {
|
||||
const archiveRef = `${ref}.tar.gz`;
|
||||
const url = `${GITEA_URL}/api/v1/repos/${owner}/${repo}/archive/${archiveRef}`;
|
||||
const headers = {};
|
||||
if (authToken) headers.Authorization = `token ${authToken}`;
|
||||
const res = await fetch(url, { headers });
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to download archive ${owner}/${repo}@${ref}: HTTP ${res.status}`);
|
||||
}
|
||||
const buffer = Buffer.from(await res.arrayBuffer());
|
||||
writeFileSync(archivePath, buffer);
|
||||
}
|
||||
|
||||
function syncExtractedTree({ sourceRoot, destRoot, entries = null }) {
|
||||
mkdirSync(destRoot, { recursive: true });
|
||||
const selectedEntries = Array.isArray(entries) ? entries : readdirSync(sourceRoot);
|
||||
for (const entry of selectedEntries) {
|
||||
const sourcePath = path.join(sourceRoot, entry);
|
||||
if (!existsSync(sourcePath)) continue;
|
||||
const destPath = path.join(destRoot, entry);
|
||||
rmSync(destPath, { recursive: true, force: true });
|
||||
cpSync(sourcePath, destPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function syncRepoArchive({ owner, repo, ref, destRoot, entries = null, authToken = '' }) {
|
||||
const tempDir = mkdtempSync('/tmp/phoenix-archive-');
|
||||
const archivePath = path.join(tempDir, 'repo.tar.gz');
|
||||
const extractDir = path.join(tempDir, 'extract');
|
||||
mkdirSync(extractDir, { recursive: true });
|
||||
try {
|
||||
await downloadRepoArchive({ owner, repo, ref, archivePath, authToken });
|
||||
await execFile('tar', ['-xzf', archivePath, '-C', extractDir]);
|
||||
const [rootDir] = readdirSync(extractDir);
|
||||
if (!rootDir) {
|
||||
throw new Error(`Archive for ${owner}/${repo}@${ref} was empty`);
|
||||
}
|
||||
syncExtractedTree({
|
||||
sourceRoot: path.join(extractDir, rootDir),
|
||||
destRoot,
|
||||
entries,
|
||||
});
|
||||
} finally {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareDeployWorkspace({ repo, branch, sha, target }) {
|
||||
const repoRoot = resolvePhoenixRepoRoot();
|
||||
if (!repoRoot) {
|
||||
throw new Error('PHOENIX_REPO_ROOT is not configured');
|
||||
}
|
||||
|
||||
const [owner, repoName] = repo.includes('/') ? repo.split('/') : ['d-bis', repo];
|
||||
const externalWorkspaceRoot = path.join(repoRoot, '.phoenix-deploy-workspaces', owner, repoName);
|
||||
|
||||
// Manual smoke tests can target the already-staged local workspace without
|
||||
// forcing an archive sync from Gitea.
|
||||
if (sha === 'HEAD' || sha === 'local') {
|
||||
mkdirSync(repoRoot, { recursive: true });
|
||||
if (repo !== 'd-bis/proxmox') {
|
||||
mkdirSync(externalWorkspaceRoot, { recursive: true });
|
||||
}
|
||||
return {
|
||||
PHOENIX_REPO_ROOT: repoRoot,
|
||||
PROXMOX_REPO_ROOT: repoRoot,
|
||||
PHOENIX_DEPLOY_WORKSPACE: repo === 'd-bis/proxmox' ? repoRoot : externalWorkspaceRoot,
|
||||
};
|
||||
}
|
||||
|
||||
const ref = sha || branch || 'main';
|
||||
|
||||
if (repo === 'd-bis/proxmox') {
|
||||
await syncRepoArchive({
|
||||
owner,
|
||||
repo: repoName,
|
||||
ref,
|
||||
destRoot: repoRoot,
|
||||
entries: ['config', 'phoenix-deploy-api', 'reports', 'scripts', 'token-lists'],
|
||||
authToken: GITEA_TOKEN,
|
||||
});
|
||||
} else {
|
||||
await syncRepoArchive({
|
||||
owner,
|
||||
repo: repoName,
|
||||
ref,
|
||||
destRoot: externalWorkspaceRoot,
|
||||
authToken: GITEA_TOKEN,
|
||||
});
|
||||
}
|
||||
|
||||
if (repo === 'd-bis/proxmox' && target === 'atomic-swap-dapp-live') {
|
||||
const [swapOwner, swapRepo] = ATOMIC_SWAP_REPO.includes('/')
|
||||
? ATOMIC_SWAP_REPO.split('/')
|
||||
: ['d-bis', ATOMIC_SWAP_REPO];
|
||||
await syncRepoArchive({
|
||||
owner: swapOwner,
|
||||
repo: swapRepo,
|
||||
ref: ATOMIC_SWAP_REF,
|
||||
destRoot: path.join(repoRoot, 'atomic-swap-dapp'),
|
||||
authToken: GITEA_TOKEN,
|
||||
});
|
||||
|
||||
if (CROSS_CHAIN_PMM_LPS_REPO) {
|
||||
const [lpsOwner, lpsRepo] = CROSS_CHAIN_PMM_LPS_REPO.includes('/')
|
||||
? CROSS_CHAIN_PMM_LPS_REPO.split('/')
|
||||
: ['d-bis', CROSS_CHAIN_PMM_LPS_REPO];
|
||||
await syncRepoArchive({
|
||||
owner: lpsOwner,
|
||||
repo: lpsRepo,
|
||||
ref: CROSS_CHAIN_PMM_LPS_REF,
|
||||
destRoot: path.join(repoRoot, 'cross-chain-pmm-lps'),
|
||||
authToken: GITEA_TOKEN,
|
||||
});
|
||||
}
|
||||
|
||||
if (SMOM_DBIS_138_REPO) {
|
||||
const [smomOwner, smomRepo] = SMOM_DBIS_138_REPO.includes('/')
|
||||
? SMOM_DBIS_138_REPO.split('/')
|
||||
: ['d-bis', SMOM_DBIS_138_REPO];
|
||||
await syncRepoArchive({
|
||||
owner: smomOwner,
|
||||
repo: smomRepo,
|
||||
ref: SMOM_DBIS_138_REF,
|
||||
destRoot: path.join(repoRoot, 'smom-dbis-138'),
|
||||
authToken: GITEA_TOKEN,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
PHOENIX_REPO_ROOT: repoRoot,
|
||||
PROXMOX_REPO_ROOT: repoRoot,
|
||||
PHOENIX_DEPLOY_WORKSPACE: repo === 'd-bis/proxmox' ? repoRoot : externalWorkspaceRoot,
|
||||
};
|
||||
}
|
||||
|
||||
async function runDeployTarget(definition, configDefaults, context, envOverrides = {}) {
|
||||
if (!Array.isArray(definition.command) || definition.command.length === 0) {
|
||||
throw new Error('Deploy target is missing a command array');
|
||||
}
|
||||
|
||||
const childEnv = {
|
||||
...process.env,
|
||||
...envOverrides,
|
||||
PHOENIX_DEPLOY_REPO: context.repo,
|
||||
PHOENIX_DEPLOY_BRANCH: context.branch,
|
||||
PHOENIX_DEPLOY_SHA: context.sha || '',
|
||||
PHOENIX_DEPLOY_TARGET: context.target,
|
||||
PHOENIX_DEPLOY_TRIGGER: context.trigger,
|
||||
};
|
||||
|
||||
const cwd = expandEnvTokens(definition.cwd || configDefaults.cwd || process.cwd(), childEnv);
|
||||
const timeoutSeconds = Number(definition.timeout_sec || configDefaults.timeout_sec || 1800);
|
||||
const timeout = Number.isFinite(timeoutSeconds) && timeoutSeconds > 0 ? timeoutSeconds * 1000 : 1800 * 1000;
|
||||
const command = definition.command.map((part) => expandEnvTokens(part, childEnv));
|
||||
const missingEnv = (definition.required_env || []).filter((key) => !childEnv[key]);
|
||||
if (missingEnv.length > 0) {
|
||||
throw new Error(`Missing required env for deploy target: ${missingEnv.join(', ')}`);
|
||||
}
|
||||
if (!existsSync(cwd)) {
|
||||
throw new Error(`Deploy working directory does not exist: ${cwd}`);
|
||||
}
|
||||
|
||||
const { stdout, stderr } = await execFile(command[0], command.slice(1), {
|
||||
cwd,
|
||||
env: childEnv,
|
||||
timeout,
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
});
|
||||
|
||||
const healthcheck = await verifyHealthCheck(definition.healthcheck || configDefaults.healthcheck || null);
|
||||
|
||||
return {
|
||||
cwd,
|
||||
command,
|
||||
stdout: stdout || '',
|
||||
stderr: stderr || '',
|
||||
timeout_sec: timeoutSeconds,
|
||||
healthcheck,
|
||||
};
|
||||
}
|
||||
|
||||
async function executeDeploy({ repo, branch = 'main', target = 'default', sha = '', trigger = 'api' }) {
|
||||
if (!repo) {
|
||||
const error = new Error('repo required');
|
||||
error.statusCode = 400;
|
||||
error.payload = { error: error.message };
|
||||
throw error;
|
||||
}
|
||||
|
||||
const [owner, repoName] = repo.includes('/') ? repo.split('/') : ['d-bis', repo];
|
||||
const commitSha = sha || '';
|
||||
const requestedTarget = target || 'default';
|
||||
const { config, match, wantedTarget } = findDeployTarget(repo, branch, requestedTarget);
|
||||
|
||||
if (!match) {
|
||||
const error = new Error('Deploy target not configured');
|
||||
error.statusCode = 404;
|
||||
error.payload = {
|
||||
error: error.message,
|
||||
repo,
|
||||
branch,
|
||||
target: wantedTarget,
|
||||
config_path: config.path,
|
||||
};
|
||||
if (commitSha && GITEA_TOKEN) {
|
||||
await setGiteaCommitStatus(owner, repoName, commitSha, 'failure', `No deploy target for ${repo} ${branch} ${wantedTarget}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (commitSha && GITEA_TOKEN) {
|
||||
await setGiteaCommitStatus(owner, repoName, commitSha, 'pending', 'Phoenix deployment in progress');
|
||||
}
|
||||
|
||||
console.log(`[deploy] ${repo} branch=${branch} target=${wantedTarget} sha=${commitSha} trigger=${trigger}`);
|
||||
|
||||
let deployResult = null;
|
||||
let deployError = null;
|
||||
let envOverrides = {};
|
||||
|
||||
try {
|
||||
envOverrides = await prepareDeployWorkspace({
|
||||
repo,
|
||||
branch,
|
||||
sha: commitSha,
|
||||
target: wantedTarget,
|
||||
});
|
||||
deployResult = await runDeployTarget(match, config.defaults, {
|
||||
repo,
|
||||
branch,
|
||||
sha: commitSha,
|
||||
target: wantedTarget,
|
||||
trigger,
|
||||
}, envOverrides);
|
||||
if (commitSha && GITEA_TOKEN) {
|
||||
await setGiteaCommitStatus(owner, repoName, commitSha, 'success', `Deployed to ${wantedTarget}`);
|
||||
}
|
||||
return {
|
||||
status: 'completed',
|
||||
repo,
|
||||
branch,
|
||||
target: wantedTarget,
|
||||
config_path: config.path,
|
||||
command: deployResult.command,
|
||||
cwd: deployResult.cwd,
|
||||
stdout: deployResult.stdout,
|
||||
stderr: deployResult.stderr,
|
||||
healthcheck: deployResult.healthcheck,
|
||||
};
|
||||
} catch (err) {
|
||||
deployError = err;
|
||||
if (commitSha && GITEA_TOKEN) {
|
||||
await setGiteaCommitStatus(owner, repoName, commitSha, 'failure', `Deploy failed: ${err.message.slice(0, 120)}`);
|
||||
}
|
||||
err.statusCode = err.statusCode || 500;
|
||||
err.payload = err.payload || {
|
||||
error: err.message,
|
||||
repo,
|
||||
branch,
|
||||
target: wantedTarget,
|
||||
config_path: config.path,
|
||||
};
|
||||
throw err;
|
||||
} finally {
|
||||
if (PHOENIX_WEBHOOK_URL) {
|
||||
const payload = {
|
||||
event: 'deploy.completed',
|
||||
repo,
|
||||
branch,
|
||||
target: wantedTarget,
|
||||
sha: commitSha,
|
||||
success: Boolean(deployResult),
|
||||
command: deployResult?.command,
|
||||
cwd: deployResult?.cwd,
|
||||
phoenix_repo_root: envOverrides.PHOENIX_REPO_ROOT || null,
|
||||
error: deployError?.message || null,
|
||||
};
|
||||
const body = JSON.stringify(payload);
|
||||
const sig = crypto.createHmac('sha256', PHOENIX_WEBHOOK_SECRET || '').update(body).digest('hex');
|
||||
fetch(PHOENIX_WEBHOOK_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Phoenix-Signature': `sha256=${sig}` },
|
||||
body,
|
||||
}).catch((e) => console.error('[webhook] outbound failed', e.message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const httpsAgent = new https.Agent({ rejectUnauthorized: process.env.PROXMOX_TLS_VERIFY !== '0' });
|
||||
|
||||
function formatProxmoxAuthHeader(user, tokenName, tokenValue) {
|
||||
if (tokenName.includes('!')) {
|
||||
return `PVEAPIToken=${tokenName}=${tokenValue}`;
|
||||
}
|
||||
return `PVEAPIToken=${user}!${tokenName}=${tokenValue}`;
|
||||
}
|
||||
|
||||
async function proxmoxRequest(endpoint, method = 'GET', body = null) {
|
||||
const baseUrl = `https://${PROXMOX_HOST}:${PROXMOX_PORT}/api2/json`;
|
||||
const url = `${baseUrl}${endpoint}`;
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `PVEAPIToken=${PROXMOX_USER}!${PROXMOX_TOKEN_NAME}=${PROXMOX_TOKEN_VALUE}`,
|
||||
Authorization: formatProxmoxAuthHeader(PROXMOX_USER, PROXMOX_TOKEN_NAME, PROXMOX_TOKEN_VALUE),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
agent: httpsAgent,
|
||||
@@ -162,12 +562,44 @@ app.post('/webhook/gitea', async (req, res) => {
|
||||
|
||||
if (action === 'push' || (action === 'synchronize' && payload.pull_request)) {
|
||||
if (branch === 'main' || branch === 'master' || ref.startsWith('refs/tags/')) {
|
||||
if (sha && GITEA_TOKEN) {
|
||||
await setGiteaCommitStatus(owner, repoName, sha, 'pending', 'Phoenix deployment triggered');
|
||||
if (!WEBHOOK_DEPLOY_ENABLED) {
|
||||
return res.status(200).json({
|
||||
received: true,
|
||||
repo: fullName,
|
||||
branch,
|
||||
sha,
|
||||
deployed: false,
|
||||
message: 'Webhook accepted; set PHOENIX_WEBHOOK_DEPLOY_ENABLED=1 to execute deploys from webhook events.',
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await executeDeploy({
|
||||
repo: fullName,
|
||||
branch,
|
||||
sha,
|
||||
target: 'default',
|
||||
trigger: 'webhook',
|
||||
});
|
||||
return res.status(200).json({
|
||||
received: true,
|
||||
repo: fullName,
|
||||
branch,
|
||||
sha,
|
||||
deployed: true,
|
||||
result,
|
||||
});
|
||||
} catch (err) {
|
||||
return res.status(200).json({
|
||||
received: true,
|
||||
repo: fullName,
|
||||
branch,
|
||||
sha,
|
||||
deployed: false,
|
||||
error: err.message,
|
||||
details: err.payload || null,
|
||||
});
|
||||
}
|
||||
// Stub: enqueue deploy; actual implementation would call Proxmox/deploy logic
|
||||
console.log(`[deploy-stub] Would deploy ${fullName} branch=${branch} sha=${sha}`);
|
||||
// Stub: when full deploy runs, call setGiteaCommitStatus(owner, repoName, sha, 'success'|'failure', ...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,47 +617,36 @@ app.post('/api/deploy', async (req, res) => {
|
||||
}
|
||||
|
||||
const { repo, branch = 'main', target, sha } = req.body;
|
||||
if (!repo) {
|
||||
return res.status(400).json({ error: 'repo required' });
|
||||
try {
|
||||
const result = await executeDeploy({
|
||||
repo,
|
||||
branch,
|
||||
sha,
|
||||
target,
|
||||
trigger: 'api',
|
||||
});
|
||||
res.status(200).json(result);
|
||||
} catch (err) {
|
||||
res.status(err.statusCode || 500).json(err.payload || { error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
const [owner, repoName] = repo.includes('/') ? repo.split('/') : ['d-bis', repo];
|
||||
const commitSha = sha || '';
|
||||
|
||||
if (commitSha && GITEA_TOKEN) {
|
||||
await setGiteaCommitStatus(owner, repoName, commitSha, 'pending', 'Phoenix deployment in progress');
|
||||
}
|
||||
|
||||
console.log(`[deploy] ${repo} branch=${branch} target=${target || 'default'} sha=${commitSha}`);
|
||||
// Stub: no real deploy yet — report success so Gitea shows green; replace with real deploy + setGiteaCommitStatus on completion
|
||||
const deploySuccess = true;
|
||||
if (commitSha && GITEA_TOKEN) {
|
||||
await setGiteaCommitStatus(
|
||||
owner,
|
||||
repoName,
|
||||
commitSha,
|
||||
deploySuccess ? 'success' : 'failure',
|
||||
deploySuccess ? 'Deploy accepted (stub)' : 'Deploy failed (stub)'
|
||||
);
|
||||
}
|
||||
res.status(202).json({
|
||||
status: 'accepted',
|
||||
repo,
|
||||
branch,
|
||||
target: target || 'default',
|
||||
message: 'Deploy request queued (stub). Implement full deploy logic in Sankofa Phoenix API.',
|
||||
app.get('/api/deploy-targets', (req, res) => {
|
||||
const config = loadDeployTargetsConfig();
|
||||
const targets = config.targets.map((entry) => ({
|
||||
repo: entry.repo,
|
||||
branch: entry.branch || 'main',
|
||||
target: entry.target || 'default',
|
||||
description: entry.description || '',
|
||||
cwd: entry.cwd || config.defaults.cwd || '',
|
||||
command: entry.command || [],
|
||||
has_healthcheck: Boolean(entry.healthcheck || config.defaults.healthcheck),
|
||||
}));
|
||||
res.json({
|
||||
config_path: config.path,
|
||||
count: targets.length,
|
||||
targets,
|
||||
});
|
||||
|
||||
if (PHOENIX_WEBHOOK_URL) {
|
||||
const payload = { event: 'deploy.completed', repo, branch, target: target || 'default', sha: commitSha, success: deploySuccess };
|
||||
const body = JSON.stringify(payload);
|
||||
const sig = crypto.createHmac('sha256', PHOENIX_WEBHOOK_SECRET || '').update(body).digest('hex');
|
||||
fetch(PHOENIX_WEBHOOK_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Phoenix-Signature': `sha256=${sig}` },
|
||||
body,
|
||||
}).catch((e) => console.error('[webhook] outbound failed', e.message));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -474,7 +895,10 @@ app.listen(PORT, () => {
|
||||
if (!GITEA_TOKEN) console.warn('GITEA_TOKEN not set — commit status updates disabled');
|
||||
if (!hasProxmox) console.warn('PROXMOX_* not set — Infra/VE API returns stub data');
|
||||
if (PHOENIX_WEBHOOK_URL) console.log('Outbound webhook enabled:', PHOENIX_WEBHOOK_URL);
|
||||
if (WEBHOOK_DEPLOY_ENABLED) console.log('Inbound webhook deploy execution enabled');
|
||||
if (PARTNER_KEYS.length > 0) console.log('Partner API key auth enabled for /api/v1/* (except GET /api/v1/public-sector/programs)');
|
||||
const mpath = resolvePublicSectorManifestPath();
|
||||
const dpath = resolveDeployTargetsPath();
|
||||
console.log(`Public-sector manifest: ${mpath} (${existsSync(mpath) ? 'ok' : 'missing'})`);
|
||||
console.log(`Deploy targets: ${dpath} (${existsSync(dpath) ? 'ok' : 'missing'})`);
|
||||
});
|
||||
|
||||
152
scripts/deployment/deploy-atomic-swap-dapp-5801.sh
Normal file
152
scripts/deployment/deploy-atomic-swap-dapp-5801.sh
Normal file
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SUBMODULE_ROOT="$PROJECT_ROOT/atomic-swap-dapp"
|
||||
source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true
|
||||
PROXMOX_HOST="${PROXMOX_DAPP_HOST:-${PROXMOX_HOST_R630_02:-192.168.11.12}}"
|
||||
VMID="${VMID:-5801}"
|
||||
DEPLOY_ROOT="${DEPLOY_ROOT:-/var/www/atomic-swap}"
|
||||
TMP_ARCHIVE="/tmp/atomic-swap-dapp-5801.tgz"
|
||||
DIST_DIR="$SUBMODULE_ROOT/dist"
|
||||
SKIP_BUILD="${SKIP_BUILD:-0}"
|
||||
SSH_OPTS="${SSH_OPTS:--o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new}"
|
||||
|
||||
cleanup() {
|
||||
rm -f "$TMP_ARCHIVE"
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
if [ ! -d "$SUBMODULE_ROOT" ]; then
|
||||
echo "Missing submodule at $SUBMODULE_ROOT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$SUBMODULE_ROOT"
|
||||
if [ "$SKIP_BUILD" != "1" ]; then
|
||||
if [ -f package-lock.json ]; then
|
||||
npm ci >/dev/null
|
||||
else
|
||||
npm install >/dev/null
|
||||
fi
|
||||
npm run sync:ecosystem >/dev/null
|
||||
npm run validate:manifest >/dev/null
|
||||
npm run build >/dev/null
|
||||
fi
|
||||
|
||||
for required_path in \
|
||||
"$DIST_DIR/index.html" \
|
||||
"$DIST_DIR/data/ecosystem-manifest.json" \
|
||||
"$DIST_DIR/data/live-route-registry.json" \
|
||||
"$DIST_DIR/data/deployed-venue-inventory.json"; do
|
||||
if [ ! -f "$required_path" ]; then
|
||||
echo "Missing required build artifact: $required_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
jq -e '.supportedNetworks[] | select(.chainId == 138) | .deployedVenuePoolCount >= 19 and .publicRoutingPoolCount >= 19' \
|
||||
"$DIST_DIR/data/ecosystem-manifest.json" >/dev/null
|
||||
jq -e '.liveSwapRoutes | length >= 19' "$DIST_DIR/data/live-route-registry.json" >/dev/null
|
||||
jq -e '.liveBridgeRoutes | length >= 12' "$DIST_DIR/data/live-route-registry.json" >/dev/null
|
||||
jq -e '.networks[] | select(.chainId == 138) | .venueCounts.deployedVenuePoolCount >= 19 and .summary.totalVenues >= 19' \
|
||||
"$DIST_DIR/data/deployed-venue-inventory.json" >/dev/null
|
||||
|
||||
rm -f "$TMP_ARCHIVE"
|
||||
tar -C "$SUBMODULE_ROOT" -czf "$TMP_ARCHIVE" dist
|
||||
|
||||
ssh $SSH_OPTS "root@$PROXMOX_HOST" true
|
||||
scp -q $SSH_OPTS "$TMP_ARCHIVE" "root@$PROXMOX_HOST:/tmp/atomic-swap-dapp-5801.tgz"
|
||||
|
||||
ssh $SSH_OPTS "root@$PROXMOX_HOST" "
|
||||
set -euo pipefail
|
||||
pct push $VMID /tmp/atomic-swap-dapp-5801.tgz /tmp/atomic-swap-dapp-5801.tgz
|
||||
pct exec $VMID -- bash -lc '
|
||||
set -euo pipefail
|
||||
mkdir -p \"$DEPLOY_ROOT\"
|
||||
find \"$DEPLOY_ROOT\" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
||||
rm -rf /tmp/dist
|
||||
tar -xzf /tmp/atomic-swap-dapp-5801.tgz -C /tmp
|
||||
cp -R /tmp/dist/. \"$DEPLOY_ROOT/\"
|
||||
mkdir -p /var/cache/nginx/atomic-swap-api
|
||||
cat > /etc/nginx/conf.d/atomic-swap-api-cache.conf <<\"EOF\"
|
||||
proxy_cache_path /var/cache/nginx/atomic-swap-api
|
||||
levels=1:2
|
||||
keys_zone=atomic_swap_api_cache:10m
|
||||
max_size=256m
|
||||
inactive=30m
|
||||
use_temp_path=off;
|
||||
EOF
|
||||
cat > /etc/nginx/sites-available/atomic-swap <<\"EOF\"
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
|
||||
root $DEPLOY_ROOT;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
|
||||
location = /index.html {
|
||||
add_header Cache-Control \"no-store, no-cache, must-revalidate\" always;
|
||||
}
|
||||
|
||||
location /data/ {
|
||||
add_header Cache-Control \"no-store, no-cache, must-revalidate\" always;
|
||||
}
|
||||
|
||||
location /assets/ {
|
||||
add_header Cache-Control \"public, max-age=31536000, immutable\" always;
|
||||
}
|
||||
|
||||
location /api/v1/ {
|
||||
proxy_pass https://explorer.d-bis.org/api/v1/;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_set_header Host explorer.d-bis.org;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Host \$host;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering on;
|
||||
proxy_cache atomic_swap_api_cache;
|
||||
proxy_cache_methods GET HEAD;
|
||||
proxy_cache_key \"\$scheme\$proxy_host\$request_uri\";
|
||||
proxy_cache_lock on;
|
||||
proxy_cache_lock_timeout 10s;
|
||||
proxy_cache_lock_age 10s;
|
||||
proxy_cache_background_update on;
|
||||
proxy_cache_revalidate on;
|
||||
proxy_cache_valid 200 10s;
|
||||
proxy_cache_valid 404 1s;
|
||||
proxy_cache_valid any 0;
|
||||
proxy_cache_use_stale error timeout invalid_header updating http_429 http_500 http_502 http_503 http_504;
|
||||
add_header X-Atomic-Swap-Cache \$upstream_cache_status always;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
ln -sfn /etc/nginx/sites-available/atomic-swap /etc/nginx/sites-enabled/atomic-swap
|
||||
rm -f /etc/nginx/sites-enabled/default
|
||||
rm -f /etc/nginx/sites-enabled/dapp
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
curl -fsS http://127.0.0.1/index.html >/dev/null
|
||||
curl -fsS http://127.0.0.1/data/ecosystem-manifest.json >/dev/null
|
||||
curl -fsS http://127.0.0.1/data/live-route-registry.json >/dev/null
|
||||
curl -fsS http://127.0.0.1/data/deployed-venue-inventory.json >/dev/null
|
||||
rm -rf /tmp/dist /tmp/atomic-swap-dapp-5801.tgz
|
||||
'
|
||||
rm -f /tmp/atomic-swap-dapp-5801.tgz
|
||||
"
|
||||
|
||||
curl -fsS https://atomic-swap.defi-oracle.io/ >/dev/null
|
||||
curl -fsS https://atomic-swap.defi-oracle.io/data/ecosystem-manifest.json | jq -e '.supportedNetworks[] | select(.chainId == 138) | .deployedVenuePoolCount >= 19 and .publicRoutingPoolCount >= 19' >/dev/null
|
||||
curl -fsS https://atomic-swap.defi-oracle.io/data/live-route-registry.json | jq -e '.liveSwapRoutes | length >= 19' >/dev/null
|
||||
curl -fsS https://atomic-swap.defi-oracle.io/data/live-route-registry.json | jq -e '.liveBridgeRoutes | length >= 12' >/dev/null
|
||||
curl -fsS https://atomic-swap.defi-oracle.io/data/deployed-venue-inventory.json | jq -e '.networks[] | select(.chainId == 138) | .venueCounts.deployedVenuePoolCount >= 19 and .summary.totalVenues >= 19' >/dev/null
|
||||
|
||||
echo "Deployed atomic-swap-dapp to VMID $VMID via $PROXMOX_HOST"
|
||||
244
scripts/deployment/phoenix-deploy-currencicombo-from-workspace.sh
Executable file
244
scripts/deployment/phoenix-deploy-currencicombo-from-workspace.sh
Executable file
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
source "$PROJECT_ROOT/scripts/lib/load-project-env.sh"
|
||||
source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true
|
||||
|
||||
PHOENIX_DEPLOY_WORKSPACE="${PHOENIX_DEPLOY_WORKSPACE:-}"
|
||||
PROXMOX_HOST="${PROXMOX_HOST_R630_01:-192.168.11.11}"
|
||||
PROXMOX_SSH_USER="${PROXMOX_SSH_USER:-root}"
|
||||
VMID="${CURRENCICOMBO_PHOENIX_VMID:-8604}"
|
||||
CT_IP="${IP_CURRENCICOMBO_PHOENIX:-10.160.0.14}"
|
||||
CT_REPO_DIR="${CT_REPO_DIR:-/var/lib/currencicombo/repo}"
|
||||
PUBLIC_URL="${PUBLIC_URL:-https://curucombo.xn--vov0g.com}"
|
||||
PUBLIC_DOMAIN="${PUBLIC_DOMAIN:-curucombo.xn--vov0g.com}"
|
||||
NPM_URL="${NPM_URL:-https://${IP_NPMPLUS:-192.168.11.167}:81}"
|
||||
NPM_EMAIL="${NPM_EMAIL:-}"
|
||||
NPM_PASSWORD="${NPM_PASSWORD:-}"
|
||||
DRY_RUN=0
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: phoenix-deploy-currencicombo-from-workspace.sh [--dry-run]
|
||||
|
||||
Requires:
|
||||
PHOENIX_DEPLOY_WORKSPACE Full staged CurrenciCombo checkout prepared by phoenix-deploy-api
|
||||
|
||||
This script:
|
||||
1. Packs the staged repo workspace.
|
||||
2. Pushes it into CT 8604 on r630-01.
|
||||
3. Ensures host prerequisites, install.sh, prune cron, and deploy script run in-CT.
|
||||
4. Updates the public NPMplus host so /api/* preserves the full path and supports SSE.
|
||||
5. Verifies the public portal + /api/ready end to end.
|
||||
USAGE
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "unknown arg: $1" >&2; usage; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
log() { printf '[currencicombo-phoenix] %s\n' "$*" >&2; }
|
||||
die() { printf '[currencicombo-phoenix][FATAL] %s\n' "$*" >&2; exit 1; }
|
||||
run() { if [[ "$DRY_RUN" -eq 1 ]]; then printf '[dry-run] %s\n' "$*" >&2; else eval "$*"; fi; }
|
||||
need_cmd() { command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"; }
|
||||
|
||||
for cmd in ssh scp tar curl jq mktemp; do
|
||||
need_cmd "$cmd"
|
||||
done
|
||||
|
||||
[[ -n "$PHOENIX_DEPLOY_WORKSPACE" ]] || die "PHOENIX_DEPLOY_WORKSPACE is required"
|
||||
[[ -d "$PHOENIX_DEPLOY_WORKSPACE" ]] || die "staged workspace missing: $PHOENIX_DEPLOY_WORKSPACE"
|
||||
|
||||
if [[ "$DRY_RUN" -eq 0 ]]; then
|
||||
[[ -n "$NPM_EMAIL" ]] || die "NPM_EMAIL is required"
|
||||
[[ -n "$NPM_PASSWORD" ]] || die "NPM_PASSWORD is required"
|
||||
fi
|
||||
|
||||
SSH_TARGET="${PROXMOX_SSH_USER}@${PROXMOX_HOST}"
|
||||
SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new)
|
||||
TMP_DIR="$(mktemp -d /tmp/currencicombo-phoenix-XXXXXX)"
|
||||
ARCHIVE_PATH="${TMP_DIR}/currencicombo-workspace.tgz"
|
||||
REMOTE_ARCHIVE="/tmp/$(basename "$ARCHIVE_PATH")"
|
||||
CT_ARCHIVE="/root/$(basename "$ARCHIVE_PATH")"
|
||||
NPM_COOKIE_JAR="${TMP_DIR}/npm-cookies.txt"
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
ssh_remote() {
|
||||
local cmd="$1"
|
||||
if [[ "$DRY_RUN" -eq 1 ]]; then
|
||||
printf '[dry-run] ssh %q %q\n' "$SSH_TARGET" "$cmd" >&2
|
||||
else
|
||||
ssh "${SSH_OPTS[@]}" "$SSH_TARGET" "$cmd"
|
||||
fi
|
||||
}
|
||||
|
||||
pct_exec_script() {
|
||||
local local_script="$1"
|
||||
local remote_script
|
||||
local ct_script
|
||||
remote_script="/tmp/$(basename "$local_script")"
|
||||
ct_script="/root/$(basename "$local_script")"
|
||||
run "scp ${SSH_OPTS[*]} '$local_script' '${SSH_TARGET}:${remote_script}'"
|
||||
ssh_remote "pct push ${VMID} '${remote_script}' '${ct_script}' --perms 0755 && rm -f '${remote_script}' && pct exec ${VMID} -- bash '${ct_script}' && pct exec ${VMID} -- rm -f '${ct_script}'"
|
||||
}
|
||||
|
||||
log "packing staged workspace from ${PHOENIX_DEPLOY_WORKSPACE}"
|
||||
run "tar -C '$PHOENIX_DEPLOY_WORKSPACE' --exclude='.git' --exclude='node_modules' --exclude='dist' --exclude='orchestrator/node_modules' --exclude='orchestrator/dist' -czf '$ARCHIVE_PATH' ."
|
||||
|
||||
log "ensuring CT ${VMID} is running on ${PROXMOX_HOST}"
|
||||
ssh_remote "pct start ${VMID} >/dev/null 2>&1 || true"
|
||||
|
||||
log "uploading staged archive to CT ${VMID}"
|
||||
run "scp ${SSH_OPTS[*]} '$ARCHIVE_PATH' '${SSH_TARGET}:${REMOTE_ARCHIVE}'"
|
||||
ssh_remote "pct push ${VMID} '${REMOTE_ARCHIVE}' '${CT_ARCHIVE}' && rm -f '${REMOTE_ARCHIVE}'"
|
||||
|
||||
CT_SCRIPT="${TMP_DIR}/currencicombo-ct-deploy.sh"
|
||||
cat > "$CT_SCRIPT" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
ARCHIVE_PATH="__CT_ARCHIVE__"
|
||||
REPO_DIR="__CT_REPO_DIR__"
|
||||
|
||||
need_pkg() {
|
||||
dpkg -s "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
apt-get update -qq
|
||||
for pkg in ca-certificates curl git jq postgresql redis-server rsync build-essential; do
|
||||
need_pkg "$pkg" || apt-get install -y -qq "$pkg"
|
||||
done
|
||||
|
||||
if ! command -v node >/dev/null 2>&1 || ! node -v 2>/dev/null | grep -q '^v20\.'; then
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
||||
apt-get install -y -qq nodejs
|
||||
fi
|
||||
|
||||
systemctl enable --now postgresql >/dev/null 2>&1 || true
|
||||
systemctl enable --now redis-server >/dev/null 2>&1 || true
|
||||
|
||||
if [[ ! -f /root/currencicombo-prephoenix-archive.tgz && -d /opt/currencicombo ]]; then
|
||||
tar -czf /root/currencicombo-prephoenix-archive.tgz /opt/currencicombo /etc/currencicombo 2>/dev/null || true
|
||||
fi
|
||||
|
||||
install -d -o root -g root -m 0755 "$(dirname "$REPO_DIR")"
|
||||
rm -rf "$REPO_DIR"
|
||||
mkdir -p "$REPO_DIR"
|
||||
tar -xzf "$ARCHIVE_PATH" -C "$REPO_DIR"
|
||||
rm -f "$ARCHIVE_PATH"
|
||||
|
||||
bash "$REPO_DIR/scripts/deployment/install.sh"
|
||||
bash "$REPO_DIR/scripts/deployment/install-prune-cron.sh"
|
||||
CC_GIT_REF=local bash "$REPO_DIR/scripts/deployment/deploy-currencicombo-8604.sh"
|
||||
systemctl is-active currencicombo-orchestrator.service currencicombo-webapp.service
|
||||
curl -fsS http://127.0.0.1:8080/ready
|
||||
curl -fsS http://127.0.0.1:3000/ >/dev/null
|
||||
EOF
|
||||
perl -0pi -e "s|__CT_ARCHIVE__|${CT_ARCHIVE//|/\\|}|g; s|__CT_REPO_DIR__|${CT_REPO_DIR//|/\\|}|g" "$CT_SCRIPT"
|
||||
|
||||
log "running install + deploy inside CT ${VMID}"
|
||||
pct_exec_script "$CT_SCRIPT"
|
||||
|
||||
if [[ "$DRY_RUN" -eq 0 ]]; then
|
||||
log "updating NPMplus proxy host for ${PUBLIC_DOMAIN}"
|
||||
AUTH_JSON="$(jq -nc --arg identity "$NPM_EMAIL" --arg secret "$NPM_PASSWORD" '{identity:$identity,secret:$secret}')"
|
||||
TOKEN_RESPONSE="$(curl -sk -X POST "$NPM_URL/api/tokens" -H 'Content-Type: application/json' -d "$AUTH_JSON" -c "$NPM_COOKIE_JAR")"
|
||||
TOKEN="$(echo "$TOKEN_RESPONSE" | jq -r '.token // .accessToken // .access_token // .data.token // empty' 2>/dev/null)"
|
||||
USE_COOKIE_AUTH=0
|
||||
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
|
||||
if echo "$TOKEN_RESPONSE" | jq -e '.expires' >/dev/null 2>&1; then
|
||||
USE_COOKIE_AUTH=1
|
||||
else
|
||||
die "NPMplus authentication failed"
|
||||
fi
|
||||
fi
|
||||
|
||||
npm_api() {
|
||||
if [[ "$USE_COOKIE_AUTH" -eq 1 ]]; then
|
||||
curl -sk -b "$NPM_COOKIE_JAR" "$@"
|
||||
else
|
||||
curl -sk -H "Authorization: Bearer $TOKEN" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
HOSTS_JSON="$(npm_api -X GET "$NPM_URL/api/nginx/proxy-hosts")"
|
||||
HOST_ID="$(echo "$HOSTS_JSON" | jq -r --arg domain "$PUBLIC_DOMAIN" '
|
||||
(if type == "array" then . elif .data != null then .data elif .result != null then .result else [] end)
|
||||
| map(select(.domain_names | type == "array"))
|
||||
| map(select(any(.domain_names[]; . == $domain)))
|
||||
| .[0].id // empty
|
||||
')"
|
||||
[[ -n "$HOST_ID" ]] || die "NPMplus proxy host not found for ${PUBLIC_DOMAIN}"
|
||||
|
||||
ADVANCED_CONFIG="$(cat <<CFG
|
||||
location ^~ /api/ {
|
||||
proxy_pass http://${CT_IP}:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_set_header Connection \"\";
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
proxy_read_timeout 24h;
|
||||
proxy_send_timeout 24h;
|
||||
add_header Cache-Control \"no-cache\";
|
||||
}
|
||||
CFG
|
||||
)"
|
||||
|
||||
PAYLOAD="$(echo "$HOSTS_JSON" | jq -c --arg domain "$PUBLIC_DOMAIN" --arg host "$CT_IP" --arg advanced "$ADVANCED_CONFIG" '
|
||||
(if type == "array" then . elif .data != null then .data elif .result != null then .result else [] end)
|
||||
| map(select(.domain_names | type == "array"))
|
||||
| map(select(any(.domain_names[]; . == $domain)))
|
||||
| .[0]
|
||||
| {
|
||||
domain_names,
|
||||
forward_scheme: (.forward_scheme // "http"),
|
||||
forward_host: $host,
|
||||
forward_port: 3000,
|
||||
access_list_id,
|
||||
certificate_id,
|
||||
ssl_forced,
|
||||
caching_enabled,
|
||||
block_exploits,
|
||||
advanced_config: $advanced,
|
||||
allow_websocket_upgrade,
|
||||
http2_support,
|
||||
hsts_enabled,
|
||||
hsts_subdomains,
|
||||
enabled
|
||||
}
|
||||
')"
|
||||
[[ -n "$PAYLOAD" && "$PAYLOAD" != "null" ]] || die "failed to build NPMplus update payload"
|
||||
UPDATE_RESPONSE="$(npm_api -X PUT "$NPM_URL/api/nginx/proxy-hosts/${HOST_ID}" -H 'Content-Type: application/json' -d "$PAYLOAD")"
|
||||
echo "$UPDATE_RESPONSE" | jq -e '.id != null' >/dev/null 2>&1 || die "NPMplus proxy host update failed"
|
||||
|
||||
log "running public smoke checks"
|
||||
HEADERS="$(curl -skI "$PUBLIC_URL/")"
|
||||
echo "$HEADERS" | grep -q '^HTTP/2 200' || die "public root is not HTTP 200"
|
||||
if echo "$HEADERS" | grep -qi '^x-nextjs-prerender:'; then
|
||||
die "old Next.js headers still present on public root"
|
||||
fi
|
||||
|
||||
curl -sk "$PUBLIC_URL/" | grep -F '<title>Solace Bank Group PLC — Treasury Management Portal</title>' >/dev/null || die "public title mismatch"
|
||||
READY_BODY="$(curl -sk "$PUBLIC_URL/api/ready")"
|
||||
echo "$READY_BODY" | grep -F '"ready":true' >/dev/null || die "public /api/ready failed"
|
||||
curl -skN --max-time 5 -H 'Accept: text/event-stream' "$PUBLIC_URL/api/plans/demo-pay-014/status/stream" | grep -F '"type":"connected"' >/dev/null || die "public SSE smoke failed"
|
||||
|
||||
log "capturing EXT-* blocker summary"
|
||||
ssh_remote "pct exec ${VMID} -- journalctl -u currencicombo-orchestrator.service -n 200 --no-pager | grep -E 'ExternalBlockers|EXT-' || true"
|
||||
fi
|
||||
|
||||
log "CurrenciCombo Phoenix deploy completed from ${PHOENIX_DEPLOY_WORKSPACE}"
|
||||
56
scripts/verify/check-gitea-branch-workflow-parity.sh
Normal file
56
scripts/verify/check-gitea-branch-workflow-parity.sh
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
SOURCE_TARGET_PAIRS=(
|
||||
".gitea/workflow-sources/deploy-to-phoenix.yml:.gitea/workflows/deploy-to-phoenix.yml"
|
||||
".gitea/workflow-sources/validate-on-pr.yml:.gitea/workflows/validate-on-pr.yml"
|
||||
)
|
||||
|
||||
REMOTE="${GITEA_WORKFLOW_REMOTE:-origin}"
|
||||
if git remote | grep -qx gitea; then
|
||||
REMOTE="${GITEA_WORKFLOW_REMOTE:-gitea}"
|
||||
fi
|
||||
|
||||
missing_ref=false
|
||||
for ref in "$REMOTE/main" "$REMOTE/master"; do
|
||||
if ! git rev-parse --verify "$ref" >/dev/null 2>&1; then
|
||||
missing_ref=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$missing_ref" == true ]]; then
|
||||
echo "[i] Skipping main/master workflow parity check ($REMOTE/main or $REMOTE/master not available)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for pair in "${SOURCE_TARGET_PAIRS[@]}"; do
|
||||
source="${pair%%:*}"
|
||||
target="${pair##*:}"
|
||||
|
||||
main_blob="$(git show "$REMOTE/main:$source" 2>/dev/null || true)"
|
||||
master_blob="$(git show "$REMOTE/master:$source" 2>/dev/null || true)"
|
||||
|
||||
if [[ -z "$main_blob" ]]; then
|
||||
main_blob="$(git show "$REMOTE/main:$target" 2>/dev/null || true)"
|
||||
fi
|
||||
if [[ -z "$master_blob" ]]; then
|
||||
master_blob="$(git show "$REMOTE/master:$target" 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "$main_blob" || -z "$master_blob" ]]; then
|
||||
echo "[✗] Missing $source/$target on $REMOTE/main or $REMOTE/master" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$main_blob" != "$master_blob" ]]; then
|
||||
echo "[✗] Branch workflow drift: $source differs between $REMOTE/main and $REMOTE/master" >&2
|
||||
echo " Keep both deploy branches in lockstep for workflow-source files." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[✓] Branch parity OK for $source"
|
||||
done
|
||||
32
scripts/verify/check-gitea-workflows.sh
Normal file
32
scripts/verify/check-gitea-workflows.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
check_one() {
|
||||
local source_rel="$1"
|
||||
local target_rel="$2"
|
||||
|
||||
if [[ ! -f "$source_rel" ]]; then
|
||||
echo "[✗] Missing workflow source: $source_rel" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$target_rel" ]]; then
|
||||
echo "[✗] Missing generated workflow: $target_rel" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! diff -u "$source_rel" "$target_rel" >/dev/null; then
|
||||
echo "[✗] Workflow drift detected: $target_rel does not match $source_rel" >&2
|
||||
echo " Run: bash scripts/verify/sync-gitea-workflows.sh" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "[✓] $target_rel matches $source_rel"
|
||||
}
|
||||
|
||||
check_one ".gitea/workflow-sources/deploy-to-phoenix.yml" ".gitea/workflows/deploy-to-phoenix.yml"
|
||||
check_one ".gitea/workflow-sources/validate-on-pr.yml" ".gitea/workflows/validate-on-pr.yml"
|
||||
50
scripts/verify/check-pnpm-workspace-lockfile.sh
Executable file
50
scripts/verify/check-pnpm-workspace-lockfile.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# Every path listed under "packages:" in pnpm-workspace.yaml must have a matching
|
||||
# importer entry in pnpm-lock.yaml. If one is missing, pnpm can fail in confusing
|
||||
# ways (e.g. pnpm outdated -r: Cannot read ... 'optionalDependencies').
|
||||
# Usage: bash scripts/verify/check-pnpm-workspace-lockfile.sh
|
||||
# Exit: 0 if check passes or pnpm is not used; 1 on mismatch.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
WS="${PROJECT_ROOT}/pnpm-workspace.yaml"
|
||||
LOCK="${PROJECT_ROOT}/pnpm-lock.yaml"
|
||||
|
||||
if [[ ! -f "$WS" ]] || [[ ! -f "$LOCK" ]]; then
|
||||
echo " (skip: pnpm-workspace.yaml or pnpm-lock.yaml not present at repo root)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Paths under the top-level `packages:` block only (stops at next top-level key)
|
||||
mapfile -t _paths < <(awk '
|
||||
/^packages:/ { p=1; next }
|
||||
p && /^[a-zA-Z]/ && $0 !~ /^packages/ { exit }
|
||||
p && /^[[:space:]]*-[[:space:]]/ {
|
||||
sub(/^[[:space:]]*-[[:space:]]+/, "")
|
||||
sub(/[[:space:]]*#.*/, "")
|
||||
gsub(/[[:space:]]+$/, "")
|
||||
if (length) print
|
||||
}
|
||||
' "$WS")
|
||||
|
||||
missing=()
|
||||
for relp in "${_paths[@]}"; do
|
||||
if [[ -z "$relp" ]]; then
|
||||
continue
|
||||
fi
|
||||
if ! grep -qFx " ${relp}:" "$LOCK"; then
|
||||
missing+=("$relp")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
echo "✗ pnpm lockfile is missing importer(s) for these workspace path(s):"
|
||||
printf ' %q\n' "${missing[@]}"
|
||||
echo " Run: pnpm install (at repo root) to refresh pnpm-lock.yaml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " pnpm workspace / lockfile importers aligned (${#_paths[@]} path(s))."
|
||||
exit 0
|
||||
@@ -3,6 +3,7 @@
|
||||
# Use for CI or pre-deploy: dependencies, config files, optional genesis.
|
||||
# Usage: bash scripts/verify/run-all-validation.sh [--skip-genesis]
|
||||
# --skip-genesis: do not run validate-genesis.sh (default: run if smom-dbis-138 present).
|
||||
# Steps: dependencies, config files, cW* mesh matrix (if pair-discovery JSON exists), genesis.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -24,15 +25,64 @@ bash "$SCRIPT_DIR/check-dependencies.sh" || log_err "check-dependencies failed"
|
||||
log_ok "Dependencies OK"
|
||||
echo ""
|
||||
|
||||
echo "1b. pnpm workspace vs lockfile..."
|
||||
if [[ -f "$PROJECT_ROOT/pnpm-workspace.yaml" ]]; then
|
||||
bash "$SCRIPT_DIR/check-pnpm-workspace-lockfile.sh" || log_err "pnpm lockfile / workspace drift"
|
||||
log_ok "pnpm lockfile aligned with workspace"
|
||||
else
|
||||
echo " (no pnpm-workspace.yaml at root — skip)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "1c. Gitea workflow source sync..."
|
||||
bash "$SCRIPT_DIR/check-gitea-workflows.sh" || log_err "Gitea workflow source drift"
|
||||
log_ok "Gitea workflows match source-of-truth files"
|
||||
echo ""
|
||||
|
||||
echo "1d. main/master workflow parity..."
|
||||
bash "$SCRIPT_DIR/check-gitea-branch-workflow-parity.sh" || log_err "main/master workflow parity drift"
|
||||
log_ok "main/master workflow parity OK"
|
||||
echo ""
|
||||
|
||||
echo "2. Config files..."
|
||||
bash "$SCRIPT_DIR/../validation/validate-config-files.sh" || log_err "validate-config-files failed"
|
||||
log_ok "Config validation OK"
|
||||
echo ""
|
||||
|
||||
if [[ "$SKIP_GENESIS" == true ]]; then
|
||||
echo "3. Genesis — skipped (--skip-genesis)"
|
||||
echo "3. cW* mesh matrix (deployment-status + Uni V2 pair-discovery)..."
|
||||
DISCOVERY_JSON="$PROJECT_ROOT/reports/extraction/promod-uniswap-v2-live-pair-discovery-latest.json"
|
||||
if [[ -f "$DISCOVERY_JSON" ]]; then
|
||||
MATRIX_JSON="$PROJECT_ROOT/reports/status/cw-mesh-deployment-matrix-latest.json"
|
||||
bash "$SCRIPT_DIR/build-cw-mesh-deployment-matrix.sh" --no-markdown --json-out "$MATRIX_JSON" || log_err "cw mesh matrix merge failed"
|
||||
log_ok "cW mesh matrix OK (also wrote $MATRIX_JSON)"
|
||||
else
|
||||
echo "3. Genesis (smom-dbis-138)..."
|
||||
echo " ($DISCOVERY_JSON missing — run: bash scripts/verify/build-promod-uniswap-v2-live-pair-discovery.sh)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "3b. deployment-status graph (cross-chain-pmm-lps)..."
|
||||
PMM_VALIDATE="$PROJECT_ROOT/cross-chain-pmm-lps/scripts/validate-deployment-status.cjs"
|
||||
if [[ -f "$PMM_VALIDATE" ]] && command -v node &>/dev/null; then
|
||||
node "$PMM_VALIDATE" || log_err "validate-deployment-status.cjs failed"
|
||||
log_ok "deployment-status.json rules OK"
|
||||
else
|
||||
echo " (skip: node or $PMM_VALIDATE missing)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "3c. External dependency blockers..."
|
||||
EXT_CHECK="$SCRIPT_DIR/check-external-dependencies.sh"
|
||||
if [[ -x "$EXT_CHECK" ]]; then
|
||||
bash "$EXT_CHECK" --advisory || true
|
||||
else
|
||||
echo " (skip: $EXT_CHECK missing)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
if [[ "$SKIP_GENESIS" == true ]]; then
|
||||
echo "4. Genesis — skipped (--skip-genesis)"
|
||||
else
|
||||
echo "4. Genesis (smom-dbis-138)..."
|
||||
GENESIS_SCRIPT="$PROJECT_ROOT/smom-dbis-138/scripts/validation/validate-genesis.sh"
|
||||
if [[ -x "$GENESIS_SCRIPT" ]]; then
|
||||
bash "$GENESIS_SCRIPT" || log_err "validate-genesis failed"
|
||||
|
||||
18
scripts/verify/sync-gitea-workflows.sh
Normal file
18
scripts/verify/sync-gitea-workflows.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
sync_one() {
|
||||
local source_rel="$1"
|
||||
local target_rel="$2"
|
||||
|
||||
mkdir -p "$(dirname "$target_rel")"
|
||||
cp "$source_rel" "$target_rel"
|
||||
echo "[✓] Synced $target_rel from $source_rel"
|
||||
}
|
||||
|
||||
sync_one ".gitea/workflow-sources/deploy-to-phoenix.yml" ".gitea/workflows/deploy-to-phoenix.yml"
|
||||
sync_one ".gitea/workflow-sources/validate-on-pr.yml" ".gitea/workflows/validate-on-pr.yml"
|
||||
524
token-list.json
524
token-list.json
@@ -1,29 +1,21 @@
|
||||
{
|
||||
"name": "DBIS Chain 138 Token List",
|
||||
"name": "SMOM-DBIS-138 Token List",
|
||||
"version": {
|
||||
"major": 1,
|
||||
"minor": 7,
|
||||
"patch": 4
|
||||
"minor": 1,
|
||||
"patch": 2
|
||||
},
|
||||
"timestamp": "2026-04-14T22:00:00.000Z",
|
||||
"keywords": [
|
||||
"dbis",
|
||||
"chain138",
|
||||
"defi oracle meta"
|
||||
],
|
||||
"logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/chain138-list.svg",
|
||||
"timestamp": "2025-12-24T00:00:00.000Z",
|
||||
"logoURI": "https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png",
|
||||
"tokens": [
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6",
|
||||
"address": "0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6",
|
||||
"name": "ETH/USD Price Feed",
|
||||
"symbol": "ETH-USD",
|
||||
"decimals": 8,
|
||||
"logoURI": "https://ipfs.io/ipfs/QmPZuycjyJEe2otREuQ5HirvPJ8X6Yc6MBtwz1VhdD79pY",
|
||||
"tags": [
|
||||
"oracle",
|
||||
"pricefeed"
|
||||
]
|
||||
"logoURI": "https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png",
|
||||
"tags": ["oracle", "price-feed"]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
@@ -31,23 +23,17 @@
|
||||
"name": "Wrapped Ether",
|
||||
"symbol": "WETH",
|
||||
"decimals": 18,
|
||||
"logoURI": "https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong",
|
||||
"tags": [
|
||||
"defi",
|
||||
"wrapped"
|
||||
]
|
||||
"logoURI": "https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png",
|
||||
"tags": ["defi", "wrapped"]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F",
|
||||
"address": "0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f",
|
||||
"name": "Wrapped Ether v10",
|
||||
"symbol": "WETH10",
|
||||
"decimals": 18,
|
||||
"logoURI": "https://ipfs.io/ipfs/QmanDFPHxnbKd6SSNzzXHf9GbpL9dLXSphxDZSPPYE6ds4",
|
||||
"tags": [
|
||||
"defi",
|
||||
"wrapped"
|
||||
]
|
||||
"logoURI": "https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png",
|
||||
"tags": ["defi", "wrapped"]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
@@ -55,454 +41,8 @@
|
||||
"name": "Chainlink Token",
|
||||
"symbol": "LINK",
|
||||
"decimals": 18,
|
||||
"logoURI": "https://ipfs.io/ipfs/QmenWcmfNGfssz4HXvrRV912eZDiKqLTt6z2brRYuTGz9A",
|
||||
"tags": [
|
||||
"defi",
|
||||
"oracle",
|
||||
"ccip"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22",
|
||||
"name": "Tether USD (Compliant)",
|
||||
"symbol": "cUSDT",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://explorer.d-bis.org/token-icons/cUSDT.png",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi",
|
||||
"compliant"
|
||||
],
|
||||
"extensions": {
|
||||
"currencyCode": "USD",
|
||||
"gruVersion": "v1",
|
||||
"forwardCanonical": false,
|
||||
"x402Ready": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x9FBfab33882Efe0038DAa608185718b772EE5660",
|
||||
"name": "Tether USD (Compliant V2)",
|
||||
"symbol": "cUSDT",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://explorer.d-bis.org/token-icons/cUSDT.png",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi",
|
||||
"compliant",
|
||||
"v2",
|
||||
"x402",
|
||||
"fwdcanon"
|
||||
],
|
||||
"extensions": {
|
||||
"currencyCode": "USD",
|
||||
"gruVersion": "v2",
|
||||
"forwardCanonical": true,
|
||||
"x402Ready": true,
|
||||
"explorerLabel": "Chain 138 cUSDT V2 forward canonical"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xf22258f57794CC8E06237084b353Ab30fFfa640b",
|
||||
"name": "USD Coin (Compliant)",
|
||||
"symbol": "cUSDC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://explorer.d-bis.org/token-icons/cUSDC.png",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi",
|
||||
"compliant"
|
||||
],
|
||||
"extensions": {
|
||||
"currencyCode": "USD",
|
||||
"gruVersion": "v1",
|
||||
"forwardCanonical": false,
|
||||
"x402Ready": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x219522c60e83dEe01FC5b0329d6fA8fD84b9D13d",
|
||||
"name": "USD Coin (Compliant V2)",
|
||||
"symbol": "cUSDC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://explorer.d-bis.org/token-icons/cUSDC.png",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi",
|
||||
"compliant",
|
||||
"v2",
|
||||
"x402",
|
||||
"fwdcanon"
|
||||
],
|
||||
"extensions": {
|
||||
"currencyCode": "USD",
|
||||
"gruVersion": "v2",
|
||||
"forwardCanonical": true,
|
||||
"x402Ready": true,
|
||||
"explorerLabel": "Chain 138 cUSDC V2 forward canonical"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x71D6687F38b93CCad569Fa6352c876eea967201b",
|
||||
"name": "USD Coin (Chain 138)",
|
||||
"symbol": "USDC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1",
|
||||
"name": "Tether USD (Chain 138)",
|
||||
"symbol": "USDT",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x8085961F9cF02b4d800A3c6d386D31da4B34266a",
|
||||
"name": "Euro Coin (Compliant)",
|
||||
"symbol": "cEURC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cEURC.svg",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi",
|
||||
"compliant"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xdf4b71c61E5912712C1Bdd451416B9aC26949d72",
|
||||
"name": "Tether EUR (Compliant)",
|
||||
"symbol": "cEURT",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cEURT.svg",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi",
|
||||
"compliant"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x003960f16D9d34F2e98d62723B6721Fb92074aD2",
|
||||
"name": "Pound Sterling (Compliant)",
|
||||
"symbol": "cGBPC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cGBPC.svg",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi",
|
||||
"compliant"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x350f54e4D23795f86A9c03988c7135357CCaD97c",
|
||||
"name": "Tether GBP (Compliant)",
|
||||
"symbol": "cGBPT",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cGBPT.svg",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi",
|
||||
"compliant"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xD51482e567c03899eecE3CAe8a058161FD56069D",
|
||||
"name": "Australian Dollar (Compliant)",
|
||||
"symbol": "cAUDC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cAUDC.svg",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi",
|
||||
"compliant"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xEe269e1226a334182aace90056EE4ee5Cc8A6770",
|
||||
"name": "Japanese Yen (Compliant)",
|
||||
"symbol": "cJPYC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cJPYC.svg",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi",
|
||||
"compliant"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x873990849DDa5117d7C644f0aF24370797C03885",
|
||||
"name": "Swiss Franc (Compliant)",
|
||||
"symbol": "cCHFC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cCHFC.svg",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi",
|
||||
"compliant"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x54dBd40cF05e15906A2C21f600937e96787f5679",
|
||||
"name": "Canadian Dollar (Compliant)",
|
||||
"symbol": "cCADC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://raw.githubusercontent.com/Order-of-Hospitallers/proxmox-cp/main/token-lists/logos/gru/cCADC.svg",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi",
|
||||
"compliant"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x290E52a8819A4fbD0714E517225429aA2B70EC6b",
|
||||
"name": "Gold (Compliant)",
|
||||
"symbol": "cXAUC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://explorer.d-bis.org/token-icons/cXAUC.png",
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant"
|
||||
],
|
||||
"extensions": {
|
||||
"unitOfAccount": "troy_ounce",
|
||||
"unitDescription": "1 full token = 1 troy oz fine gold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x94e408E26c6FD8F4ee00b54dF19082FDA07dC96E",
|
||||
"name": "Tether XAU (Compliant)",
|
||||
"symbol": "cXAUT",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://explorer.d-bis.org/token-icons/cXAUT.png",
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant"
|
||||
],
|
||||
"extensions": {
|
||||
"unitOfAccount": "troy_ounce",
|
||||
"unitDescription": "1 full token = 1 troy oz fine gold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xF8e9802a1766422e33a269556AD5fC032338EeFc",
|
||||
"name": "Ethereum Mainnet Gas (Compliant)",
|
||||
"symbol": "cETH",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_mainnet",
|
||||
"mirroredSymbol": "cWETH",
|
||||
"backingMode": "strict_escrow",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x18A6B163d255cC0Cb32B99697843b487D059907d",
|
||||
"name": "Ethereum L2 Gas (Compliant)",
|
||||
"symbol": "cETHL2",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "eth_l2",
|
||||
"mirroredSymbol": "cWETHL2",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_fungible_inventory_gated",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x94517875f32680984a5921DA7abf78E22f65d70F",
|
||||
"name": "BNB Gas (Compliant)",
|
||||
"symbol": "cBNB",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/smartchain/info/logo.png",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "bnb",
|
||||
"mirroredSymbol": "cWBNB",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x19fec21eDedE9EdB91D1FbeC7C4095C5Ac1FC10c",
|
||||
"name": "Polygon Gas (Compliant)",
|
||||
"symbol": "cPOL",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/info/logo.png",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "pol",
|
||||
"mirroredSymbol": "cWPOL",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x9c8f6F4fe1e535e6F85a78Ee95249deD5540A2D0",
|
||||
"name": "Avalanche Gas (Compliant)",
|
||||
"symbol": "cAVAX",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/avalanchec/info/logo.png",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "avax",
|
||||
"mirroredSymbol": "cWAVAX",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xFb48bb6c623571F9795B535B73f0D660E8643fd4",
|
||||
"name": "Cronos Gas (Compliant)",
|
||||
"symbol": "cCRO",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/cronos/info/logo.png",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "cro",
|
||||
"mirroredSymbol": "cWCRO",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xA39A59138F8a45b4a31455875b8A29FF31E76Ec8",
|
||||
"name": "Gnosis Gas (Compliant)",
|
||||
"symbol": "cXDAI",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/xdai/info/logo.png",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "xdai",
|
||||
"mirroredSymbol": "cWXDAI",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x1814661DAE0933Bf397b1274f5a62411B94195C7",
|
||||
"name": "Celo Gas (Compliant)",
|
||||
"symbol": "cCELO",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/celo/info/logo.png",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "celo",
|
||||
"mirroredSymbol": "cWCELO",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x4D82206BeC5B4Dfa17759FFEDE07E35f4f63a050",
|
||||
"name": "Wemix Gas (Compliant)",
|
||||
"symbol": "cWEMIX",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/wemix/info/logo.png",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
"familyKey": "wemix",
|
||||
"mirroredSymbol": "cWWEMIX",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"canonicalChainId": 138,
|
||||
"wave": "wave1"
|
||||
}
|
||||
"logoURI": "https://raw.githubusercontent.com/chainlink/chainlink-docs/main/docs/images/chainlink-logo.svg",
|
||||
"tags": ["defi", "oracle", "ccip"]
|
||||
}
|
||||
],
|
||||
"tags": {
|
||||
@@ -518,41 +58,9 @@
|
||||
"name": "Oracle",
|
||||
"description": "Oracle price feed tokens"
|
||||
},
|
||||
"pricefeed": {
|
||||
"price-feed": {
|
||||
"name": "Price Feed",
|
||||
"description": "Price feed oracle contracts"
|
||||
},
|
||||
"stablecoin": {
|
||||
"name": "Stablecoin",
|
||||
"description": "Stable value tokens pegged to fiat currencies"
|
||||
},
|
||||
"compliant": {
|
||||
"name": "Compliant",
|
||||
"description": "Regulatory compliant tokens with compliance features"
|
||||
},
|
||||
"ccip": {
|
||||
"name": "CCIP",
|
||||
"description": "Cross Chain Interoperability Protocol tokens"
|
||||
},
|
||||
"v2": {
|
||||
"name": "Version 2",
|
||||
"description": "Next generation GRU token contracts"
|
||||
},
|
||||
"x402": {
|
||||
"name": "x402 Ready",
|
||||
"description": "Permit or authorization capable payment tokens"
|
||||
},
|
||||
"staged": {
|
||||
"name": "Staged",
|
||||
"description": "Deployed and discoverable, not yet canonical"
|
||||
},
|
||||
"fwdcanon": {
|
||||
"name": "Forward canonical",
|
||||
"description": "GRU v2 forward canonical token variant"
|
||||
},
|
||||
"gasnative": {
|
||||
"name": "Gas native",
|
||||
"description": "Gas native compliant families and wrapped gas mirrors"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
**Date**: 2026-01-26
|
||||
**Status**: ✅ **100% COMPLETE**
|
||||
|
||||
> **2026-04-14 note:** This file is a historical milestone snapshot. Current canonical Chain 138 inventory is in `docs/11-references/EXPLORER_TOKEN_LIST_CROSSCHECK.md` §5 and `lists/dbis-138.tokenlist.json` (29 entries as of 1.7.4).
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Executive Summary
|
||||
|
||||
@@ -59,19 +59,18 @@ token-lists/
|
||||
|
||||
## Token List Contents
|
||||
|
||||
Current version: **1.7.4**
|
||||
Current version: **1.7.0**
|
||||
|
||||
Canonical currency and lifecycle state are also tracked in [`../config/gru-iso4217-currency-manifest.json`](../config/gru-iso4217-currency-manifest.json). The token list is the wallet/explorer-facing projection of that broader GRU asset inventory. Canonical GRU SVG logos now live under [`logos/gru/`](logos/gru) and are referenced through raw HTTPS URLs so wallets and explorers can use the same source artwork.
|
||||
|
||||
### Tokens
|
||||
|
||||
Current token count: **29** (includes official mirror **USDT/USDC**, Wave 1 gas-family `c*` entries, and the ETH-USD oracle row).
|
||||
Current token count: **18**
|
||||
|
||||
Notable current state:
|
||||
- `cUSDT` and `cUSDC` each have both V1 and V2 entries on Chain 138.
|
||||
- Duplicate symbols for V1/V2 are intentional during coexistence and cutover.
|
||||
- V2 entries are forward-canonical x402 surfaces; **Uniswap Token Lists schema** allows only short `extensions` string values, so long `ipfs://…` disclosure URIs are **not** embedded in the JSON list (see on-chain `tokenURI` / [`CONTRACT_ADDRESSES_REFERENCE.md`](../docs/11-references/CONTRACT_ADDRESSES_REFERENCE.md)). V2 rows use tag **`fwdcanon`** (≤10 characters per schema).
|
||||
- Gas-family rows use tag **`gasnative`** and keep transport metadata under `extensions` only (no extra top-level token fields), per the same schema.
|
||||
- The V2 entries are the forward-canonical x402 surfaces and include IPFS-backed metadata/disclosure/reporting URIs in `extensions`.
|
||||
- Non-USD GRU currencies currently covered in the list are `EUR`, `GBP`, `AUD`, `JPY`, `CHF`, `CAD`, and `XAU`.
|
||||
|
||||
1. **ETH/USD Price Feed** (Oracle)
|
||||
@@ -114,26 +113,12 @@ Notable current state:
|
||||
- Decimals: 6
|
||||
- Category: Stablecoin, DeFi, Compliant, V2, x402-ready
|
||||
|
||||
9. **USDC** / **USDT** (Chain 138 mirror labels; D3 official mirror addresses) — see [`docs/11-references/EXPLORER_TOKEN_LIST_CROSSCHECK.md`](../docs/11-references/EXPLORER_TOKEN_LIST_CROSSCHECK.md) §5.
|
||||
|
||||
10. **Additional GRU currencies**
|
||||
9. **Additional GRU currencies**
|
||||
- `cEURC`, `cEURT`, `cGBPC`, `cGBPT`, `cAUDC`, `cJPYC`, `cCHFC`, `cCADC`, `cXAUC`, `cXAUT`
|
||||
- See `lists/dbis-138.tokenlist.json` for the full current set.
|
||||
|
||||
---
|
||||
|
||||
## Checksums (dbis-138)
|
||||
|
||||
After editing **`lists/dbis-138.tokenlist.json`**, refresh integrity checksum (same output as release workflow):
|
||||
|
||||
```bash
|
||||
bash scripts/update-sha256sums.sh
|
||||
```
|
||||
|
||||
Committed file: **`lists/SHA256SUMS`**. **minisign** signature (`.sig`) is produced in CI or locally via **`scripts/sign-list.sh`** when a private key is available.
|
||||
|
||||
---
|
||||
|
||||
## Validation
|
||||
|
||||
All token lists are validated against:
|
||||
@@ -143,7 +128,7 @@ All token lists are validated against:
|
||||
- Chain ID strict validation (must be 138)
|
||||
- Duplicate detection (addresses and symbols)
|
||||
- Logo URL accessibility
|
||||
- On-chain contract verification (`verify-on-chain.js`: **pricefeed**-tagged rows use oracle ABI; **LINK** uses ERC-20; canonical **WETH9** may return `decimals() == 0` on-chain while the list keeps **18** — logged as a warning)
|
||||
- On-chain contract verification
|
||||
- Expected duplicate-symbol warnings during V1/V2 coexistence
|
||||
|
||||
### Running Validations
|
||||
|
||||
@@ -25,34 +25,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## [Unreleased]
|
||||
|
||||
### Planned
|
||||
- Additional token entries as requested
|
||||
- Enhanced metadata and documentation
|
||||
- Logo updates for controlled hosting
|
||||
|
||||
---
|
||||
|
||||
## [1.7.4] - 2026-04-14
|
||||
|
||||
### Added
|
||||
- Official mirror **USDT** / **USDC** (Chain 138) rows; Wave 1 gas-family `c*` entries with schema-safe **`gasnative`** tag; **`fwdcanon`** tag for GRU V2 USD surfaces.
|
||||
- **`lists/SHA256SUMS`** for `dbis-138.tokenlist.json` (integrity helper; release workflow regenerates on tag).
|
||||
|
||||
### Changed
|
||||
- V2 **`extensions`**: removed long `ipfs://` URI strings from the JSON list (Uniswap schema length); on-chain `tokenURI` remains canonical per contract docs.
|
||||
- Display **`name`** fields aligned with on-chain token metadata where verify surfaced drift.
|
||||
- Gas rows: transport fields only under **`extensions`**; EIP-55 on all affected addresses; tag **`gasnative`** (replaces hyphenated tag id for schema).
|
||||
|
||||
### Security
|
||||
- **`verify-on-chain.js`**: oracle ABI only for **`pricefeed`**-tagged rows; **LINK** verified as ERC-20; canonical **WETH9** decimals mismatch downgraded to warning when list keeps 18 for UX.
|
||||
|
||||
---
|
||||
|
||||
## [1.7.3] - 2026-04-14
|
||||
|
||||
### Changed
|
||||
- Uniswap schema compliance sweep (tags ≤10 chars, no extra top-level token properties, root tag map keys).
|
||||
|
||||
---
|
||||
|
||||
[1.1.0]: https://github.com/dbis/token-lists/releases/tag/v1.1.0
|
||||
[1.7.3]: https://github.com/Order-of-Hospitallers/proxmox-cp/releases
|
||||
[1.7.4]: https://github.com/Order-of-Hospitallers/proxmox-cp/releases
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
6beddb60a7c8a355bb5b5ac02aed82c701323c07ce0c216b3e3370a5f6e6c6ef dbis-138.tokenlist.json
|
||||
@@ -41,16 +41,20 @@
|
||||
},
|
||||
{
|
||||
"chainId": 42161,
|
||||
"address": "0xE27bE001BC55cb2a8ED5Ba5A62C834cA135244a3",
|
||||
"address": "0xe27be001bc55cb2a8ed5ba5a62c834ca135244a3",
|
||||
"name": "Wrapped Ethereum L2 Gas (Compliant)",
|
||||
"symbol": "cWETHL2",
|
||||
"familyKey": "eth_l2",
|
||||
"canonicalSymbol": "cETHL2",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_fungible_inventory_gated",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -72,9 +76,9 @@
|
||||
"name": "Wrapped",
|
||||
"description": "Wrapped tokens representing native assets"
|
||||
},
|
||||
"gasnative": {
|
||||
"name": "Gas native",
|
||||
"description": "Gas native compliant families and wrapped gas mirrors"
|
||||
"gas-native": {
|
||||
"name": "Gas Native",
|
||||
"description": "Gas-native compliant families and wrapped gas mirrors"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,16 +41,20 @@
|
||||
},
|
||||
{
|
||||
"chainId": 43114,
|
||||
"address": "0xE1D4Aee2ef8f48A20338935188A8fe7f7c7dE7d0",
|
||||
"address": "0xe1d4aee2ef8f48a20338935188a8fe7f7c7de7d0",
|
||||
"name": "Wrapped AVAX Gas (Compliant)",
|
||||
"symbol": "cWAVAX",
|
||||
"familyKey": "avax",
|
||||
"canonicalSymbol": "cAVAX",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/avalanchec/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -72,9 +76,9 @@
|
||||
"name": "Wrapped",
|
||||
"description": "Wrapped tokens representing native assets"
|
||||
},
|
||||
"gasnative": {
|
||||
"name": "Gas native",
|
||||
"description": "Gas native compliant families and wrapped gas mirrors"
|
||||
"gas-native": {
|
||||
"name": "Gas Native",
|
||||
"description": "Gas-native compliant families and wrapped gas mirrors"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,16 +138,20 @@
|
||||
},
|
||||
{
|
||||
"chainId": 25,
|
||||
"address": "0x9b10eB0f77c45322DBd1fCb07176Fd9A7609c164",
|
||||
"address": "0x9b10eb0f77c45322dbd1fcb07176fd9a7609c164",
|
||||
"name": "Wrapped CRO Gas (Compliant)",
|
||||
"symbol": "cWCRO",
|
||||
"familyKey": "cro",
|
||||
"canonicalSymbol": "cCRO",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/cronos/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -185,9 +189,9 @@
|
||||
"name": "CCIP",
|
||||
"description": "Cross Chain Interoperability Protocol tokens"
|
||||
},
|
||||
"gasnative": {
|
||||
"name": "Gas native",
|
||||
"description": "Gas native compliant families and wrapped gas mirrors"
|
||||
"gas-native": {
|
||||
"name": "Gas Native",
|
||||
"description": "Gas-native compliant families and wrapped gas mirrors"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
"version": {
|
||||
"major": 1,
|
||||
"minor": 7,
|
||||
"patch": 4
|
||||
"patch": 1
|
||||
},
|
||||
"timestamp": "2026-04-14T22:00:00.000Z",
|
||||
"timestamp": "2026-04-05T23:30:00.000Z",
|
||||
"keywords": [
|
||||
"dbis",
|
||||
"chain138",
|
||||
@@ -65,7 +65,7 @@
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x93E66202A11B1772E55407B32B44e5Cd8eda7f22",
|
||||
"name": "Tether USD (Compliant)",
|
||||
"name": "Compliant Tether USD",
|
||||
"symbol": "cUSDT",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://explorer.d-bis.org/token-icons/cUSDT.png",
|
||||
@@ -84,7 +84,7 @@
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x9FBfab33882Efe0038DAa608185718b772EE5660",
|
||||
"name": "Tether USD (Compliant V2)",
|
||||
"name": "Compliant Tether USD V2",
|
||||
"symbol": "cUSDT",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://explorer.d-bis.org/token-icons/cUSDT.png",
|
||||
@@ -94,20 +94,23 @@
|
||||
"compliant",
|
||||
"v2",
|
||||
"x402",
|
||||
"fwdcanon"
|
||||
"forward-canonical"
|
||||
],
|
||||
"extensions": {
|
||||
"currencyCode": "USD",
|
||||
"gruVersion": "v2",
|
||||
"forwardCanonical": true,
|
||||
"x402Ready": true,
|
||||
"explorerLabel": "Chain 138 cUSDT V2 forward canonical"
|
||||
"explorerLabel": "Chain 138 cUSDT V2 (forward-canonical)",
|
||||
"metadataURI": "ipfs://QmNQ61ABKhBPsZY2UEE7fPbMdV6EUWJxWyML2eWXLn2d9M/cusdt-v2/metadata.json",
|
||||
"regulatoryDisclosureURI": "ipfs://QmNQ61ABKhBPsZY2UEE7fPbMdV6EUWJxWyML2eWXLn2d9M/cusdt-v2/regulatory-disclosure.json",
|
||||
"reportingURI": "ipfs://QmNQ61ABKhBPsZY2UEE7fPbMdV6EUWJxWyML2eWXLn2d9M/cusdt-v2/reporting.json"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xf22258f57794CC8E06237084b353Ab30fFfa640b",
|
||||
"name": "USD Coin (Compliant)",
|
||||
"name": "Compliant USD Coin",
|
||||
"symbol": "cUSDC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://explorer.d-bis.org/token-icons/cUSDC.png",
|
||||
@@ -126,7 +129,7 @@
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x219522c60e83dEe01FC5b0329d6fA8fD84b9D13d",
|
||||
"name": "USD Coin (Compliant V2)",
|
||||
"name": "Compliant USD Coin V2",
|
||||
"symbol": "cUSDC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://explorer.d-bis.org/token-icons/cUSDC.png",
|
||||
@@ -136,40 +139,19 @@
|
||||
"compliant",
|
||||
"v2",
|
||||
"x402",
|
||||
"fwdcanon"
|
||||
"forward-canonical"
|
||||
],
|
||||
"extensions": {
|
||||
"currencyCode": "USD",
|
||||
"gruVersion": "v2",
|
||||
"forwardCanonical": true,
|
||||
"x402Ready": true,
|
||||
"explorerLabel": "Chain 138 cUSDC V2 forward canonical"
|
||||
"explorerLabel": "Chain 138 cUSDC V2 (forward-canonical)",
|
||||
"metadataURI": "ipfs://QmNQ61ABKhBPsZY2UEE7fPbMdV6EUWJxWyML2eWXLn2d9M/cusdc-v2/metadata.json",
|
||||
"regulatoryDisclosureURI": "ipfs://QmNQ61ABKhBPsZY2UEE7fPbMdV6EUWJxWyML2eWXLn2d9M/cusdc-v2/regulatory-disclosure.json",
|
||||
"reportingURI": "ipfs://QmNQ61ABKhBPsZY2UEE7fPbMdV6EUWJxWyML2eWXLn2d9M/cusdc-v2/reporting.json"
|
||||
}
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x71D6687F38b93CCad569Fa6352c876eea967201b",
|
||||
"name": "USD Coin (Chain 138)",
|
||||
"symbol": "USDC",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1",
|
||||
"name": "Tether USD (Chain 138)",
|
||||
"symbol": "USDT",
|
||||
"decimals": 6,
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png",
|
||||
"tags": [
|
||||
"stablecoin",
|
||||
"defi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x8085961F9cF02b4d800A3c6d386D31da4B34266a",
|
||||
@@ -308,15 +290,19 @@
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xF8e9802a1766422e33a269556AD5fC032338EeFc",
|
||||
"address": "0xf8e9802a1766422e33a269556ad5fc032338eefc",
|
||||
"name": "Ethereum Mainnet Gas (Compliant)",
|
||||
"symbol": "cETH",
|
||||
"familyKey": "eth_mainnet",
|
||||
"mirroredSymbol": "cWETH",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
|
||||
"backingMode": "strict_escrow",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -330,15 +316,19 @@
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x18A6B163d255cC0Cb32B99697843b487D059907d",
|
||||
"address": "0x18a6b163d255cc0cb32b99697843b487d059907d",
|
||||
"name": "Ethereum L2 Gas (Compliant)",
|
||||
"symbol": "cETHL2",
|
||||
"familyKey": "eth_l2",
|
||||
"mirroredSymbol": "cWETHL2",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_fungible_inventory_gated",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -352,15 +342,19 @@
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x94517875f32680984a5921DA7abf78E22f65d70F",
|
||||
"address": "0x94517875f32680984a5921da7abf78e22f65d70f",
|
||||
"name": "BNB Gas (Compliant)",
|
||||
"symbol": "cBNB",
|
||||
"familyKey": "bnb",
|
||||
"mirroredSymbol": "cWBNB",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/smartchain/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -374,15 +368,19 @@
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x19fec21eDedE9EdB91D1FbeC7C4095C5Ac1FC10c",
|
||||
"address": "0x19fec21edede9edb91d1fbec7c4095c5ac1fc10c",
|
||||
"name": "Polygon Gas (Compliant)",
|
||||
"symbol": "cPOL",
|
||||
"familyKey": "pol",
|
||||
"mirroredSymbol": "cWPOL",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -396,15 +394,19 @@
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x9c8f6F4fe1e535e6F85a78Ee95249deD5540A2D0",
|
||||
"address": "0x9c8f6f4fe1e535e6f85a78ee95249ded5540a2d0",
|
||||
"name": "Avalanche Gas (Compliant)",
|
||||
"symbol": "cAVAX",
|
||||
"familyKey": "avax",
|
||||
"mirroredSymbol": "cWAVAX",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/avalanchec/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -418,15 +420,19 @@
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xFb48bb6c623571F9795B535B73f0D660E8643fd4",
|
||||
"address": "0xfb48bb6c623571f9795b535b73f0d660e8643fd4",
|
||||
"name": "Cronos Gas (Compliant)",
|
||||
"symbol": "cCRO",
|
||||
"familyKey": "cro",
|
||||
"mirroredSymbol": "cWCRO",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/cronos/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -440,15 +446,19 @@
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0xA39A59138F8a45b4a31455875b8A29FF31E76Ec8",
|
||||
"address": "0xa39a59138f8a45b4a31455875b8a29ff31e76ec8",
|
||||
"name": "Gnosis Gas (Compliant)",
|
||||
"symbol": "cXDAI",
|
||||
"familyKey": "xdai",
|
||||
"mirroredSymbol": "cWXDAI",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/xdai/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -462,15 +472,19 @@
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x1814661DAE0933Bf397b1274f5a62411B94195C7",
|
||||
"address": "0x1814661dae0933bf397b1274f5a62411b94195c7",
|
||||
"name": "Celo Gas (Compliant)",
|
||||
"symbol": "cCELO",
|
||||
"familyKey": "celo",
|
||||
"mirroredSymbol": "cWCELO",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/celo/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -484,15 +498,19 @@
|
||||
},
|
||||
{
|
||||
"chainId": 138,
|
||||
"address": "0x4D82206BeC5B4Dfa17759FFEDE07E35f4f63a050",
|
||||
"address": "0x4d82206bec5b4dfa17759ffede07e35f4f63a050",
|
||||
"name": "Wemix Gas (Compliant)",
|
||||
"symbol": "cWEMIX",
|
||||
"familyKey": "wemix",
|
||||
"mirroredSymbol": "cWWEMIX",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/wemix/info/logo.png",
|
||||
"backingMode": "hybrid_cap",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -546,13 +564,9 @@
|
||||
"name": "Staged",
|
||||
"description": "Deployed and discoverable, not yet canonical"
|
||||
},
|
||||
"fwdcanon": {
|
||||
"name": "Forward canonical",
|
||||
"description": "GRU v2 forward canonical token variant"
|
||||
},
|
||||
"gasnative": {
|
||||
"name": "Gas native",
|
||||
"description": "Gas native compliant families and wrapped gas mirrors"
|
||||
"gas-native": {
|
||||
"name": "Gas Native",
|
||||
"description": "Gas-native compliant families and wrapped gas mirrors"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,16 +232,20 @@
|
||||
},
|
||||
{
|
||||
"chainId": 1,
|
||||
"address": "0xf6dC5587e18F27Adff60E303fDD98F35b50FA8a5",
|
||||
"address": "0xf6dc5587e18f27adff60e303fdd98f35b50fa8a5",
|
||||
"name": "Wrapped Ethereum Mainnet Gas (Compliant)",
|
||||
"symbol": "cWETH",
|
||||
"familyKey": "eth_mainnet",
|
||||
"canonicalSymbol": "cETH",
|
||||
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
|
||||
"backingMode": "strict_escrow",
|
||||
"redeemPolicy": "family_only",
|
||||
"decimals": 18,
|
||||
"tags": [
|
||||
"defi",
|
||||
"compliant",
|
||||
"wrapped",
|
||||
"gasnative"
|
||||
"gas-native"
|
||||
],
|
||||
"extensions": {
|
||||
"assetClass": "gas_native",
|
||||
@@ -279,9 +283,9 @@
|
||||
"name": "CCIP",
|
||||
"description": "Assets related to CCIP and bridge infrastructure"
|
||||
},
|
||||
"gasnative": {
|
||||
"name": "Gas native",
|
||||
"description": "Gas native compliant families and wrapped gas mirrors"
|
||||
"gas-native": {
|
||||
"name": "Gas Native",
|
||||
"description": "Gas-native compliant families and wrapped gas mirrors"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerate SHA256SUMS for dbis-138 (matches release.yml "Generate checksums" step).
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LISTS_DIR="$(cd "$SCRIPT_DIR/../lists" && pwd)"
|
||||
cd "$LISTS_DIR"
|
||||
sha256sum dbis-138.tokenlist.json > SHA256SUMS
|
||||
echo "Wrote $LISTS_DIR/SHA256SUMS"
|
||||
cat SHA256SUMS
|
||||
@@ -87,21 +87,10 @@ async function verifyERC20Token(provider, token, index) {
|
||||
try {
|
||||
const onChainDecimals = await contract.decimals();
|
||||
if (Number(onChainDecimals) !== token.decimals) {
|
||||
const wethNineCanonical =
|
||||
token.address?.toLowerCase() === '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
|
||||
if (wethNineCanonical && token.decimals === 18) {
|
||||
results.push({
|
||||
type: 'warning',
|
||||
message:
|
||||
`${prefix}: Decimals differ (list: ${token.decimals}, on-chain: ${onChainDecimals}) — ` +
|
||||
'list keeps 18 for canonical WETH9 UX; on-chain may be bridge placeholder metadata'
|
||||
});
|
||||
} else {
|
||||
results.push({
|
||||
type: 'error',
|
||||
message: `${prefix}: Decimals mismatch - list: ${token.decimals}, on-chain: ${onChainDecimals}`
|
||||
});
|
||||
}
|
||||
results.push({
|
||||
type: 'error',
|
||||
message: `${prefix}: Decimals mismatch - list: ${token.decimals}, on-chain: ${onChainDecimals}`
|
||||
});
|
||||
} else {
|
||||
results.push({ type: 'success', message: `${prefix}: Decimals verified (${token.decimals})` });
|
||||
}
|
||||
@@ -195,8 +184,7 @@ async function verifyOracleToken(provider, token, index) {
|
||||
}
|
||||
|
||||
function isOracleToken(token) {
|
||||
// Only true price-feed contracts (e.g. ETH-USD). LINK uses ERC-20 surface and must not use ORACLE_ABI.
|
||||
return Array.isArray(token.tags) && token.tags.includes('pricefeed');
|
||||
return token.tags && (token.tags.includes('oracle') || token.tags.includes('pricefeed'));
|
||||
}
|
||||
|
||||
async function verifyOnChain(filePath, required = false) {
|
||||
|
||||
Reference in New Issue
Block a user