chore: sync submodule state (parent ref update)
Made-with: Cursor
This commit is contained in:
4
chain138-snap/.github/workflows/codeql.yml
vendored
4
chain138-snap/.github/workflows/codeql.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 0 * * 1' # weekly Monday
|
||||
- cron: '0 0 * * 1' # weekly Monday
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
@@ -37,4 +37,4 @@ jobs:
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
category: '/language:${{ matrix.language }}'
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Build Snap companion site (pathPrefix /snap). Optional: set repository variable
|
||||
# SNAP_VERIFY_BASE_URL (e.g. https://explorer.d-bis.org) to run verify-snap-site-vmid5000.sh after build.
|
||||
# Build Snap companion site (pathPrefix /snap). Uploads artifact for deploy to your host.
|
||||
# Optional: set secret GATSBY_SNAP_API_BASE_URL for production API in build.
|
||||
name: Deploy Snap Site
|
||||
name: Build Snap Site
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -9,8 +8,6 @@ on:
|
||||
paths:
|
||||
- 'packages/site/**'
|
||||
- 'packages/snap/**'
|
||||
- 'scripts/deploy-snap-site-to-vmid5000.sh'
|
||||
- 'scripts/verify-snap-site-vmid5000.sh'
|
||||
- '.github/workflows/deploy-snap-site.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -18,8 +15,8 @@ env:
|
||||
GATSBY_PATH_PREFIX: /snap
|
||||
|
||||
jobs:
|
||||
build-and-verify:
|
||||
name: Build site and verify
|
||||
build:
|
||||
name: Build site
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -43,7 +40,3 @@ jobs:
|
||||
name: snap-site-${{ github.sha }}
|
||||
path: packages/site/public/
|
||||
retention-days: 7
|
||||
|
||||
- name: Verify deployed Snap site (smoke)
|
||||
if: ${{ vars.SNAP_VERIFY_BASE_URL != '' && vars.SNAP_VERIFY_BASE_URL != null }}
|
||||
run: ./scripts/verify-snap-site-vmid5000.sh "${{ vars.SNAP_VERIFY_BASE_URL }}"
|
||||
|
||||
@@ -4,19 +4,19 @@ Use this when submitting the Chain 138 Snap via the [MetaMask Snaps Directory In
|
||||
|
||||
## Pre-filled values
|
||||
|
||||
| Field | Value |
|
||||
|-------|--------|
|
||||
| **Snap name** | Chain 138 *(must match `proposedName` in snap.manifest.json)* |
|
||||
| **Snap builder name and URL** | BIS Innovations — https://github.com/bis-innovations |
|
||||
| **Snap website URL** | https://github.com/bis-innovations/chain138-snap#readme *(or your deployed companion site, e.g. https://explorer.d-bis.org/snap/)* |
|
||||
| **GitHub repository** | https://github.com/bis-innovations/chain138-snap |
|
||||
| **npm package** | https://www.npmjs.com/package/chain138-snap |
|
||||
| **Snap version to allowlist** | 0.1.0 *(must match package.json and snap.manifest.json)* |
|
||||
| **Snap auditor / audit report** | Leave blank *(no key-management APIs; audit not required)* |
|
||||
| Field | Value |
|
||||
| ------------------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| **Snap name** | Chain 138 _(must match `proposedName` in snap.manifest.json)_ |
|
||||
| **Snap builder name and URL** | BIS Innovations — https://github.com/bis-innovations |
|
||||
| **Snap website URL** | https://github.com/bis-innovations/chain138-snap#readme _(or your deployed companion site URL)_ |
|
||||
| **GitHub repository** | https://github.com/bis-innovations/chain138-snap |
|
||||
| **npm package** | https://www.npmjs.com/package/chain138-snap |
|
||||
| **Snap version to allowlist** | 0.1.2 _(must match package.json and snap.manifest.json)_ |
|
||||
| **Snap auditor / audit report** | Leave blank _(no key-management APIs; audit not required)_ |
|
||||
|
||||
## Short description (1–2 sentences)
|
||||
|
||||
Chain 138 adds DeFi Oracle Meta Mainnet (and ALL Mainnet) support in MetaMask: network params, token list, market data, swap quotes, and CCIP bridge routes. Use with the token-aggregation API for full features.
|
||||
Chain 138 adds DeFi Oracle Meta Mainnet (and ALL Mainnet) support in MetaMask: network params, token list, market data, swap quotes, and bridge routes (CCIP and Trustless). Use with the token-aggregation API for full features.
|
||||
|
||||
## Long description
|
||||
|
||||
@@ -25,15 +25,15 @@ Use line breaks and lists; no HTML. Example:
|
||||
- **Networks:** Chain 138 (DeFi Oracle Meta Mainnet) and ALL Mainnet (651940); full EIP-3085 params from API.
|
||||
- **Token list & market data:** Tokens and USD prices via token-aggregation (or optional JSON URLs).
|
||||
- **Swap quotes:** In-Snap quotes for Chain 138 when quote API is configured.
|
||||
- **Bridge routes:** CCIP WETH9/WETH10 routes to Ethereum Mainnet when bridge API is available.
|
||||
- **Bridge routes:** CCIP (WETH9/WETH10) and Trustless (Lockbox) routes to Ethereum Mainnet when bridge API is available.
|
||||
|
||||
After installing, dApps must pass `apiBaseUrl` (your token-aggregation base URL) when invoking the Snap for market data, swap quote, and bridge routes. See the repo README and INTEGRATORS.md.
|
||||
|
||||
## Customer support
|
||||
|
||||
- **Escalation contact:** *(confidential; provide email or contact form)*
|
||||
- **Escalation contact:** _(confidential; provide email or contact form)_
|
||||
- **Public support:** GitHub Issues — https://github.com/bis-innovations/chain138-snap/issues
|
||||
*(Add at least one other channel, e.g. docs link or support URL.)*
|
||||
_(Add at least one other channel, e.g. docs link or support URL.)_
|
||||
|
||||
## Images
|
||||
|
||||
|
||||
@@ -18,4 +18,4 @@ Before submitting the Chain 138 Snap for MetaMask allowlisting, confirm:
|
||||
- [x] No `console` logs, to-do comments, or unused permissions in Snap code.
|
||||
- [x] Security: CI runs MetaMask Security Code Scanner (`.github/workflows/security-code-scanner.yml`). Optionally run [Snapper](https://docs.metamask.io/snaps/how-to/get-allowlisted/) locally before submission.
|
||||
|
||||
After publishing to npm, submit via the [MetaMask Snaps Directory Information form](https://docs.metamask.io/snaps/how-to/get-allowlisted/#1-submit-your-snap).
|
||||
**Submitted** to the MetaMask Snaps Directory; pending review. For future versions, use the [MetaMask Snaps Directory Information Update form](https://docs.metamask.io/snaps/how-to/get-allowlisted/#5-update-your-snap).
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
# Deploy Chain 138 Snap site to VMID 5000 (Production)
|
||||
|
||||
The Snap companion site can be published on the same host as the explorer (VMID 5000) at **https://explorer.d-bis.org/snap/** and is linked from the explorer navbar ("MetaMask Snap").
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Site built with **pathPrefix** `/snap` (so assets load under `/snap/`).
|
||||
- Access to Proxmox host that runs VMID 5000 (e.g. `pct` or SSH to `PROXMOX_HOST_R630_02`).
|
||||
|
||||
## 1. Build for production (pathPrefix /snap)
|
||||
|
||||
From the Chain 138 Snap repo root:
|
||||
|
||||
```bash
|
||||
GATSBY_PATH_PREFIX=/snap pnpm --filter site run build
|
||||
```
|
||||
|
||||
This writes the static site into `packages/site/public/` with asset paths prefixed by `/snap/`.
|
||||
|
||||
## 2. Deploy to VMID 5000
|
||||
|
||||
**Option A – build and deploy in one go:**
|
||||
|
||||
```bash
|
||||
./scripts/deploy-snap-site-to-vmid5000.sh --build
|
||||
```
|
||||
|
||||
**Production build (market/bridge/swap from live API):** set `GATSBY_SNAP_API_BASE_URL` when building:
|
||||
|
||||
```bash
|
||||
GATSBY_SNAP_API_BASE_URL=https://your-token-aggregation-api.com ./scripts/deploy-snap-site-to-vmid5000.sh --build
|
||||
```
|
||||
|
||||
**Option B – deploy an existing build:**
|
||||
|
||||
```bash
|
||||
./scripts/deploy-snap-site-to-vmid5000.sh
|
||||
```
|
||||
|
||||
The script:
|
||||
|
||||
- Builds the site (only if `--build` is passed).
|
||||
- Packs `packages/site/public/` into a tarball and deploys it to **/var/www/html/snap/** on VMID 5000.
|
||||
- Works when run: **inside** the VM (direct), **on the Proxmox host** (`pct exec`), or **from a remote machine** (SSH to Proxmox, then `pct`). Set `PROXMOX_HOST_R630_02` (default `192.168.11.12`) when running remotely.
|
||||
|
||||
## 3. Nginx on VMID 5000
|
||||
|
||||
Nginx must serve the `/snap/` path so `/snap` and `/snap/` return 200. **One command from the Proxmox host** (or from a machine with SSH to the host):
|
||||
|
||||
```bash
|
||||
cd explorer-monorepo
|
||||
./scripts/apply-nginx-snap-vmid5000.sh
|
||||
```
|
||||
|
||||
This runs `fix-nginx-serve-custom-frontend.sh` inside VMID 5000 (via `pct` or SSH). Alternatively, run the fix script **inside VMID 5000**:
|
||||
|
||||
- **`explorer-monorepo/scripts/fix-nginx-serve-custom-frontend.sh`** – run inside the VM. It configures (among other things):
|
||||
- `location = /snap` and `location /snap/` → alias `/var/www/html/snap/`, SPA fallback to `/snap/index.html`.
|
||||
|
||||
If you manage nginx by hand, add inside the HTTPS `server` block for explorer.d-bis.org:
|
||||
|
||||
```nginx
|
||||
location /snap/ {
|
||||
alias /var/www/html/snap/;
|
||||
try_files $uri $uri/ /snap/index.html;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
}
|
||||
```
|
||||
|
||||
Then reload nginx.
|
||||
|
||||
## 4. Explorer integration
|
||||
|
||||
The explorer frontend (`explorer-monorepo/frontend/public/index.html`) has a nav link **"MetaMask Snap"** pointing to `/snap/`. After deploying the explorer frontend to VMID 5000 (e.g. `explorer-monorepo/scripts/deploy-frontend-to-vmid5000.sh`), that link will open the Snap site at https://explorer.d-bis.org/snap/.
|
||||
|
||||
## 5. Production Snap and API URL
|
||||
|
||||
- **Snap**: For production, the Snap is typically installed from the npm package or a published Snap ID; the companion site at `/snap/` is the install/connect page.
|
||||
- **API**: Set `GATSBY_SNAP_API_BASE_URL` when building the site (e.g. in `packages/site/.env.production`) to your token-aggregation API base URL so the Market data, Bridge, and Swap quote cards work. Rebuild and redeploy after changing it.
|
||||
- **Token / bridge list URLs:** If you use GitHub (or other) JSON URLs for token list, bridge list, or networks, pin them to a tag or commit SHA for reproducible deploys. Validate: `./scripts/validate-token-lists.sh <URL1> [URL2] ...`.
|
||||
|
||||
## Verification checks
|
||||
|
||||
After deploy, the script runs:
|
||||
|
||||
- `/var/www/html/snap/index.html` exists in the VM
|
||||
- Nginx config has `location /snap/`
|
||||
- `http://localhost/snap/` returns 200
|
||||
- Response body contains Snap app content (Connect|Snap|MetaMask)
|
||||
|
||||
**Standalone verify (Snap only):**
|
||||
|
||||
```bash
|
||||
cd metamask-integration/chain138-snap
|
||||
./scripts/verify-snap-site-vmid5000.sh [BASE_URL]
|
||||
# BASE_URL defaults to https://explorer.d-bis.org
|
||||
```
|
||||
|
||||
**All VMID 5000 checks (explorer + API + Snap):**
|
||||
|
||||
```bash
|
||||
cd explorer-monorepo
|
||||
./scripts/verify-vmid5000-all.sh [BASE_URL]
|
||||
```
|
||||
|
||||
This runs: Blockscout port 4000, nginx `/api/` and `/snap/`, public `/api/v2/stats`, `/api/v2/blocks`, `/api/v2/transactions`, explorer root `/`, Snap site `/snap/` (200 + content), and nginx config for `/snap/`.
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Step | Command |
|
||||
|------|--------|
|
||||
| Build site (pathPrefix /snap) | `GATSBY_PATH_PREFIX=/snap pnpm --filter site run build` |
|
||||
| Deploy (with build) | `./scripts/deploy-snap-site-to-vmid5000.sh --build` |
|
||||
| Deploy (existing build) | `./scripts/deploy-snap-site-to-vmid5000.sh` |
|
||||
| Update nginx on VMID 5000 | From host: `explorer-monorepo/scripts/apply-nginx-snap-vmid5000.sh` (or run `fix-nginx-serve-custom-frontend.sh` inside the VM) |
|
||||
| Deploy explorer (incl. Snap link) | `explorer-monorepo/scripts/deploy-frontend-to-vmid5000.sh` |
|
||||
| Verify Snap only | `./scripts/verify-snap-site-vmid5000.sh` |
|
||||
| Verify all (explorer + API + Snap) | `explorer-monorepo/scripts/verify-vmid5000-all.sh` |
|
||||
|
||||
**URLs:** https://explorer.d-bis.org/snap/ and http://192.168.11.140/snap/ (replace IP if your VM differs). **Version/health:** https://explorer.d-bis.org/snap/version.json (build version and buildTime).
|
||||
|
||||
## CI (GitHub Actions)
|
||||
|
||||
The workflow **`.github/workflows/deploy-snap-site.yml`** runs on push to `main` (when site/snap/scripts change): it builds the site and uploads the artifact. To **run the Snap site smoke verify** after deploy, set a **repository variable** in GitHub:
|
||||
|
||||
- **Name:** `SNAP_VERIFY_BASE_URL`
|
||||
- **Value:** e.g. `https://explorer.d-bis.org` (no trailing slash)
|
||||
|
||||
Then the workflow will run `verify-snap-site-vmid5000.sh` against that URL. Optional: set secret **`GATSBY_SNAP_API_BASE_URL`** so the CI build uses your production API URL.
|
||||
@@ -12,22 +12,14 @@ The Snap and companion site call the token-aggregation API for networks, token l
|
||||
|
||||
1. **Prerequisites**
|
||||
- Node.js 20+
|
||||
- PostgreSQL 14+ with TimescaleDB
|
||||
- RPC URLs for Chain 138 and 651940 (e.g. from `.env.example`)
|
||||
- PostgreSQL 14+ with TimescaleDB (if your token-aggregation service uses it)
|
||||
- RPC URLs for Chain 138 and 651940 (e.g. from your API `.env.example`)
|
||||
|
||||
2. **Setup**
|
||||
```bash
|
||||
cd smom-dbis-138/services/token-aggregation
|
||||
cp .env.example .env
|
||||
# Edit .env: set DATABASE_URL, CHAIN_138_RPC_URL, CHAIN_651940_RPC_URL
|
||||
```
|
||||
- Clone or use your token-aggregation service repo. Copy `.env.example` to `.env` and set `DATABASE_URL`, `CHAIN_138_RPC_URL`, `CHAIN_651940_RPC_URL` (or equivalent).
|
||||
|
||||
3. **Database**
|
||||
- Apply migrations (see `QUICK_START.md` or `QUICK_START_COMPLETE.md` in that repo), e.g.:
|
||||
```bash
|
||||
psql $DATABASE_URL -f ../../explorer-monorepo/backend/database/migrations/0011_token_aggregation_schema.up.sql
|
||||
# plus 0012 if used
|
||||
```
|
||||
- Apply migrations as required by your token-aggregation service (see that service’s QUICK_START or migration docs).
|
||||
|
||||
4. **Run**
|
||||
```bash
|
||||
@@ -41,10 +33,10 @@ The Snap and companion site call the token-aggregation API for networks, token l
|
||||
### Option B: Docker
|
||||
|
||||
```bash
|
||||
cd smom-dbis-138/services/token-aggregation
|
||||
# From your token-aggregation service directory
|
||||
# Ensure .env exists with DATABASE_URL etc.
|
||||
docker-compose up -d
|
||||
# API on http://localhost:3000
|
||||
# API on http://localhost:3000 (or the port your service uses)
|
||||
```
|
||||
|
||||
### Option C: Deployed / staging
|
||||
@@ -64,8 +56,9 @@ Use your deployed token-aggregation base URL (e.g. `https://your-token-aggregati
|
||||
The companion site passes `apiBaseUrl` to the Snap so that market data, bridge, and swap quote cards work.
|
||||
|
||||
1. **Create env file**
|
||||
|
||||
```bash
|
||||
cd metamask-integration/chain138-snap/packages/site
|
||||
cd packages/site
|
||||
cp .env.production.dist .env
|
||||
# or .env.production for production build
|
||||
```
|
||||
@@ -73,7 +66,7 @@ The companion site passes `apiBaseUrl` to the Snap so that market data, bridge,
|
||||
2. **Set API base URL**
|
||||
- Local token-aggregation: `GATSBY_SNAP_API_BASE_URL=http://localhost:3000`
|
||||
- Deployed: `GATSBY_SNAP_API_BASE_URL=https://your-token-aggregation-api.com`
|
||||
Do not add a trailing slash.
|
||||
Do not add a trailing slash.
|
||||
|
||||
3. **Restart the site** if it is already running so the variable is picked up (Gatsby reads env at build/start).
|
||||
|
||||
@@ -81,10 +74,9 @@ The companion site passes `apiBaseUrl` to the Snap so that market data, bridge,
|
||||
|
||||
## 3. Run Snap + site
|
||||
|
||||
From the Chain 138 Snap monorepo root:
|
||||
From the Chain 138 Snap repo root:
|
||||
|
||||
```bash
|
||||
cd metamask-integration/chain138-snap
|
||||
pnpm run start
|
||||
```
|
||||
|
||||
@@ -104,7 +96,6 @@ Use the [E2E testing checklist (MetaMask Flask)](TESTING_INSTRUCTIONS.md#e2e-tes
|
||||
To run the automated E2E tests (site loads and shows Snap Connect UI):
|
||||
|
||||
```bash
|
||||
cd metamask-integration/chain138-snap
|
||||
pnpm install
|
||||
npx playwright install
|
||||
pnpm run test:e2e
|
||||
@@ -120,16 +111,14 @@ pnpm run test:e2e
|
||||
|
||||
## 6. Run E2E against deployed Snap site
|
||||
|
||||
To run Playwright (or manual checks) against the **deployed** Snap site (e.g. https://explorer.d-bis.org/snap/):
|
||||
To run Playwright (or manual checks) against a **deployed** Snap companion site (e.g. https://yoursite.com/snap/):
|
||||
|
||||
1. **Playwright against a URL:** Set the base URL and run tests (if your Playwright config supports it), e.g.:
|
||||
- Add a second config or override in `playwright.config.ts`: `baseURL: process.env.SNAP_BASE_URL || 'http://localhost:8000'`.
|
||||
- Run: `SNAP_BASE_URL=https://explorer.d-bis.org/snap pnpm run test:e2e` (after adding `baseURL` to the config).
|
||||
- Or run the manual E2E checklist in TESTING_INSTRUCTIONS.md while opening https://explorer.d-bis.org/snap/ in the browser.
|
||||
- Add a config or override in `playwright.config.ts`: `baseURL: process.env.SNAP_BASE_URL || 'http://localhost:8000'`.
|
||||
- Run: `SNAP_BASE_URL=https://yoursite.com/snap pnpm run test:e2e` (after adding `baseURL` to the config).
|
||||
- Or run the manual E2E checklist in TESTING_INSTRUCTIONS.md while opening your deployed site URL in the browser.
|
||||
|
||||
2. **Environment:** The deployed site must be built with `GATSBY_SNAP_API_BASE_URL` set to the production token-aggregation URL for Market, Bridge, and Swap cards to work. If not set, those cards will show "Set GATSBY_SNAP_API_BASE_URL".
|
||||
|
||||
3. **CI:** You can run `scripts/verify-snap-site-vmid5000.sh https://explorer.d-bis.org` in CI as a smoke test after deploy (no MetaMask required). Set repo variable `SNAP_VERIFY_BASE_URL` to enable the verify step in `.github/workflows/deploy-snap-site.yml`.
|
||||
2. **Environment:** The deployed site must be built with `GATSBY_SNAP_API_BASE_URL` set to your token-aggregation URL for Market, Bridge, and Swap cards to work. If not set, those cards will show "Set GATSBY_SNAP_API_BASE_URL".
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Chain 138 Snap — Integrator guide
|
||||
|
||||
Use this Snap from your dApp to provide Chain 138 (and ALL Mainnet) network params, token list, market data, swap quotes, and bridge routes inside MetaMask.
|
||||
Use this Snap from your dApp to provide Chain 138 (and ALL Mainnet) network params, token list, market data, swap quotes, and bridge routes (CCIP and Trustless) inside MetaMask.
|
||||
|
||||
## Production Snap ID
|
||||
|
||||
@@ -35,11 +35,11 @@ params: {
|
||||
|
||||
You can pass these instead of or in addition to `apiBaseUrl` for specific data:
|
||||
|
||||
| Param | Purpose | Used by RPCs |
|
||||
|----------------|----------------------|--------------------------------------|
|
||||
| `networksUrl` | JSON URL for networks| `get_networks`, `get_chain138_config` |
|
||||
| `tokenListUrl` | JSON URL for tokens | `get_token_list`, `get_token_list_url`|
|
||||
| `bridgeListUrl`| JSON URL for bridge | `get_bridge_routes`, `show_bridge_routes` |
|
||||
| Param | Purpose | Used by RPCs |
|
||||
| --------------- | --------------------- | ----------------------------------------- |
|
||||
| `networksUrl` | JSON URL for networks | `get_networks`, `get_chain138_config` |
|
||||
| `tokenListUrl` | JSON URL for tokens | `get_token_list`, `get_token_list_url` |
|
||||
| `bridgeListUrl` | JSON URL for bridge | `get_bridge_routes`, `show_bridge_routes` |
|
||||
|
||||
## Companion site env
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Use this checklist to complete **manual** E2E verification. Covers Snap install, all RPC methods, and companion site cards.
|
||||
|
||||
**For thorough pre-publish testing** (logos/images, every asset, production-like test, recommendations): use **[docs/PRE_PUBLISH_TESTING.md](docs/PRE_PUBLISH_TESTING.md)**.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [ ] MetaMask Flask installed: https://metamask.io/flask/
|
||||
@@ -29,16 +31,24 @@ Use this checklist to complete **manual** E2E verification. Covers Snap install,
|
||||
- [ ] `get_bridge_routes`, `show_bridge_routes` (apiBaseUrl or bridgeListUrl)
|
||||
- [ ] `get_swap_quote`, `show_swap_quote` (apiBaseUrl, tokenIn, tokenOut, amountIn; optional chainId)
|
||||
|
||||
*(Use browser console and `wallet_invokeSnap` as in TESTING_INSTRUCTIONS.md.)*
|
||||
_(Use browser console and `wallet_invokeSnap` as in TESTING_INSTRUCTIONS.md.)_
|
||||
|
||||
---
|
||||
|
||||
## 3. Companion site cards
|
||||
|
||||
- [ ] **Market data:** "Show market data" opens Snap dialog; "Fetch market summary" shows tokens/prices
|
||||
- [ ] **Bridge:** "Show bridge routes" opens Snap dialog with CCIP routes
|
||||
- [ ] **Bridge:** "Show bridge routes" opens Snap dialog with CCIP and Trustless routes
|
||||
- [ ] **Swap quote:** Enter token In/Out addresses and amount (raw); "Get quote" shows amountOut; "Show quote in Snap" opens dialog
|
||||
|
||||
---
|
||||
|
||||
When all items are checked, manual E2E (snap-9 and snap-10) is complete.
|
||||
## 4. Logos and images (pre-publish)
|
||||
|
||||
- [ ] Snap icon shows in MetaMask (Settings → Snaps → Chain 138).
|
||||
- [ ] Token list from API: every token has `logoURI`; list has list-level `logoURI` (see PRE_PUBLISH_TESTING.md §4.3).
|
||||
- [ ] Networks from API: each network has `iconUrls` and URLs resolve (see PRE_PUBLISH_TESTING.md §4.4).
|
||||
|
||||
---
|
||||
|
||||
When all items are checked, manual E2E (snap-9 and snap-10) is complete. Before publishing, complete the full [PRE_PUBLISH_TESTING.md](docs/PRE_PUBLISH_TESTING.md) sign-off.
|
||||
|
||||
18
chain138-snap/NEXT_STEPS.md
Normal file
18
chain138-snap/NEXT_STEPS.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Chain 138 Snap — Next steps
|
||||
|
||||
## Completed
|
||||
|
||||
- [x] **GitHub repo:** https://github.com/bis-innovations/chain138-snap (pushed; includes CodeQL workflow, npm README, publish script)
|
||||
- [x] **npm:** [chain138-snap@0.1.2](https://www.npmjs.com/package/chain138-snap) published; Snap ID: `npm:chain138-snap`
|
||||
- [x] **CodeQL:** `.github/workflows/codeql.yml` added; Security → Code scanning will run on push/PR and weekly
|
||||
- [x] **Docs:** README on npm, INTEGRATORS.md, PUSH_AND_PUBLISH.md, ALLOWLIST_FORM_FIELDS.md, ALLOWLIST_SOURCE_AND_COMPLIANCE_CHECKLIST.md
|
||||
- [x] **Documentation and FAQs:** All Snap-specific docs and FAQs live in this repo. See [docs/README.md](docs/README.md) for the index. Includes: CONTRIBUTING, FAQ, DEPLOY_COMPANION_SITE, RUNBOOK. Proxmox and other proprietary/internal references have been removed from docs and scripts.
|
||||
|
||||
## Remaining (manual)
|
||||
|
||||
1. ~~**Submit for allowlist**~~ **Done** — Submitted to MetaMask Snaps Directory; pending review/approval. After allowlisting, the Snap will be installable in standard MetaMask (non-Flask).
|
||||
|
||||
2. ~~**Dependabot alerts**~~ **Addressed** — Added pnpm `overrides` and Yarn `resolutions` for vulnerable transitive deps (cookie, glob, sharp, socket.io, ws, path-to-regexp). Bumped `sharp` override to ^0.34.5. Snap tests fixed with `@types/jest`. Re-run Dependabot/audit after pushing; merge any new Dependabot PRs for remaining bumps.
|
||||
|
||||
3. **Future releases**
|
||||
Bump version in `packages/snap/package.json`, then from this repo root run `pnpm run publish:snap`. Push to GitHub. If this repo is used as a subtree elsewhere, use your usual subtree push (e.g. `git subtree push --prefix=chain138-snap chain138-snap main` or split + force push if the remote has diverged).
|
||||
@@ -4,9 +4,7 @@ The Snap repo is **https://github.com/bis-innovations/chain138-snap**.
|
||||
|
||||
## 1. Push to GitHub
|
||||
|
||||
This Snap lives inside the **metamask-integration** repo as `chain138-snap/`. To push updates to the dedicated Snap repo:
|
||||
|
||||
From the **metamask-integration** repo root (parent of `chain138-snap/`):
|
||||
If this repo is used as a subtree (e.g. inside a parent monorepo as `chain138-snap/`), push from the parent repo root:
|
||||
|
||||
```bash
|
||||
# One-time: add the Snap repo as a remote (if not already added)
|
||||
@@ -18,13 +16,16 @@ git commit -m "your message"
|
||||
git subtree push --prefix=chain138-snap chain138-snap main
|
||||
```
|
||||
|
||||
The remote **chain138-snap** and branch **main** are already set up; the initial push has been done.
|
||||
Use the remote name and branch that match your setup (e.g. `chain138-snap`, `main`).
|
||||
|
||||
## 2. Publish Snap package to npm
|
||||
|
||||
From the **chain138-snap** monorepo root:
|
||||
|
||||
```bash
|
||||
# 0. (Recommended) Run thorough pre-publish testing
|
||||
# See docs/PRE_PUBLISH_TESTING.md — build, logos/images, all RPC methods, companion site, Send page, production-like test.
|
||||
|
||||
# 1. Build (updates manifest shasum)
|
||||
pnpm run build
|
||||
|
||||
@@ -33,9 +34,8 @@ pnpm run build
|
||||
# "Publish" and "Bypass 2FA for publish" at https://www.npmjs.com/settings/~/tokens
|
||||
|
||||
# 3. Publish (uses NPM_ACCESS_TOKEN from .env if set)
|
||||
# Run from the chain138-snap monorepo root (not from the parent proxmox repo):
|
||||
# Run from the chain138-snap monorepo root:
|
||||
pnpm run publish:snap
|
||||
# If you're in the proxmox repo root: cd metamask-integration/chain138-snap && pnpm run publish:snap
|
||||
# Or manually: cd packages/snap && npm login && npm publish --access public
|
||||
```
|
||||
|
||||
|
||||
@@ -1,67 +1,99 @@
|
||||
# Chain 138 Snap (MetaMask)
|
||||
|
||||
This Snap provides **Chain 138** (DeFi Oracle Meta Mainnet) and **ALL Mainnet** (651940) support in MetaMask: network params, token list, market data (prices), swap quotes, and CCIP bridge routes. It reads configuration from a **token-aggregation** (or compatible) API.
|
||||
A MetaMask Snap that adds **both** supported blockchains inside MetaMask:
|
||||
|
||||
**Why we built it:** MetaMask already supports Chain 138 as a custom EVM network (add via RPC), but native **Swaps**, **Portfolio Bridge**, and **USD pricing** do not include Chain 138 (Consensys-controlled). No public Snap existed for swap/bridge/pricing on 138. This Snap gives in-wallet swap quotes, bridge routes, and market data by calling our APIs, so users get feature parity without waiting for Consensys. See [docs/04-configuration/CHAIN138_WALLET_ECOSYSTEM_AND_RATIONALE.md](../../docs/04-configuration/CHAIN138_WALLET_ECOSYSTEM_AND_RATIONALE.md#2-why-we-created-the-metamask-snap) for full rationale.
|
||||
| Blockchain | Chain ID | Name |
|
||||
| --------------- | -------- | ------------------------ |
|
||||
| **Chain 138** | 138 | DeFi Oracle Meta Mainnet |
|
||||
| **ALL Mainnet** | 651940 | ALL Mainnet |
|
||||
|
||||
For detailed development and testing, see [TESTING_INSTRUCTIONS.md](TESTING_INSTRUCTIONS.md). For implementation phases and backend APIs, see [docs/04-configuration/metamask/SNAP_IMPLEMENTATION_ROADMAP.md](../../docs/04-configuration/metamask/SNAP_IMPLEMENTATION_ROADMAP.md) in the repo root.
|
||||
The Snap provides **network params**, **token list**, **market data** (USD prices), **swap quotes**, and **CCIP bridge routes** for these chains. It reads configuration from a **token-aggregation** (or compatible) API that you supply via `apiBaseUrl` or optional URL params.
|
||||
|
||||
**Integrators:** Production Snap ID: **`npm:chain138-snap`**. Market data, swap quote, and bridge route features require the dApp to pass `apiBaseUrl` (the token-aggregation service base URL) when invoking the Snap. You may also pass optional URLs: `networksUrl`, `tokenListUrl`, `bridgeListUrl`. Set `GATSBY_SNAP_API_BASE_URL` on the companion site so the demo page works. For production, set `SNAP_ORIGIN=npm:chain138-snap` in the site env so the companion uses the published Snap.
|
||||
**Why this Snap:** MetaMask supports Chain 138 as a custom EVM network, but native **Swaps**, **Portfolio Bridge**, and **USD pricing** do not include Chain 138 or ALL Mainnet. This Snap gives in-wallet swap quotes, bridge routes, and market data by calling your token-aggregation API, so users get feature parity on both blockchains without waiting for upstream support.
|
||||
|
||||
This Snap targets the **latest stable MetaMask Snap SDK** (`@metamask/snaps-sdk`).
|
||||
---
|
||||
|
||||
## Snaps is pre-release software
|
||||
## Features (both blockchains)
|
||||
|
||||
To interact with (your) Snaps, you will need to install [MetaMask Flask](https://metamask.io/flask/),
|
||||
a canary distribution for developers that provides access to upcoming features.
|
||||
- **Networks & config** — EIP-3085 params for Chain 138 and ALL Mainnet (`get_networks`, `get_chain138_config`, `get_chain138_market_chains`).
|
||||
- **Token list** — By chain; optional `chainId` (138 or 651940) for `get_token_list` / `get_token_list_url`.
|
||||
- **Market data** — Tokens and USD prices; in-Snap dialog via `show_market_data`.
|
||||
- **Bridge routes** — CCIP (WETH9/WETH10) and Trustless (Lockbox) routes to Ethereum Mainnet; dialog via `show_bridge_routes`.
|
||||
- **Swap quote** — Quote for Chain 138; dialog via `show_swap_quote`.
|
||||
- **Send (Chain 138)** — Companion site **Send** page (`/send`) so users can send ETH on Chain 138 without using MetaMask’s in-wallet Send button (which can error with “No XChain Swaps native asset found” on custom chains).
|
||||
- **Oracles & dynamic info** — API config and in-Snap dialog (`get_oracles`, `show_dynamic_info`).
|
||||
|
||||
## Getting Started
|
||||
Every method and parameter is documented with **tables and diagrams** in **[docs/FEATURES.md](docs/FEATURES.md)** (method matrix, params, response shapes, request flow).
|
||||
|
||||
Clone the template-snap repository [using this template](https://github.com/MetaMask/template-snap-monorepo/generate)
|
||||
and set up the development environment.
|
||||
---
|
||||
|
||||
**Default (pnpm):**
|
||||
## Snap ID
|
||||
|
||||
**Production:** `npm:chain138-snap`
|
||||
|
||||
Install from a dApp or the [companion site](https://github.com/bis-innovations/chain138-snap) by connecting with MetaMask and adding the Snap with this ID.
|
||||
|
||||
---
|
||||
|
||||
## Integrators
|
||||
|
||||
- **Snap ID:** `npm:chain138-snap`
|
||||
- **Market data, swap quote, bridge routes** require the dApp to pass **`apiBaseUrl`** (your token-aggregation base URL) when invoking the Snap.
|
||||
- Optional overrides: `networksUrl`, `tokenListUrl`, `bridgeListUrl` (see [INTEGRATORS.md](INTEGRATORS.md)).
|
||||
- Companion site: set `GATSBY_SNAP_API_BASE_URL` for the demo; set `SNAP_ORIGIN=npm:chain138-snap` for production so the site uses the published Snap.
|
||||
|
||||
---
|
||||
|
||||
## Testing before publish
|
||||
|
||||
For **thorough pre-publish testing** (build, all RPC methods, **logos/images** for chain and tokens, companion site, Send page, production-like test, and recommendations), see **[docs/PRE_PUBLISH_TESTING.md](docs/PRE_PUBLISH_TESTING.md)**. Quick manual E2E: [MANUAL_E2E_CHECKLIST.md](MANUAL_E2E_CHECKLIST.md) and [TESTING_INSTRUCTIONS.md](TESTING_INSTRUCTIONS.md).
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting (balance, swap, data not showing)
|
||||
|
||||
If **main balance or USD is not showing**, **Swap is malfunctioning**, or **historical/market data** does not load, see **[docs/CHAIN138_SNAP_TROUBLESHOOTING.md](docs/CHAIN138_SNAP_TROUBLESHOOTING.md)**. Summary:
|
||||
|
||||
- **$0.00 / no conversion rate:** MetaMask has no price feed for Chain 138; use Snap “Show market data” on the companion site or accept quantity-only in the wallet.
|
||||
- **In-wallet Swap fails:** MetaMask Swap does not support Chain 138; use [Send on Chain 138](https://explorer.d-bis.org/snap/send) and swap quotes from the Snap companion site.
|
||||
- **Snap market/swap/bridge errors:** Ensure `GATSBY_SNAP_API_BASE_URL` points to a host that serves the token-aggregation API (`/api/v1/networks`, `/api/v1/tokens`, `/api/v1/quote`, etc.); see the troubleshooting doc and [FAQ](docs/FAQ.md).
|
||||
|
||||
---
|
||||
|
||||
## Getting started
|
||||
|
||||
**Clone this repo** and run:
|
||||
|
||||
```shell
|
||||
pnpm install && pnpm start
|
||||
```
|
||||
|
||||
**Alternative (yarn):**
|
||||
Or with Yarn: `yarn install && yarn start`. See [PACKAGE_MANAGER.md](PACKAGE_MANAGER.md).
|
||||
|
||||
```shell
|
||||
yarn install && yarn start
|
||||
```
|
||||
The companion site and Snap are served at **http://localhost:8000**. Use [MetaMask Flask](https://metamask.io/flask/) for development; once the Snap is allowlisted, standard MetaMask can install it.
|
||||
|
||||
See [PACKAGE_MANAGER.md](PACKAGE_MANAGER.md) for details.
|
||||
---
|
||||
|
||||
## Cloning
|
||||
## Documentation
|
||||
|
||||
This repository contains GitHub Actions that you may find useful, see
|
||||
`.github/workflows` and [Releasing & Publishing](https://github.com/MetaMask/template-snap-monorepo/edit/main/README.md#releasing--publishing)
|
||||
below for more information.
|
||||
| Link | Description |
|
||||
| -------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| [docs/README.md](docs/README.md) | Documentation index |
|
||||
| [docs/FEATURES.md](docs/FEATURES.md) | **All functions and features** — RPC methods, params, both blockchains, tables, flow diagram |
|
||||
| [INTEGRATORS.md](INTEGRATORS.md) | Integrator guide (Snap ID, apiBaseUrl, RPC list) |
|
||||
| [TESTING_INSTRUCTIONS.md](TESTING_INSTRUCTIONS.md) | Development and E2E testing |
|
||||
| [docs/FAQ.md](docs/FAQ.md) | FAQ |
|
||||
|
||||
If you clone or create this repository outside the MetaMask GitHub organization,
|
||||
you probably want to run `./scripts/cleanup.sh` to remove some files that will
|
||||
not work properly outside the MetaMask GitHub organization.
|
||||
|
||||
If you don't wish to use any of the existing GitHub actions in this repository,
|
||||
simply delete the `.github/workflows` directory.
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
### Testing and Linting
|
||||
- **Lint:** `pnpm run lint` / `pnpm run lint:fix`
|
||||
- **Test:** `pnpm run test` (Snap unit tests), `pnpm run test:e2e` (Playwright; run `npx playwright install` once)
|
||||
- See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) and [E2E_PREPARATION.md](E2E_PREPARATION.md) for full setup.
|
||||
|
||||
**pnpm (default):** `pnpm run test`, `pnpm run lint`, `pnpm run lint:fix`
|
||||
**yarn:** `yarn test`, `yarn lint`, `yarn lint:fix`
|
||||
Scripts are disabled by default (LavaMoat). If needed: `pnpm run allow-scripts` and enable the package in the `lavamoat.allowScripts` section of `package.json`. See [@lavamoat/allow-scripts](https://github.com/LavaMoat/LavaMoat/tree/main/packages/allow-scripts).
|
||||
|
||||
- **Unit tests:** `pnpm run test` (Snap Jest tests).
|
||||
- **E2E (Playwright):** `pnpm run test:e2e` — starts the dev server if needed and runs companion-site E2E tests. First time run `npx playwright install`. See [TESTING_INSTRUCTIONS.md](TESTING_INSTRUCTIONS.md) and [E2E_PREPARATION.md](E2E_PREPARATION.md) for full manual E2E (MetaMask Flask) and token-aggregation setup.
|
||||
---
|
||||
|
||||
### Using NPM packages with scripts
|
||||
|
||||
Scripts are disabled by default for security reasons. If you need to use NPM
|
||||
packages with scripts, run **pnpm run allow-scripts** (or **yarn allow-scripts auto**) and enable the
|
||||
script in the `lavamoat.allowScripts` section of `package.json`.
|
||||
|
||||
See the documentation for [@lavamoat/allow-scripts](https://github.com/LavaMoat/LavaMoat/tree/main/packages/allow-scripts)
|
||||
for more information.
|
||||
This Snap targets the **latest stable MetaMask Snap SDK** (`@metamask/snaps-sdk`).
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
# Chain 138 Snap Runbook
|
||||
|
||||
Quick reference for building, deploying, and verifying the Snap companion site.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
GATSBY_PATH_PREFIX=/snap GATSBY_BUILD_SHA=$(git rev-parse --short HEAD) pnpm --filter site run build
|
||||
# Production API (market/bridge/swap):
|
||||
# GATSBY_SNAP_API_BASE_URL=https://your-token-aggregation-api.com
|
||||
```
|
||||
|
||||
## Deploy to VMID 5000
|
||||
|
||||
```bash
|
||||
./scripts/deploy-snap-site-to-vmid5000.sh --build # build + deploy
|
||||
GATSBY_SNAP_API_BASE_URL=https://your-api.com ./scripts/deploy-snap-site-to-vmid5000.sh --build # production API
|
||||
./scripts/deploy-snap-site-to-vmid5000.sh # deploy existing build
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
./scripts/verify-snap-site-vmid5000.sh [BASE_URL]
|
||||
# Full explorer + API + Snap:
|
||||
# cd ../explorer-monorepo && ./scripts/verify-vmid5000-all.sh [BASE_URL]
|
||||
```
|
||||
|
||||
## Rollback
|
||||
|
||||
See `explorer-monorepo/RUNBOOK.md` (rollback from VM or from host using `/tmp/snap-site-last.tar`).
|
||||
|
||||
## Nginx
|
||||
|
||||
From Proxmox host: `cd explorer-monorepo && ./scripts/apply-nginx-snap-vmid5000.sh`. Or inside VMID 5000: `explorer-monorepo/scripts/fix-nginx-serve-custom-frontend.sh`. Ensures `/snap` and `/snap/` return 200 with content.
|
||||
|
||||
## Token / bridge list validation
|
||||
|
||||
```bash
|
||||
./scripts/validate-token-lists.sh [URL1] [URL2] ...
|
||||
# Or set TOKEN_LIST_JSON_URL, BRIDGE_LIST_JSON_URL, NETWORKS_JSON_URL
|
||||
```
|
||||
|
||||
## URLs
|
||||
|
||||
- Production: https://explorer.d-bis.org/snap/
|
||||
- Version/health: https://explorer.d-bis.org/snap/version.json
|
||||
|
||||
See also `DEPLOY_VMID5000.md` and `explorer-monorepo/RUNBOOK.md`.
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
## HTTPS only
|
||||
|
||||
- The Snap companion site is intended to be served over **HTTPS** (e.g. https://explorer.d-bis.org/snap/). Avoid mixed content: ensure all API and asset URLs use HTTPS when the page is loaded over HTTPS.
|
||||
- Explorer and token-aggregation API should be HTTPS in production.
|
||||
- The Snap companion site is intended to be served over **HTTPS** in production. Avoid mixed content: ensure all API and asset URLs use HTTPS when the page is loaded over HTTPS.
|
||||
- Your token-aggregation API should use HTTPS in production.
|
||||
|
||||
## Token-aggregation API (public)
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
**Date:** 2026-01-30
|
||||
**Status:** Built and ready for testing
|
||||
|
||||
**Thorough pre-publish testing:** For a complete pass before every npm publish (including **all logos/images**, every RPC method, companion site, Send page, production-like test, and recommendations), use **[docs/PRE_PUBLISH_TESTING.md](docs/PRE_PUBLISH_TESTING.md)**.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
@@ -12,10 +14,12 @@
|
||||
- Install as separate browser extension (won't conflict with regular MetaMask)
|
||||
|
||||
2. **Snap Development Server Running**
|
||||
|
||||
```bash
|
||||
cd metamask-integration/chain138-snap
|
||||
pnpm run start
|
||||
```
|
||||
|
||||
(From the repo root.)
|
||||
(Or use **yarn start** if you prefer Yarn; see [PACKAGE_MANAGER.md](PACKAGE_MANAGER.md).)
|
||||
- Server will start on http://localhost:8000
|
||||
- Keep this terminal open
|
||||
@@ -81,7 +85,9 @@ await ethereum.request({
|
||||
**Optional:** You can pass `networksUrl` instead of (or without) `apiBaseUrl` to fetch networks from a JSON URL (e.g. GitHub raw):
|
||||
|
||||
```javascript
|
||||
params: { networksUrl: 'https://raw.githubusercontent.com/org/repo/main/networks.json' }
|
||||
params: {
|
||||
networksUrl: 'https://raw.githubusercontent.com/org/repo/main/networks.json';
|
||||
}
|
||||
```
|
||||
|
||||
#### Test `get_chain138_config`
|
||||
@@ -191,7 +197,7 @@ params: { tokenListUrl: 'https://raw.githubusercontent.com/org/repo/main/token-l
|
||||
|
||||
#### Test `get_bridge_routes`
|
||||
|
||||
Requires `apiBaseUrl` or `bridgeListUrl`. Returns CCIP bridge routes (WETH9 / WETH10) and Chain 138 bridge addresses.
|
||||
Requires `apiBaseUrl` or `bridgeListUrl`. Returns bridge routes: CCIP (WETH9/WETH10) and, when configured, Trustless (Lockbox on 138) and Chain 138 bridge addresses.
|
||||
|
||||
```javascript
|
||||
await ethereum.request({
|
||||
@@ -212,7 +218,7 @@ await ethereum.request({
|
||||
|
||||
#### Test `show_bridge_routes`
|
||||
|
||||
Requires `apiBaseUrl` or `bridgeListUrl`. Opens a Snap dialog with bridge route summary (WETH9/WETH10 → Ethereum Mainnet).
|
||||
Requires `apiBaseUrl` or `bridgeListUrl`. Opens a Snap dialog with bridge route summary: CCIP (WETH9/WETH10) and Trustless (Lockbox) → Ethereum Mainnet.
|
||||
|
||||
```javascript
|
||||
await ethereum.request({
|
||||
@@ -284,7 +290,7 @@ Use this checklist for full manual E2E testing:
|
||||
|
||||
1. **Environment**
|
||||
- [ ] MetaMask Flask installed
|
||||
- [ ] Snap dev server running: `pnpm run start` (or `yarn start`) in `metamask-integration/chain138-snap`
|
||||
- [ ] Snap dev server running: `pnpm run start` (or `yarn start`) in the repo root
|
||||
- [ ] For API-dependent tests: token-aggregation service reachable. Set `apiBaseUrl` to your deployment (e.g. `https://your-token-aggregation-api.com`) or a local/staging URL (e.g. `http://localhost:3000` if running token-aggregation locally).
|
||||
|
||||
2. **Install Snap**
|
||||
@@ -305,7 +311,7 @@ Use this checklist for full manual E2E testing:
|
||||
4. **Companion site cards**
|
||||
- [ ] Set `GATSBY_SNAP_API_BASE_URL` in `.env` (copy from `.env.production.dist` and fill) so the site passes apiBaseUrl to the Snap.
|
||||
- [ ] **Market data:** "Show market data" opens Snap dialog; "Fetch market summary" displays tokens/prices below.
|
||||
- [ ] **Bridge:** "Show bridge routes" opens Snap dialog with CCIP routes.
|
||||
- [ ] **Bridge:** "Show bridge routes" opens Snap dialog with CCIP and Trustless routes.
|
||||
- [ ] **Swap quote:** Enter token In/Out addresses and amount (raw), then "Get quote" shows amountOut; "Show quote in Snap" opens dialog.
|
||||
|
||||
---
|
||||
@@ -343,6 +349,7 @@ Use this checklist for full manual E2E testing:
|
||||
|
||||
**Checklist before publishing:**
|
||||
|
||||
- [ ] **Thorough test:** Complete [docs/PRE_PUBLISH_TESTING.md](docs/PRE_PUBLISH_TESTING.md) (build, logos/images, all RPC methods, companion site, Send page, production-like, final sign-off).
|
||||
- [ ] All manual E2E checklist items above completed and passing.
|
||||
- [ ] Token-aggregation (or your API) deployed and stable; production `apiBaseUrl` known.
|
||||
- [ ] Snap built with no errors; `prepublishOnly` has run (updates manifest shasum).
|
||||
@@ -377,7 +384,7 @@ Use this checklist for full manual E2E testing:
|
||||
- ✅ Token list and token list URL (`get_token_list`, `get_token_list_url`)
|
||||
- ✅ Market data: `get_market_summary` (tokens with prices), `show_market_data` (dialog)
|
||||
- ✅ Oracles config (`get_oracles`), dynamic info dialog (`show_dynamic_info`)
|
||||
- ✅ Bridge routes (`get_bridge_routes`, `show_bridge_routes`) when bridge API is available
|
||||
- ✅ Bridge routes (`get_bridge_routes`, `show_bridge_routes`) — CCIP and Trustless — when bridge API is available
|
||||
- ✅ Swap quote (`get_swap_quote`, `show_swap_quote`) when quote API is available
|
||||
|
||||
---
|
||||
|
||||
167
chain138-snap/docs/CHAIN138_SNAP_TROUBLESHOOTING.md
Normal file
167
chain138-snap/docs/CHAIN138_SNAP_TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Chain 138 Snap — Troubleshooting (balance, swap, data)
|
||||
|
||||
If **installation fails** (“snap is not on the allowlist”), **main balance or USD value is not showing**, **swap is malfunctioning**, or **historical/data not loading** when using the Chain 138 Snap with MetaMask (including Flask), use this guide.
|
||||
|
||||
---
|
||||
|
||||
## 1. Installation fails: "The snap is not on the allowlist"
|
||||
|
||||
**What you see:** When adding the Snap in MetaMask you get:
|
||||
|
||||
- **"Connection failed — Fetching of chain138-snap failed, check your network and try again."**
|
||||
- **"Cannot install version '0.1.2' of snap 'npm:chain138-snap': The snap is not on the allowlist."**
|
||||
|
||||
**Cause:** MetaMask only allows installing Snaps that use **protected permissions** (such as `endowment:rpc` and `endowment:network-access`) if the Snap is on their **allowlist**. The Chain 138 Snap uses those permissions, so it must be allowlisted. The Snap is published on npm and has been **submitted** for allowlisting; until MetaMask/Consensys approves it, standard MetaMask will block installation.
|
||||
|
||||
**What you can do:**
|
||||
|
||||
1. **Use MetaMask Flask (workaround now)**
|
||||
[MetaMask Flask](https://metamask.io/flask/) is the development build and **does not enforce the allowlist**. Install Flask, then add the Snap using the same ID: `npm:chain138-snap`. The Snap works the same once installed.
|
||||
|
||||
2. **Wait for allowlist approval**
|
||||
Once the Snap is approved, it will be installable in standard MetaMask. No code changes are required on your side.
|
||||
|
||||
3. **Operators:** If you submitted the allowlist form and it has been pending for a long time, you can follow up via the [MetaMask Snaps allowlist process](https://docs.metamask.io/snaps/how-to/get-allowlisted/#1-submit-your-snap). For new versions (e.g. 0.1.3), use the [Snaps Directory Information Update form](https://docs.metamask.io/snaps/how-to/get-allowlisted/#5-update-your-snap) to add the new version. Pre-filled fields for the submission are in [ALLOWLIST_FORM_FIELDS.md](../ALLOWLIST_FORM_FIELDS.md).
|
||||
|
||||
---
|
||||
|
||||
## 2. Main balance / USD not showing ($0.00, "No conversion rate available")
|
||||
|
||||
**What you see:** MetaMask shows **$0.00** or **"No conversion rate available"** for ETH (or other tokens) even when you hold a non‑zero balance (e.g. 1,000 ETH).
|
||||
|
||||
**Cause:** The **main wallet balance and USD value** are provided by **MetaMask (Consensys)**, not by the Snap. MetaMask gets conversion rates from its own providers (e.g. LavaPack). **Chain 138 (DeFi Oracle Meta Mainnet) is not in that list**, so MetaMask has no price feed for it and shows no USD value.
|
||||
|
||||
**This is expected** for custom chains until Consensys or price providers add support.
|
||||
|
||||
**What you can do:**
|
||||
|
||||
- **Trust the token quantity:** Your on‑chain balance (e.g. "1,000 ETH") is correct; only the USD conversion is missing in the wallet UI.
|
||||
- **Use the Snap for prices:** Open the **Snap companion site** (e.g. https://explorer.d-bis.org/snap/) and use **"Show market data"** or **"Fetch market summary"**. Those use the token‑aggregation API (see §5) and can show USD prices for Chain 138 tokens.
|
||||
- **Long‑term:** Submit Chain 138 and tokens to CoinGecko/CMC and/or Consensys so native MetaMask USD may be supported later (see project docs, e.g. `COINGECKO_SUBMISSION_GUIDE`, Consensys outreach).
|
||||
|
||||
---
|
||||
|
||||
## 3. "We could not fetch any historical data"
|
||||
|
||||
**What you see:** In the token/asset detail view, MetaMask shows a raccoon mascot and **"We could not fetch any historical data"** with time range buttons (1D, 1W, 1M, etc.).
|
||||
|
||||
**Cause:** Historical price/chart data is also supplied by **MetaMask’s portfolio/price providers**, which do not support Chain 138.
|
||||
|
||||
**What you can do:** This is a MetaMask UI limitation. Use the Snap’s market data (companion site) or external explorers/APIs for historical data on Chain 138.
|
||||
|
||||
---
|
||||
|
||||
## 4. Swap malfunctioning (in‑wallet Swap button)
|
||||
|
||||
**What you see:** Clicking **Swap** in MetaMask on Chain 138 fails or shows errors (e.g. "No XChain Swaps native asset found for chainId: eip155:138").
|
||||
|
||||
**Cause:** MetaMask’s **in‑wallet Swap** feature does **not** support Chain 138. It only supports chains in their native Swaps list.
|
||||
|
||||
**What you can do:**
|
||||
|
||||
- **Send ETH on Chain 138:** Use the dedicated **Send on Chain 138** page:
|
||||
https://explorer.d-bis.org/snap/send
|
||||
It uses `eth_sendTransaction` from the dApp context and works on Chain 138 (see `packages/site/src/pages/send.tsx`).
|
||||
- **Swap quotes via Snap:** On the Snap companion site, use the **Swap quote** card: enter token In/Out and amount, then **"Get quote"** or **"Show quote in Snap"**. This uses the token‑aggregation API. Executing the actual swap must be done in your dApp or a DEX that supports Chain 138, not via MetaMask’s Swap button.
|
||||
- **WETH display quirk:** If you see wrong WETH balance formatting (e.g. "6,000,000,000.0T WETH"), see [METAMASK_WETH9_DISPLAY_BUG.md](../../docs/METAMASK_WETH9_DISPLAY_BUG.md) (token list / decimals).
|
||||
|
||||
---
|
||||
|
||||
## 5. Snap market data / swap quote / bridge not loading
|
||||
|
||||
**What you see:** On the companion site (e.g. https://explorer.d-bis.org/snap/), **"Show market data"**, **"Fetch market summary"**, **"Get quote"** / **"Show quote in Snap"**, or **bridge routes** show an error or "Set GATSBY_SNAP_API_BASE_URL".
|
||||
|
||||
**Cause:** Those features need a **token‑aggregation–compatible API**. The Snap calls:
|
||||
|
||||
- `GET {apiBaseUrl}/api/v1/networks`
|
||||
- `GET {apiBaseUrl}/api/v1/tokens?chainId=138&limit=...`
|
||||
- `GET {apiBaseUrl}/api/v1/quote?chainId=138&tokenIn=...&tokenOut=...&amountIn=...`
|
||||
- `GET {apiBaseUrl}/api/v1/bridge/routes`
|
||||
- etc.
|
||||
|
||||
The companion site passes `apiBaseUrl` from **GATSBY_SNAP_API_BASE_URL** at **build time**. If that URL does not serve the token‑aggregation API (see `smom-dbis-138/services/token-aggregation` and [REST_API_REFERENCE.md](../../../smom-dbis-138/services/token-aggregation/docs/REST_API_REFERENCE.md)), those calls fail.
|
||||
|
||||
**What you can do:**
|
||||
|
||||
1. **If you build with `GATSBY_SNAP_API_BASE_URL=https://explorer.d-bis.org`**
|
||||
Then **explorer.d-bis.org** must expose the token‑aggregation API. The explorer’s normal Blockscout/Go APIs do **not** implement `/api/v1/networks`, `/api/v1/tokens`, `/api/v1/quote`, etc. You must either:
|
||||
- **Deploy the token‑aggregation service** (from `smom-dbis-138/services/token-aggregation`) and **proxy** its routes under `https://explorer.d-bis.org/api/v1/...`. On the explorer VM (VMID 5000), run **`explorer-monorepo/scripts/apply-nginx-token-aggregation-proxy.sh`** (or ensure the nginx config includes a `location /api/v1/` proxy to the token‑aggregation port, e.g. 3000). The script **`fix-nginx-conflicts-vmid5000.sh`** in the same repo already adds this proxy when applied.
|
||||
- Or use a **separate API host** (see below).
|
||||
|
||||
2. **If the token‑aggregation service runs elsewhere** (e.g. `https://api.d-bis.org` or an internal URL):
|
||||
Build the Snap site with that base URL:
|
||||
|
||||
```bash
|
||||
export GATSBY_SNAP_API_BASE_URL=https://your-token-aggregation-host
|
||||
bash scripts/build-snap-site-for-explorer.sh
|
||||
```
|
||||
|
||||
Deploy the built `packages/site/public/` to `/var/www/html/snap/` (or your Snap host). Then the companion site will pass the correct `apiBaseUrl` to the Snap and market/swap/bridge can work.
|
||||
|
||||
3. **Local development:** Set `GATSBY_SNAP_API_BASE_URL` in `packages/site/.env` to your token‑aggregation base URL (e.g. `http://localhost:3001` if the service runs there). See [FAQ.md](FAQ.md) (“The companion site shows ‘Set GATSBY_SNAP_API_BASE_URL’”).
|
||||
|
||||
---
|
||||
|
||||
## 6. No icons or tokens showing in MetaMask Flask
|
||||
|
||||
**What you see:** MetaMask Flask shows no token icons, no chain icons, or no tokens from the token list when viewing Chain 138 or ALL Mainnet.
|
||||
|
||||
**Cause:** MetaMask does not auto-load tokens from the Snap. Chain 138 is not in MetaMask’s built-in token detection (which only covers Ethereum, Polygon, Arbitrum, etc.). You must add the token list URL manually. Icons come from `logoURI` (per token) and `iconUrls` (per network) in the API responses; if those URLs are missing or unreachable, MetaMask shows no icons.
|
||||
|
||||
**What you can do:**
|
||||
|
||||
1. **Get and add the token list URL**
|
||||
- Open the Snap companion site (e.g. https://explorer.d-bis.org/snap/).
|
||||
- Connect MetaMask Flask and install the Snap.
|
||||
- Click **"Show dynamic info"** (requires `GATSBY_SNAP_API_BASE_URL` to be set at build time).
|
||||
- The Snap dialog shows the **Token list URL** (e.g. `https://explorer.d-bis.org/api/v1/report/token-list`).
|
||||
- Add that URL in MetaMask: **Settings → Security & Privacy → Token list** (or equivalent), if your MetaMask version supports custom token list URLs.
|
||||
|
||||
2. **If MetaMask does not support custom token list URLs**
|
||||
- Add tokens manually: **Tokens** tab → **Import tokens** → **Custom token** → enter the token contract address for Chain 138 (e.g. cUSDC, cUSDT from the explorer or [CHAIN138_TOKEN_ADDRESSES](../../../docs/11-references/CHAIN138_TOKEN_ADDRESSES.md)).
|
||||
|
||||
3. **Ensure `apiBaseUrl` is set and reachable**
|
||||
- The companion site must be built with `GATSBY_SNAP_API_BASE_URL` pointing at the token-aggregation API.
|
||||
- Verify: `curl "https://explorer.d-bis.org/api/v1/report/token-list?chainId=138"` returns valid JSON with `tokens` and each token has `logoURI`.
|
||||
|
||||
4. **Icons not showing**
|
||||
- Token icons: Each token in the token list must have a valid `logoURI`. The token-aggregation API provides these from `canonical-tokens.ts` (Trust Wallet assets).
|
||||
- Network icons: `GET /api/v1/networks` must return `iconUrls` for each chain. Each network has a primary URL (e.g. `explorer.d-bis.org/favicon.ico`) and a fallback (Trust Wallet ETH logo). If the primary 404s, MetaMask uses the fallback. Operators: add `favicon.ico` to explorer and alltra sites if missing.
|
||||
- If logo URLs (e.g. raw.githubusercontent.com) are blocked or return 404, operators can host logos locally and update `canonical-tokens.ts` or `networks.ts`.
|
||||
|
||||
**Operators:** See [PRE_PUBLISH_TESTING.md](../PRE_PUBLISH_TESTING.md) §4.3 (token list logoURI) and §4.4 (network iconUrls) for verification steps.
|
||||
|
||||
---
|
||||
|
||||
## 7. Permissions and connected site
|
||||
|
||||
The Snap needs:
|
||||
|
||||
- **Access the internet** — to call the token‑aggregation API.
|
||||
- **Display dialog windows in MetaMask** — for market data, swap quote, bridge dialogs.
|
||||
- **Allow websites to communicate directly with Chain 138** — so the companion site (e.g. explorer.d-bis.org) can invoke the Snap.
|
||||
|
||||
If market/swap work on the companion site but not from another origin, ensure that site is **connected** in MetaMask (Snap settings → Connected sites) and that you’re using the same Snap origin (e.g. `npm:chain138-snap`) and correct `apiBaseUrl`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Quick reference
|
||||
|
||||
| Issue | Cause | Action |
|
||||
| --------------------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Installation: "snap is not on the allowlist" | Snap not yet approved on MetaMask allowlist | Use [MetaMask Flask](https://metamask.io/flask/) to install now; or wait for allowlist approval |
|
||||
| No icons or tokens in MetaMask Flask | Token list not added; Chain 138 not in built-in detection | Use "Show dynamic info" to get token list URL; add in MetaMask Settings → Token list; or add tokens manually |
|
||||
| Main balance USD = $0.00 / no conversion rate | MetaMask has no price feed for Chain 138 | Use Snap “Show market data” or accept quantity-only in wallet |
|
||||
| Historical data not loading | MetaMask portfolio doesn’t support Chain 138 | Use Snap/explorer or external APIs |
|
||||
| In‑wallet Swap fails | MetaMask Swap doesn’t support Chain 138 | Use [Send on Chain 138](https://explorer.d-bis.org/snap/send); get swap quotes from Snap companion site and execute swap in dApp/DEX |
|
||||
| Snap market/swap/bridge errors | `apiBaseUrl` not set or not serving token‑aggregation API | Set GATSBY_SNAP_API_BASE_URL to token‑aggregation host; ensure `/api/v1/...` is available there (or proxy it under explorer.d-bis.org) |
|
||||
|
||||
---
|
||||
|
||||
**Related docs**
|
||||
|
||||
- [TOKEN_LIST_AND_ICONS_GUIDE.md](TOKEN_LIST_AND_ICONS_GUIDE.md) — how to add token list URL and fix icons
|
||||
- [FAQ.md](FAQ.md) — apiBaseUrl, permissions, “Set GATSBY_SNAP_API_BASE_URL”
|
||||
- [TESTING_INSTRUCTIONS.md](../TESTING_INSTRUCTIONS.md) — testing market summary, swap quote, bridge
|
||||
- [METAMASK_WETH9_DISPLAY_BUG.md](../../docs/METAMASK_WETH9_DISPLAY_BUG.md) — WETH balance display
|
||||
- Token‑aggregation API: `smom-dbis-138/services/token-aggregation/docs/REST_API_REFERENCE.md`
|
||||
62
chain138-snap/docs/CONTRIBUTING.md
Normal file
62
chain138-snap/docs/CONTRIBUTING.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Contributing to Chain 138 Snap
|
||||
|
||||
Thank you for your interest in contributing. This guide covers setup, testing, and publishing.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js** 18.6.0 or later (LTS recommended)
|
||||
- **pnpm** (default) or **Yarn** — see [PACKAGE_MANAGER.md](../PACKAGE_MANAGER.md)
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
# Clone the repo (or your fork)
|
||||
git clone https://github.com/bis-innovations/chain138-snap.git
|
||||
cd chain138-snap
|
||||
|
||||
# Install dependencies (pnpm default)
|
||||
pnpm install
|
||||
|
||||
# Optional: enable scripts for packages that need postinstall (e.g. sharp)
|
||||
pnpm run allow-scripts
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
- **Start dev server** (Snap + companion site): `pnpm run start` — serves at http://localhost:8000
|
||||
- **Build:** `pnpm run build` — builds Snap and site
|
||||
- **Lint:** `pnpm run lint` — ESLint + Prettier (JSON/MD/YAML)
|
||||
- **Lint fix:** `pnpm run lint:fix`
|
||||
|
||||
## Testing
|
||||
|
||||
- **Unit tests (Snap):** `pnpm run test` — builds the Snap and runs Jest in packages/snap
|
||||
- **E2E (Playwright):** `pnpm run test:e2e` — runs Playwright against the companion site (first time: `npx playwright install`)
|
||||
|
||||
For full manual E2E (MetaMask Flask, all RPC methods), see [TESTING_INSTRUCTIONS.md](../TESTING_INSTRUCTIONS.md) and [E2E_PREPARATION.md](../E2E_PREPARATION.md).
|
||||
|
||||
## Packages with scripts
|
||||
|
||||
Scripts are disabled by default (LavaMoat). If you need to run install scripts (e.g. for sharp or gatsby):
|
||||
|
||||
```bash
|
||||
pnpm run allow-scripts
|
||||
```
|
||||
|
||||
Then enable the package in the lavamoat.allowScripts section of the root package.json if required.
|
||||
|
||||
## Submitting changes
|
||||
|
||||
1. Create a branch, make your changes, and run `pnpm run lint` and `pnpm run test`.
|
||||
2. Open a pull request against main.
|
||||
3. Ensure CI passes (build, lint, tests, and any security workflows).
|
||||
|
||||
## Publishing (maintainers)
|
||||
|
||||
- **Push to GitHub:** See [PUSH_AND_PUBLISH.md](../PUSH_AND_PUBLISH.md) (subtree push from parent repo if applicable).
|
||||
- **Publish to npm:** From this repo root, `pnpm run publish:snap` (requires NPM_ACCESS_TOKEN in .env for 2FA bypass; see .env.example).
|
||||
- **Allowlist:** After publishing a new version, submit an update via the MetaMask Snaps Directory Information Update form if the Snap is already allowlisted.
|
||||
|
||||
## Code of conduct
|
||||
|
||||
Be respectful and constructive. Report security issues privately (see [SECURITY.md](../SECURITY.md)).
|
||||
65
chain138-snap/docs/DEPLOY_COMPANION_SITE.md
Normal file
65
chain138-snap/docs/DEPLOY_COMPANION_SITE.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Deploy the companion site
|
||||
|
||||
The companion site (`packages/site`) is a Gatsby app that lets users connect to and test the Snap. You can build it and deploy it to any static host (e.g. GitHub Pages, Netlify, or your own server).
|
||||
|
||||
## Build
|
||||
|
||||
From the repo root:
|
||||
|
||||
```bash
|
||||
# Build with path prefix (e.g. /snap if served at https://yoursite.com/snap/)
|
||||
GATSBY_PATH_PREFIX=/snap pnpm --filter site run build
|
||||
|
||||
# With production API URL (so Market data, Bridge, Swap cards work)
|
||||
GATSBY_SNAP_API_BASE_URL=https://your-token-aggregation-api.com GATSBY_PATH_PREFIX=/snap pnpm --filter site run build
|
||||
```
|
||||
|
||||
- Output is in `packages/site/public/`. Asset paths are prefixed by `GATSBY_PATH_PREFIX` (e.g. `/snap/`).
|
||||
- Use `GATSBY_PATH_PREFIX=/` if the site is served at the root of your domain.
|
||||
|
||||
## Deploy to your host
|
||||
|
||||
1. **Upload** the contents of `packages/site/public/` to your web server (e.g. `/var/www/html/snap/` or your CDN).
|
||||
2. **Web server:** Ensure the server serves the SPA correctly:
|
||||
- For a path like `/snap/`, map `/snap` and `/snap/*` to the built files and use a fallback to `/snap/index.html` for client-side routing.
|
||||
- Example (nginx):
|
||||
|
||||
```nginx
|
||||
location /snap/ {
|
||||
alias /var/www/html/snap/;
|
||||
try_files $uri $uri/ /snap/index.html;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
}
|
||||
```
|
||||
|
||||
3. **HTTPS:** Serve the site over HTTPS in production (see [SECURITY.md](../SECURITY.md)).
|
||||
|
||||
## Environment variables (build time)
|
||||
|
||||
| Variable | Description |
|
||||
| -------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `GATSBY_PATH_PREFIX` | Path prefix (e.g. `/snap`). Default: none (root). |
|
||||
| `GATSBY_SNAP_API_BASE_URL` | Token-aggregation API base URL passed to the Snap for market data, bridge, swap. No trailing slash. |
|
||||
| `GATSBY_SNAP_SITE_URL` | Public origin of the companion site (e.g. `https://explorer.d-bis.org`). When set, the "Send on Chain 138" link is absolute HTTPS so it never redirects to HTTP. |
|
||||
| `GATSBY_BUILD_SHA` | Optional; written to `version.json` for display. |
|
||||
|
||||
Set these when running the build; they are baked into the static output.
|
||||
|
||||
## Verification
|
||||
|
||||
After deploy, open your site URL and confirm:
|
||||
|
||||
- The Snap Connect UI loads.
|
||||
- Connecting with MetaMask (or MetaMask Flask) installs the Snap.
|
||||
- If you set `GATSBY_SNAP_API_BASE_URL`, the Market data, Bridge, and Swap quote cards work when the API is reachable.
|
||||
|
||||
## Optional: validate token/list URLs
|
||||
|
||||
If you use external JSON URLs for networks, token list, or bridge list, you can validate them before deploy:
|
||||
|
||||
```bash
|
||||
./scripts/validate-token-lists.sh [URL1] [URL2] ...
|
||||
# Or set TOKEN_LIST_JSON_URL, BRIDGE_LIST_JSON_URL, NETWORKS_JSON_URL
|
||||
```
|
||||
|
||||
See script help for details.
|
||||
123
chain138-snap/docs/FAQ.md
Normal file
123
chain138-snap/docs/FAQ.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Chain 138 Snap — FAQ
|
||||
|
||||
Frequently asked questions about the Chain 138 Snap (MetaMask).
|
||||
|
||||
---
|
||||
|
||||
## General
|
||||
|
||||
### What is the Chain 138 Snap?
|
||||
|
||||
A MetaMask Snap that adds **Chain 138** (DeFi Oracle Meta Mainnet) and **ALL Mainnet** (651940) support inside MetaMask: network parameters, token list, market data (USD prices), swap quotes, and bridge routes (CCIP and Trustless). It uses a token-aggregation (or compatible) API that you configure.
|
||||
|
||||
### Why use this Snap?
|
||||
|
||||
MetaMask supports Chain 138 as a custom EVM network, but native **Swaps**, **Portfolio Bridge**, and **USD pricing** do not include Chain 138. This Snap provides in-wallet swap quotes, bridge routes, and market data by calling your API, so users get feature parity without waiting for upstream support.
|
||||
|
||||
### What is the Snap ID?
|
||||
|
||||
**Production:** `npm:chain138-snap` (after installing from npm or the MetaMask Snaps Directory).
|
||||
|
||||
**Development:** `local:http://localhost:8000` when running the dev server.
|
||||
|
||||
---
|
||||
|
||||
## Installation and usage
|
||||
|
||||
### How do I install the Snap?
|
||||
|
||||
1. Install [MetaMask](https://metamask.io/) (or [MetaMask Flask](https://metamask.io/flask/) for development).
|
||||
2. From a dApp or the companion site, connect and add the Snap using the ID `npm:chain138-snap`.
|
||||
|
||||
### Do I need MetaMask Flask?
|
||||
|
||||
- **Production:** No. Once the Snap is allowlisted, it installs in standard MetaMask.
|
||||
- **Development:** Yes, for testing before allowlisting. Use [MetaMask Flask](https://metamask.io/flask/).
|
||||
|
||||
### Why do market data, swap quote, and bridge routes not work?
|
||||
|
||||
Those features require the dApp to pass **`apiBaseUrl`** (your token-aggregation service base URL) when invoking the Snap. Without it, the Snap cannot fetch data. See [INTEGRATORS.md](../INTEGRATORS.md).
|
||||
|
||||
### Can I use my own API or JSON URLs?
|
||||
|
||||
Yes. You can pass:
|
||||
|
||||
- **`apiBaseUrl`** — base URL of a token-aggregation–compatible API (networks, token list, bridge, quote endpoints).
|
||||
- **`networksUrl`** — direct URL to a networks JSON (overrides API for networks).
|
||||
- **`tokenListUrl`** — direct URL to a token list JSON.
|
||||
- **`bridgeListUrl`** — direct URL to a bridge routes JSON.
|
||||
|
||||
See [INTEGRATORS.md](../INTEGRATORS.md) and [TESTING_INSTRUCTIONS.md](../TESTING_INSTRUCTIONS.md).
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
### How do I run the Snap locally?
|
||||
|
||||
From the repo root: `pnpm run start`. The companion site and Snap are served at http://localhost:8000. Use MetaMask Flask and connect to that URL.
|
||||
|
||||
### How do I run tests?
|
||||
|
||||
- **Unit (Jest):** `pnpm run test`
|
||||
- **E2E (Playwright):** `pnpm run test:e2e` (run `npx playwright install` once)
|
||||
|
||||
See [TESTING_INSTRUCTIONS.md](../TESTING_INSTRUCTIONS.md) for full manual E2E.
|
||||
|
||||
### The companion site shows "Set GATSBY_SNAP_API_BASE_URL"
|
||||
|
||||
Set `GATSBY_SNAP_API_BASE_URL` in `packages/site/.env` (copy from `.env.production.dist`) to your token-aggregation API base URL so the site can pass `apiBaseUrl` to the Snap. Restart the dev server after changing env.
|
||||
|
||||
### How do I publish a new version?
|
||||
|
||||
1. Bump version in `packages/snap/package.json`.
|
||||
2. From repo root: `pnpm run build` then `pnpm run publish:snap` (see [PUSH_AND_PUBLISH.md](../PUSH_AND_PUBLISH.md)).
|
||||
3. Push to GitHub. If the Snap is allowlisted, submit a version update via the [MetaMask update form](https://docs.metamask.io/snaps/how-to/get-allowlisted/#5-update-your-snap).
|
||||
|
||||
---
|
||||
|
||||
## Permissions and security
|
||||
|
||||
### What permissions does the Snap use?
|
||||
|
||||
- **snap_dialog** — show dialogs (e.g. bridge routes, market data).
|
||||
- **endowment:rpc** (dapps: true) — handle RPC from dApps.
|
||||
- **endowment:network-access** — fetch data from the configured API/URLs.
|
||||
|
||||
No key-management or account APIs are used. See [SECURITY.md](../SECURITY.md) and `packages/snap/snap.manifest.json`.
|
||||
|
||||
### Is an audit required for allowlisting?
|
||||
|
||||
No. The Snap does not use key-management APIs, so a third-party audit is not required for the MetaMask Snaps Directory. See [ALLOWLIST_SOURCE_AND_COMPLIANCE_CHECKLIST.md](../ALLOWLIST_SOURCE_AND_COMPLIANCE_CHECKLIST.md).
|
||||
|
||||
---
|
||||
|
||||
## RPC methods
|
||||
|
||||
### What RPC methods are available?
|
||||
|
||||
See the table in [packages/snap/README.md](../packages/snap/README.md) or [TESTING_INSTRUCTIONS.md](../TESTING_INSTRUCTIONS.md). Summary: `hello`, `get_networks`, `get_chain138_config`, `get_chain138_market_chains`, `get_token_list`, `get_token_list_url`, `get_oracles`, `show_dynamic_info`, `get_market_summary`, `show_market_data`, `get_bridge_routes`, `show_bridge_routes`, `get_swap_quote`, `show_swap_quote`.
|
||||
|
||||
### How do I call the Snap from my dApp?
|
||||
|
||||
Use `wallet_requestSnaps` to install and `wallet_invokeSnap` to call methods. Example in [INTEGRATORS.md](../INTEGRATORS.md) and [packages/snap/README.md](../packages/snap/README.md).
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Snap not appearing in MetaMask Flask
|
||||
|
||||
- Ensure the dev server is running on port 8000 and you opened http://localhost:8000.
|
||||
- Check the browser console for errors and refresh the page.
|
||||
|
||||
### API calls failing (CORS, 404)
|
||||
|
||||
- Ensure your token-aggregation API allows the Snap/site origin in CORS.
|
||||
- Verify `apiBaseUrl` is correct (no trailing slash) and the endpoints (e.g. `/api/v1/networks`, `/api/v1/report/token-list`) exist and return valid JSON.
|
||||
|
||||
### Permission errors
|
||||
|
||||
- Confirm `snap.manifest.json` includes `endowment:network-access` if you call APIs. Reinstall the Snap after changing the manifest.
|
||||
|
||||
For more, see the **Troubleshooting** section in [TESTING_INSTRUCTIONS.md](../TESTING_INSTRUCTIONS.md).
|
||||
186
chain138-snap/docs/FEATURES.md
Normal file
186
chain138-snap/docs/FEATURES.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# Chain 138 Snap — Features and RPC methods
|
||||
|
||||
This document lists every function and feature of the Snap, with parameters and response shapes. The Snap supports **both** blockchains: **Chain 138** (DeFi Oracle Meta Mainnet) and **ALL Mainnet** (651940).
|
||||
|
||||
---
|
||||
|
||||
## Blockchains supported
|
||||
|
||||
| Chain | Chain ID | Name | Use in Snap |
|
||||
| --------------- | -------- | ------------------------ | ----------------------------------------------------- |
|
||||
| **Chain 138** | 138 | DeFi Oracle Meta Mainnet | Primary; networks, config, market, swap, bridge |
|
||||
| **ALL Mainnet** | 651940 | ALL Mainnet | Supported in networks, token list, optional `chainId` |
|
||||
|
||||
All methods that accept an optional `chainId` default to **138** when omitted.
|
||||
|
||||
---
|
||||
|
||||
## Feature overview (visual)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Chain 138 Snap (npm:chain138-snap) │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ Networks & config │ get_networks, get_chain138_config, │
|
||||
│ (Chain 138 + ALL) │ get_chain138_market_chains │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ Token list │ get_token_list, get_token_list_url │
|
||||
│ (optional chainId) │ (chainId: 138 or 651940) │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ Market data │ get_market_summary, show_market_data │
|
||||
│ (USD prices) │ (dialog: token symbols + prices) │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ Bridge routes │ get_bridge_routes, show_bridge_routes │
|
||||
│ (CCIP + Trustless) │ (dialog: CCIP WETH9/WETH10 + Trustless Lockbox)│
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ Swap quote │ get_swap_quote, show_swap_quote │
|
||||
│ (Chain 138) │ (tokenIn, tokenOut, amountIn → amountOut) │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ Oracles & dynamic │ get_oracles, show_dynamic_info │
|
||||
│ (API config) │ (dialog: networks + token list URL) │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ Test │ hello (returns greeting) │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## RPC method matrix
|
||||
|
||||
| Method | Chain 138 | ALL (651940) | Requires apiBaseUrl / URL param | Shows dialog (UI) |
|
||||
| ---------------------------- | --------- | ------------ | ---------------------------------------- | ------------------ |
|
||||
| `hello` | — | — | No | Yes (confirmation) |
|
||||
| `get_networks` | ✅ | ✅ | apiBaseUrl or networksUrl | No |
|
||||
| `get_chain138_config` | ✅ | — | apiBaseUrl or networksUrl | No |
|
||||
| `get_chain138_market_chains` | ✅ | — | apiBaseUrl | No |
|
||||
| `get_token_list` | ✅ | ✅ | apiBaseUrl or tokenListUrl | No |
|
||||
| `get_token_list_url` | ✅ | ✅ | apiBaseUrl or tokenListUrl | No |
|
||||
| `get_oracles` | ✅ | — | apiBaseUrl | No |
|
||||
| `show_dynamic_info` | ✅ | ✅ | apiBaseUrl or networksUrl/tokenListUrl | **Yes** |
|
||||
| `get_market_summary` | ✅ | ✅ | apiBaseUrl | No |
|
||||
| `show_market_data` | ✅ | ✅ | apiBaseUrl | **Yes** |
|
||||
| `get_bridge_routes` | ✅ | — | apiBaseUrl or bridgeListUrl | No |
|
||||
| `show_bridge_routes` | ✅ | — | apiBaseUrl or bridgeListUrl | **Yes** |
|
||||
| `get_swap_quote` | ✅ | — | apiBaseUrl + tokenIn, tokenOut, amountIn | No |
|
||||
| `show_swap_quote` | ✅ | — | apiBaseUrl + tokenIn, tokenOut, amountIn | **Yes** |
|
||||
|
||||
---
|
||||
|
||||
## Method reference by category
|
||||
|
||||
### Test
|
||||
|
||||
| Method | Params | Response | UI |
|
||||
| ------- | ------ | ------------------------------ | ------------------- |
|
||||
| `hello` | — | `"Hello from Chain 138 Snap!"` | Confirmation dialog |
|
||||
|
||||
---
|
||||
|
||||
### Networks and chain config
|
||||
|
||||
| Method | Params | Response shape |
|
||||
| ---------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| `get_networks` | `apiBaseUrl` or `networksUrl` | `{ version?, networks: EIP-3085[] }` |
|
||||
| `get_chain138_config` | `apiBaseUrl` or `networksUrl` | Chain 138 params (chainId, chainName, rpcUrls, nativeCurrency, blockExplorerUrls, oracles) |
|
||||
| `get_chain138_market_chains` | `apiBaseUrl` | `[{ chainId, name, nativeToken, rpcUrl, explorerUrl }]` |
|
||||
|
||||
**Visual (dialog):** None for get\_\*; use `show_dynamic_info` for an in-Snap dialog with networks and token list URL.
|
||||
|
||||
---
|
||||
|
||||
### Token list
|
||||
|
||||
| Method | Params | Response shape |
|
||||
| -------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------- |
|
||||
| `get_token_list` | `apiBaseUrl` or `tokenListUrl`, optional `chainId` (138 \| 651940) | `{ tokens: [{ symbol, name, address, ... }] }` or API shape |
|
||||
| `get_token_list_url` | Same | URL string or object with token list URL |
|
||||
|
||||
---
|
||||
|
||||
### Oracles and dynamic info
|
||||
|
||||
| Method | Params | Response / UI |
|
||||
| ------------------- | ---------------------------------------------- | ----------------------------------------------- |
|
||||
| `get_oracles` | `apiBaseUrl` | Oracles config from API |
|
||||
| `show_dynamic_info` | `apiBaseUrl` or `networksUrl` / `tokenListUrl` | **Dialog:** networks summary and token list URL |
|
||||
|
||||
---
|
||||
|
||||
### Market data (USD prices)
|
||||
|
||||
| Method | Params | Response shape | UI |
|
||||
| -------------------- | ---------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `get_market_summary` | `apiBaseUrl`, optional `chainId` (default 138) | `{ tokens: [{ symbol, name, address, market?: { priceUsd, volume24h } }] }` | No |
|
||||
| `show_market_data` | Same | — | **Dialog:** "Market data (Chain 138)" with token symbols and USD prices |
|
||||
|
||||
---
|
||||
|
||||
### Bridge routes (CCIP + Trustless)
|
||||
|
||||
| Method | Params | Response shape | UI |
|
||||
| -------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| `get_bridge_routes` | `apiBaseUrl` or `bridgeListUrl` | `{ routes: { weth9?, weth10?, trustless? }, chain138Bridges: { weth9?, weth10?, trustless? } }` | No |
|
||||
| `show_bridge_routes` | Same | — | **Dialog:** CCIP (WETH9/WETH10) and Trustless (Lockbox on 138) routes to Ethereum Mainnet |
|
||||
|
||||
The API or `bridgeListUrl` JSON may include:
|
||||
|
||||
- **CCIP:** `routes.weth9`, `routes.weth10`, `chain138Bridges.weth9`, `chain138Bridges.weth10`.
|
||||
- **Trustless:** `chain138Bridges.trustless` (Lockbox on Chain 138), optional `routes.trustless['Ethereum Mainnet (1)']` (Ethereum-side contract).
|
||||
|
||||
---
|
||||
|
||||
### Swap quote
|
||||
|
||||
| Method | Params | Response shape | UI |
|
||||
| ----------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------- | -------------------------------- |
|
||||
| `get_swap_quote` | `apiBaseUrl`, `tokenIn`, `tokenOut`, `amountIn` (raw string), optional `chainId` (default 138) | `{ amountOut?, error?, poolAddress? }` | No |
|
||||
| `show_swap_quote` | Same | — | **Dialog:** In/Out amounts (raw) |
|
||||
|
||||
---
|
||||
|
||||
## Request flow (high level)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph dApp
|
||||
A[dApp / Companion site]
|
||||
end
|
||||
subgraph Snap
|
||||
B[Chain 138 Snap]
|
||||
end
|
||||
subgraph API
|
||||
C[Token-aggregation API]
|
||||
end
|
||||
A -->|wallet_invokeSnap + apiBaseUrl / URL params| B
|
||||
B -->|GET /api/v1/networks etc.| C
|
||||
C -->|JSON| B
|
||||
B -->|result or snap_dialog| A
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Optional URL overrides
|
||||
|
||||
Instead of (or in addition to) `apiBaseUrl`, you can pass:
|
||||
|
||||
| Param | Used by methods |
|
||||
| --------------- | ----------------------------------------- |
|
||||
| `networksUrl` | `get_networks`, `get_chain138_config` |
|
||||
| `tokenListUrl` | `get_token_list`, `get_token_list_url` |
|
||||
| `bridgeListUrl` | `get_bridge_routes`, `show_bridge_routes` |
|
||||
|
||||
---
|
||||
|
||||
## Screenshots and visuals (for maintainers)
|
||||
|
||||
To make the docs more visual, add screenshots under **`docs/images/`** and link them here and in the README. Suggested captures:
|
||||
|
||||
| Screenshot | Description |
|
||||
| -------------------------- | ---------------------------------------------------------------- |
|
||||
| `connect.png` | Companion site Connect button and MetaMask install prompt |
|
||||
| `market-data-dialog.png` | Snap dialog from `show_market_data` (Chain 138 tokens + prices) |
|
||||
| `bridge-routes-dialog.png` | Snap dialog from `show_bridge_routes` (CCIP + Trustless routes) |
|
||||
| `swap-quote-dialog.png` | Snap dialog from `show_swap_quote` |
|
||||
| `dynamic-info-dialog.png` | Snap dialog from `show_dynamic_info` (networks + token list URL) |
|
||||
|
||||
After adding images, link them in this section and in [README.md](../README.md).
|
||||
253
chain138-snap/docs/PRE_PUBLISH_TESTING.md
Normal file
253
chain138-snap/docs/PRE_PUBLISH_TESTING.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# Thorough Pre-Publish Testing Guide — Chain 138 Snap
|
||||
|
||||
Use this guide to **thoroughly test the Snap before every npm publish** and before submitting or updating the Snap in the MetaMask directory. It covers build, assets (logos/images), every RPC method, companion site, Send page, production-like flows, and recommendations. No details (including chain/token logos) are left out.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview and when to use
|
||||
|
||||
- **Purpose:** Ensure the Snap and companion site work end-to-end, all assets (icons, logos) are present and reachable, and the package is ready for npm and (optionally) the MetaMask directory.
|
||||
- **When:** Before each `pnpm run publish:snap` and before submitting or updating the Snap via the [MetaMask Snaps Directory Information form](https://docs.metamask.io/snaps/how-to/get-allowlisted/#5-update-your-snap).
|
||||
- **Scope:** Build, unit tests, Snap package contents, **all logos and images**, every RPC method, companion site cards, Send page, production-like test, allowlist checklist, and recommendations.
|
||||
|
||||
---
|
||||
|
||||
## 2. Prerequisites
|
||||
|
||||
### 2.1 MetaMask Flask (for full manual E2E)
|
||||
|
||||
- Install: https://metamask.io/flask/
|
||||
- Use a **separate** browser profile or the Flask build as a separate extension so it does not conflict with regular MetaMask.
|
||||
- Ensure Flask is unlocked and (optionally) create or import a test wallet you can use on Chain 138.
|
||||
|
||||
### 2.2 Token-aggregation API (for market data, swap quote, bridge, token list)
|
||||
|
||||
- **Local:** See [E2E_PREPARATION.md](../E2E_PREPARATION.md). Run from `smom-dbis-138/services/token-aggregation` (Node 20+, PostgreSQL if required). Default port **3000**. Verify: `curl http://localhost:3000/api/v1/networks` and `curl http://localhost:3000/api/v1/report/token-list?chainId=138`.
|
||||
- **Staging/production:** Use a deployed base URL. Ensure CORS allows the Snap/site origin and all required endpoints respond (see E2E_PREPARATION.md §1). Run **`scripts/verify-snap-api-and-icons.sh [API_BASE_URL]`** to verify token list, networks, logoURIs, and iconUrls.
|
||||
|
||||
### 2.3 Companion site environment
|
||||
|
||||
- In `packages/site`: copy `.env.production.dist` to `.env` (dev) or `.env.production` (production build).
|
||||
- Set **`GATSBY_SNAP_API_BASE_URL`** to the token-aggregation base URL (e.g. `http://localhost:3000` or `https://explorer.d-bis.org`). No trailing slash.
|
||||
- For **production-like** test: set **`GATSBY_SNAP_ORIGIN=npm:chain138-snap`** so the site uses the published Snap (after it is on npm).
|
||||
- Restart the dev server after changing env so Gatsby picks up the variables.
|
||||
|
||||
### 2.4 Optional but recommended
|
||||
|
||||
- A **real wallet** with a small balance on **Chain 138** (e.g. testnet ETH) to verify Send page and in-wallet display.
|
||||
- **Deployed companion site** (e.g. https://explorer.d-bis.org/snap/) so you can test from a production URL and from a different origin (MetaMask “Connected sites”).
|
||||
|
||||
---
|
||||
|
||||
## 3. Build and unit tests
|
||||
|
||||
| Step | Command | What to verify |
|
||||
| ------------------------ | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Install deps | `pnpm install` | No peer/version errors. |
|
||||
| Build | `pnpm run build` | Both `packages/snap` and `packages/site` build successfully. Snap manifest shasum is updated (mm-snap may report “fixed”). |
|
||||
| Unit tests | `pnpm run test` | All Jest tests pass (Snap package). |
|
||||
| Automated E2E (optional) | `npx playwright install` once, then `pnpm run test:e2e` | Playwright tests pass (site loads, Connect/Reconnect/Install visible). Does not drive MetaMask. |
|
||||
| Lint | `pnpm run lint` | No ESLint/Prettier errors. |
|
||||
|
||||
**Snap package contents (must be present for publish):**
|
||||
|
||||
- [ ] `packages/snap/dist/bundle.js` — exists and non-empty.
|
||||
- [ ] `packages/snap/images/icon.svg` — exists (referenced in `snap.manifest.json` as `iconPath`).
|
||||
- [ ] `packages/snap/snap.manifest.json` — `version` matches `packages/snap/package.json`, `source.location.npm.packageName` is `chain138-snap`, `iconPath` is `images/icon.svg`.
|
||||
- [ ] `packages/snap/package.json` — `files` includes `dist/`, `images/`, `snap.manifest.json`; `publishConfig.access` is `public` if publishing as public.
|
||||
|
||||
---
|
||||
|
||||
## 4. Logos and images — complete checklist
|
||||
|
||||
Nothing should be missing or broken. Verify each of the following.
|
||||
|
||||
### 4.1 Snap icon (MetaMask Snap list and detail)
|
||||
|
||||
| Item | Location | Check |
|
||||
| ----------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| Snap icon | `packages/snap/images/icon.svg` | File exists; referenced in `snap.manifest.json` → `source.location.npm.iconPath`. |
|
||||
| In MetaMask | After installing the Snap | In Settings → Snaps, “Chain 138” shows the purple “138” icon (or your icon). Icon is clear and not pixelated. |
|
||||
|
||||
If you use a different format (e.g. PNG), ensure `iconPath` and `files` in package.json include it and the manifest points to the correct path.
|
||||
|
||||
### 4.2 Companion site assets
|
||||
|
||||
| Item | Location | Check |
|
||||
| ------------------- | ---------------------------------------------------------------- | -------------------------------------------------------- |
|
||||
| Site favicon / icon | `packages/site/gatsby-config.ts` → `icon: 'src/assets/logo.svg'` | File exists; build succeeds; browser tab shows the icon. |
|
||||
| Header logo | `packages/site/src/components/Header.tsx` → `<SnapLogo />` | Logo renders in the header; no broken image. |
|
||||
|
||||
Ensure `src/assets/logo.svg` (or the path you use) exists. Add a favicon or apple-touch icon in Gatsby config if required for directory/submission.
|
||||
|
||||
### 4.3 Token list — logoURI (token-aggregation API)
|
||||
|
||||
The Snap and MetaMask token list depend on **`GET /api/v1/report/token-list`** returning Uniswap-style tokens with **`logoURI`** per token and a **list-level `logoURI`**.
|
||||
|
||||
| Check | How to verify |
|
||||
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| List-level logoURI | `curl -s "${API_BASE}/api/v1/report/token-list?chainId=138" \| jq '.logoURI'` — present and URL is reachable (e.g. ETH or list logo). |
|
||||
| Every token has logoURI | `curl -s "${API_BASE}/api/v1/report/token-list?chainId=138" \| jq '.tokens[] \| {symbol, logoURI}'` — no token has null or missing `logoURI`. |
|
||||
| URLs resolve | For each distinct `logoURI` in the response, open in browser or `curl -sI <url>` — expect 200 (or 301/302 to a valid asset). |
|
||||
|
||||
**Token-aggregation source:** In `smom-dbis-138/services/token-aggregation`, `src/config/canonical-tokens.ts` defines `LOGO_BY_SYMBOL` and `getLogoUriForSpec()`. Defaults use Trust Wallet assets and ethereum.org ETH diamond. For Chain 138–specific tokens, set `logoUrl` on the spec or ensure they fall back to a sensible default (e.g. ETH_LOGO). Add any new token symbols to `LOGO_BY_SYMBOL` or give them a `logoUrl` so no token is missing a logo.
|
||||
|
||||
### 4.4 Network icons (iconUrls) — token-aggregation API
|
||||
|
||||
**`GET /api/v1/networks`** must return each network with **`iconUrls`** (array of URLs) so wallets can show chain icons.
|
||||
|
||||
| Chain | Expected iconUrls (examples) | Verify |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------- |
|
||||
| Chain 138 (0x8a) | `https://explorer.d-bis.org/favicon.ico`, ETH diamond or similar | Each URL returns 200 (or redirect to valid image). |
|
||||
| Ethereum Mainnet (0x1) | `https://raw.githubusercontent.com/ethereum/ethereum.org/.../eth-diamond-black.png` | URL reachable. |
|
||||
| ALL Mainnet (651940) | `https://alltra.global/favicon.ico`, ETH diamond or similar | URL reachable. |
|
||||
|
||||
**Token-aggregation source:** `smom-dbis-138/services/token-aggregation/src/config/networks.ts` — each entry in `NETWORKS` has `iconUrls`. Ensure:
|
||||
|
||||
- [ ] `explorer.d-bis.org/favicon.ico` exists (explorer site serves a favicon).
|
||||
- [ ] `alltra.global/favicon.ico` exists (or update to a valid URL).
|
||||
- [ ] Any raw GitHub or CDN URLs (ethereum.org, Trust Wallet assets) are still valid and not 404.
|
||||
|
||||
### 4.5 Suggested screenshot list (for allowlist and docs)
|
||||
|
||||
Capture and keep for submission and `docs/images/`:
|
||||
|
||||
| File | Content |
|
||||
| -------------------------- | ----------------------------------------------------------------- |
|
||||
| `connect.png` | Companion site with Connect / Install Flask / Reconnect visible. |
|
||||
| `market-data-dialog.png` | Snap dialog from “Show market data” (tokens + prices). |
|
||||
| `bridge-routes-dialog.png` | Snap dialog from “Show bridge routes” (CCIP + Trustless). |
|
||||
| `swap-quote-dialog.png` | Snap dialog from “Show quote in Snap”. |
|
||||
| `dynamic-info-dialog.png` | Snap dialog from `show_dynamic_info` (networks + token list URL). |
|
||||
| `send-page.png` | Send on Chain 138 page (network switch + send form). |
|
||||
|
||||
See [FEATURES.md](FEATURES.md) “Screenshots and visuals” and [ALLOWLIST_FORM_FIELDS.md](../ALLOWLIST_FORM_FIELDS.md) “Images”.
|
||||
|
||||
---
|
||||
|
||||
## 5. RPC methods — full verification table
|
||||
|
||||
Test each method (e.g. via browser console `wallet_invokeSnap` from the companion site origin). Use **local** Snap ID for dev (`local:http://localhost:8000` or the URL your dev server uses) or **npm** for production-like (`npm:chain138-snap`).
|
||||
|
||||
| Method | Params (required / optional) | Expected | Verify |
|
||||
| ---------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| `hello` | — | `{ message: "Hello, …" }` | No error; message contains “Chain 138” or similar. |
|
||||
| `get_networks` | `apiBaseUrl` or `networksUrl` | `{ version, networks: [ … ] }` with EIP-3085 entries for 138, 1, 651940 | Each network has `chainId`, `rpcUrls`, `nativeCurrency`, `blockExplorerUrls`; **iconUrls** present and URLs reachable. |
|
||||
| `get_chain138_config` | `apiBaseUrl` or `networksUrl` | Single Chain 138 config object | Matches your chain (e.g. DeFi Oracle Meta Mainnet, RPCs, explorer). |
|
||||
| `get_chain138_market_chains` | `apiBaseUrl` | `{ chains: [ … ] }` | At least Chain 138; names and explorer URLs correct. |
|
||||
| `get_token_list_url` | `apiBaseUrl` or `tokenListUrl` | `{ tokenListUrl, description }` | URL is your API’s token-list endpoint (e.g. `…/api/v1/report/token-list`). |
|
||||
| `get_token_list` | `apiBaseUrl` or `tokenListUrl`; optional `chainId` | `{ tokens: [ … ] }` (Uniswap-style) | Each token has **logoURI**; list usable as MetaMask token list. |
|
||||
| `get_oracles` | `apiBaseUrl`; optional `chainId` | `{ version, chains: [ … oracles ] }` | Oracles for 138 (and others) if configured. |
|
||||
| `show_dynamic_info` | `apiBaseUrl` or `networksUrl` / `tokenListUrl` | MetaMask dialog | Dialog shows networks and token list URL; no “pass apiBaseUrl” error. |
|
||||
| `get_market_summary` | `apiBaseUrl`; optional `chainId` | `{ tokens: [ { symbol, name, address, market?: { priceUsd, volume24h } } ] }` | Tokens and optional prices; no error or empty when API is up. |
|
||||
| `show_market_data` | `apiBaseUrl`; optional `chainId` | MetaMask dialog | “Market data (Chain 138)” (or chosen chain) with token symbols and prices. |
|
||||
| `get_bridge_routes` | `apiBaseUrl` or `bridgeListUrl` | `{ routes, chain138Bridges?, … }` | CCIP (WETH9/WETH10) and, if configured, Trustless routes. |
|
||||
| `show_bridge_routes` | `apiBaseUrl` or `bridgeListUrl` | MetaMask dialog | Dialog lists bridge routes. |
|
||||
| `get_swap_quote` | `apiBaseUrl`, `tokenIn`, `tokenOut`, `amountIn`; optional `chainId` | `{ amountOut?, error?, poolAddress? }` | When pool exists: `amountOut` present; when not: `error` or null amountOut. |
|
||||
| `show_swap_quote` | Same as `get_swap_quote` | MetaMask dialog | Dialog shows quote or “no quote” message. |
|
||||
|
||||
**Error cases to test:**
|
||||
|
||||
- [ ] `get_networks` without `apiBaseUrl` and without `networksUrl`: returns error message asking for params.
|
||||
- [ ] `get_market_summary` with invalid or down API: returns `{ error, tokens: [] }` or similar; no uncaught exception.
|
||||
- [ ] `show_*` methods without `apiBaseUrl` (when required): dialog or alert asks user to pass `apiBaseUrl`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Companion site — every card and page
|
||||
|
||||
### 6.1 Home page (index)
|
||||
|
||||
| Element | Check |
|
||||
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Connect / Install Flask / Reconnect | Correct button or link visible; clicking triggers MetaMask. |
|
||||
| “Add Chain 138” / network card | If present, invoking Snap to add network works. |
|
||||
| **Market data** card | “Show market data” opens Snap dialog; “Fetch market summary” shows tokens/prices below (when `GATSBY_SNAP_API_BASE_URL` is set). |
|
||||
| **Bridge** card | “Show bridge routes” opens Snap dialog with routes. |
|
||||
| **Swap quote** card | Enter token In/Out (addresses) and amount; “Get quote” shows amountOut (or error); “Show quote in Snap” opens dialog. |
|
||||
| Notice / Send link | Link to “Send on Chain 138” is present and correct (e.g. `./send` or `/send`). |
|
||||
| Footer | Version or build info if configured; no broken links. |
|
||||
|
||||
If `GATSBY_SNAP_API_BASE_URL` is not set, Market/Bridge/Swap cards should show a clear message (e.g. “Set GATSBY_SNAP_API_BASE_URL”) instead of failing silently.
|
||||
|
||||
### 6.2 Send page (`/send`)
|
||||
|
||||
| Step | Check |
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Navigate | Open `/send` (or path under your pathPrefix, e.g. `/snap/send`). Page loads. |
|
||||
| “Switch to Chain 138” | Click; MetaMask prompts to add/switch network; after approval, message confirms “Switched to Chain 138.” |
|
||||
| Send form | Enter recipient address and amount. |
|
||||
| Send transaction | Submit; MetaMask shows tx confirmation; after approval, tx is sent on Chain 138 and message or link to explorer is shown. |
|
||||
| Back link | “Back to Chain 138 Snap” (or similar) returns to home. |
|
||||
|
||||
Use a test wallet with a small balance on Chain 138 to avoid real funds at risk.
|
||||
|
||||
---
|
||||
|
||||
## 7. Production-like test
|
||||
|
||||
Before publishing (or after publishing a version you intend to allowlist):
|
||||
|
||||
1. **Publish to npm** (or use the latest published version): `pnpm run publish:snap` (see [PUSH_AND_PUBLISH.md](../PUSH_AND_PUBLISH.md)).
|
||||
2. **Build companion site with npm Snap:** Set `GATSBY_SNAP_ORIGIN=npm:chain138-snap` and `GATSBY_SNAP_API_BASE_URL` to your **production** token-aggregation URL (e.g. `https://explorer.d-bis.org` if API is proxied there). Build and serve (or deploy).
|
||||
3. **Clean MetaMask Flask:** Remove the Snap if it was installed from local; optionally use a fresh profile.
|
||||
4. **Install from production site:** Open the deployed companion site (e.g. https://explorer.d-bis.org/snap/). Connect and install the Snap — it should install from **npm** (no localhost). Verify Snap appears in Settings → Snaps as “Chain 138” with correct icon.
|
||||
5. **Run through:** Repeat the RPC checks and companion site cards using the **production** API. Confirm market data, bridge, and swap quote work when the production API is up.
|
||||
6. **Connected site:** In Snap settings, confirm the companion site origin is listed under “Connected sites” and can invoke the Snap.
|
||||
|
||||
---
|
||||
|
||||
## 8. Allowlist and directory submission
|
||||
|
||||
If you are submitting or updating the Snap in the MetaMask directory:
|
||||
|
||||
- [ ] Use [ALLOWLIST_FORM_FIELDS.md](../ALLOWLIST_FORM_FIELDS.md) for exact field values (Snap name, repo, npm, **version**).
|
||||
- [ ] **Version:** Must match `packages/snap/package.json` and `snap.manifest.json` (e.g. 0.1.2).
|
||||
- [ ] **Images:** Upload screenshots as required (companion site, Snap dialogs — see §4.5).
|
||||
- [ ] **Demo video (optional):** Short walkthrough of install and use (Connect, add network, market data, bridge, swap quote, Send page).
|
||||
- [ ] **Compliance:** [ALLOWLIST_SOURCE_AND_COMPLIANCE_CHECKLIST.md](../ALLOWLIST_SOURCE_AND_COMPLIANCE_CHECKLIST.md) — repo public, no key-management APIs, etc.
|
||||
|
||||
---
|
||||
|
||||
## 9. Recommendations and suggestions
|
||||
|
||||
- **Snapper / security:** If available, run [Snapper](https://docs.metamask.io/snaps/how-to/get-allowlisted/) (or the MetaMask security scanner) locally before publish.
|
||||
- **Real wallet on Chain 138:** Test with a wallet that has a small balance on Chain 138 so in-wallet balance and the Send page reflect real behavior (and so you can verify token list and network icons in MetaMask).
|
||||
- **Deployed companion site:** Test the full flow from the **deployed** companion site (e.g. https://explorer.d-bis.org/snap/) and from a different origin to confirm CORS and “Connected sites” behavior.
|
||||
- **API health:** Before a release, confirm token-aggregation (or your API) is up and that `/api/v1/networks`, `/api/v1/report/token-list`, `/api/v1/bridge/routes`, `/api/v1/quote`, and `/api/v1/tokens` respond as expected. Document any env (e.g. `NETWORKS_JSON_URL`, `TOKEN_LIST_JSON_URL`) so operators can run the same checks.
|
||||
- **Changelog / version:** Keep a short changelog (e.g. in README or CHANGELOG.md) and bump version deliberately; note any breaking changes for integrators (e.g. `apiBaseUrl` requirement).
|
||||
- **Token list and logos:** When adding new tokens to the token-aggregation canonical list, always set **logoURI** (or `logoUrl` in spec) so MetaMask and the Snap never show a token without a logo.
|
||||
- **Network icons:** When adding or changing chains in token-aggregation `networks.ts`, always set **iconUrls** and ensure URLs are stable and reachable (favicon or official chain logo).
|
||||
|
||||
---
|
||||
|
||||
## 10. Final sign-off checklist (before publish)
|
||||
|
||||
Use this as a single pass/fail before `pnpm run publish:snap`.
|
||||
|
||||
**Automatable (run `bash scripts/verify-pre-publish.sh`):** Build, unit tests, package contents, manifest/package version, Prettier check. Optional: `pnpm run test:e2e` (set `SKIP_E2E=1` to skip).
|
||||
|
||||
- [ ] **Build:** `pnpm run build` succeeds; manifest shasum is correct.
|
||||
- [ ] **Tests:** `pnpm run test` passes; `pnpm run lint:misc --check` (Prettier) passes. *(Full `pnpm run lint` includes ESLint; repo may have existing ESLint rule warnings.)*
|
||||
- [ ] **Snap package:** `dist/bundle.js`, `images/icon.svg`, `snap.manifest.json` present; `package.json` version and `files` correct.
|
||||
- [ ] **Snap icon:** Icon displays correctly in MetaMask Snap list/detail.
|
||||
- [ ] **Token list:** Every token from `/api/v1/report/token-list` has a valid **logoURI**; list-level logoURI set.
|
||||
- [ ] **Networks:** Each network from `/api/v1/networks` has **iconUrls**; all URLs reachable.
|
||||
- [ ] **RPC methods:** All methods in §5 tested; success and error paths as expected.
|
||||
- [ ] **Companion site:** All cards (Market, Bridge, Swap) and Send page tested; links and messages correct.
|
||||
- [ ] **Production-like:** Installed from npm and tested with production API (if applicable).
|
||||
- [ ] **Allowlist:** If submitting/updating directory, form fields and screenshots ready (§8).
|
||||
|
||||
When all items are checked, the Snap is ready for publish and (optionally) directory submission.
|
||||
|
||||
---
|
||||
|
||||
**Related docs**
|
||||
|
||||
- **Script:** `scripts/verify-pre-publish.sh` — runs build, test, package contents, version check, Prettier. Use before publish.
|
||||
- [TESTING_INSTRUCTIONS.md](../TESTING_INSTRUCTIONS.md) — RPC examples and E2E checklist.
|
||||
- [E2E_PREPARATION.md](../E2E_PREPARATION.md) — Token-aggregation and env setup.
|
||||
- [MANUAL_E2E_CHECKLIST.md](../MANUAL_E2E_CHECKLIST.md) — Short manual checklist.
|
||||
- [CHAIN138_SNAP_TROUBLESHOOTING.md](CHAIN138_SNAP_TROUBLESHOOTING.md) — Balance, swap, and API issues.
|
||||
- [PUSH_AND_PUBLISH.md](../PUSH_AND_PUBLISH.md) — Version bump and npm publish.
|
||||
- [ALLOWLIST_FORM_FIELDS.md](../ALLOWLIST_FORM_FIELDS.md) — Directory form and images.
|
||||
38
chain138-snap/docs/README.md
Normal file
38
chain138-snap/docs/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Chain 138 Snap — Documentation index
|
||||
|
||||
Documentation for the **Chain 138 Snap** (MetaMask): network params, token list, market data, swap quotes, and CCIP and Trustless bridge routes for Chain 138 and ALL Mainnet.
|
||||
|
||||
## Quick links
|
||||
|
||||
| Doc | Description |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| [FEATURES.md](FEATURES.md) | **All RPC methods and features** — method matrix, params, response shapes, blockchains (138 + 651940), flow diagram |
|
||||
| [CONTRIBUTING.md](CONTRIBUTING.md) | How to contribute: setup, testing, linting, publishing |
|
||||
| [FAQ.md](FAQ.md) | Frequently asked questions about the Snap |
|
||||
| [DEPLOY_COMPANION_SITE.md](DEPLOY_COMPANION_SITE.md) | Build and deploy the companion site to your own host |
|
||||
| [RUNBOOK.md](RUNBOOK.md) | Build, test, publish quick reference |
|
||||
|
||||
## Root-level docs (repo root)
|
||||
|
||||
| Doc | Description |
|
||||
| -------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| [README](../README.md) | Project overview, getting started |
|
||||
| [INTEGRATORS](../INTEGRATORS.md) | Integrator guide: Snap ID, apiBaseUrl, RPC methods |
|
||||
| [TESTING_INSTRUCTIONS](../TESTING_INSTRUCTIONS.md) | Full testing guide: dev server, RPC examples, E2E checklist |
|
||||
| [E2E_PREPARATION](../E2E_PREPARATION.md) | Token-aggregation and companion site setup for E2E |
|
||||
| [MANUAL_E2E_CHECKLIST](../MANUAL_E2E_CHECKLIST.md) | Short manual E2E checklist |
|
||||
| [PUSH_AND_PUBLISH](../PUSH_AND_PUBLISH.md) | Push to GitHub, publish to npm, allowlist |
|
||||
| [PACKAGE_MANAGER](../PACKAGE_MANAGER.md) | pnpm vs Yarn |
|
||||
| [SECURITY](../SECURITY.md) | Security notes: HTTPS, API, permissions |
|
||||
| [ALLOWLIST_FORM_FIELDS](../ALLOWLIST_FORM_FIELDS.md) | MetaMask allowlist form fields |
|
||||
| [ALLOWLIST_SOURCE_AND_COMPLIANCE_CHECKLIST](../ALLOWLIST_SOURCE_AND_COMPLIANCE_CHECKLIST.md) | Allowlist compliance checklist |
|
||||
| [NEXT_STEPS](../NEXT_STEPS.md) | Completed items and future releases |
|
||||
|
||||
## Visual elements
|
||||
|
||||
- **Feature overview and method matrix:** [FEATURES.md](FEATURES.md) includes ASCII feature overview, RPC method matrix (all methods × Chain 138 / ALL Mainnet × params × UI), and a Mermaid request-flow diagram.
|
||||
- **Screenshots:** Optional screenshots (Connect UI, market data dialog, bridge dialog, swap quote dialog) can be added under [docs/images/](images/). See the "Screenshots and visuals" section in [FEATURES.md](FEATURES.md) for suggested filenames.
|
||||
|
||||
## Snap package (npm)
|
||||
|
||||
The published Snap package has its own README on npm; a copy lives in [packages/snap/README.md](../packages/snap/README.md).
|
||||
45
chain138-snap/docs/RUNBOOK.md
Normal file
45
chain138-snap/docs/RUNBOOK.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Chain 138 Snap — Runbook
|
||||
|
||||
Quick reference for building, testing, and publishing the Snap and companion site.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
# Snap + site
|
||||
pnpm run build
|
||||
|
||||
# Companion site only (e.g. for deploy)
|
||||
GATSBY_PATH_PREFIX=/snap pnpm --filter site run build
|
||||
# With production API:
|
||||
# GATSBY_SNAP_API_BASE_URL=https://your-token-aggregation-api.com
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
pnpm run test # Snap unit tests (Jest)
|
||||
pnpm run test:e2e # Playwright E2E (companion site)
|
||||
pnpm run lint # ESLint + Prettier
|
||||
```
|
||||
|
||||
## Publish Snap to npm
|
||||
|
||||
From repo root:
|
||||
|
||||
```bash
|
||||
pnpm run build
|
||||
pnpm run publish:snap # uses NPM_ACCESS_TOKEN from .env if set
|
||||
```
|
||||
|
||||
See [PUSH_AND_PUBLISH.md](../PUSH_AND_PUBLISH.md).
|
||||
|
||||
## Deploy companion site
|
||||
|
||||
Build with path prefix and API URL, then upload `packages/site/public/` to your static host. See [DEPLOY_COMPANION_SITE.md](DEPLOY_COMPANION_SITE.md).
|
||||
|
||||
## Validate token/list URLs
|
||||
|
||||
```bash
|
||||
./scripts/validate-token-lists.sh [URL1] [URL2] ...
|
||||
# Or set TOKEN_LIST_JSON_URL, BRIDGE_LIST_JSON_URL, NETWORKS_JSON_URL
|
||||
```
|
||||
67
chain138-snap/docs/TOKEN_LIST_AND_ICONS_GUIDE.md
Normal file
67
chain138-snap/docs/TOKEN_LIST_AND_ICONS_GUIDE.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Chain 138 Snap — Token List and Icons Guide
|
||||
|
||||
How to display tokens and icons in MetaMask when using the Chain 138 Snap.
|
||||
|
||||
---
|
||||
|
||||
## Why tokens and icons may not show
|
||||
|
||||
- **Chain 138 is not in MetaMask’s built-in token detection.** MetaMask only auto-detects tokens on Ethereum, Polygon, Arbitrum, Optimism, Base, zkSync, etc. Chain 138 and ALL Mainnet (651940) are custom chains, so you must add the token list or import tokens manually.
|
||||
- **Icons come from the token list.** Each token needs a `logoURI` and each network needs `iconUrls`. If those URLs are missing or unreachable, MetaMask shows no icons.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Get the token list URL
|
||||
|
||||
1. Open the Snap companion site (e.g. https://explorer.d-bis.org/snap/).
|
||||
2. Connect MetaMask (or MetaMask Flask) and install the Chain 138 Snap.
|
||||
3. Click **"Show dynamic info"**.
|
||||
4. The Snap dialog shows the **Token list URL** (e.g. `https://explorer.d-bis.org/api/v1/report/token-list`).
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Add the token list in MetaMask
|
||||
|
||||
### If MetaMask supports custom token list URLs
|
||||
|
||||
- Go to **Settings → Security & Privacy** (or equivalent).
|
||||
- Find **Token list** and add the URL from Step 1.
|
||||
- MetaMask will fetch the list and display tokens with icons when you switch to Chain 138 or ALL Mainnet.
|
||||
|
||||
### If MetaMask does not support custom token list URLs
|
||||
|
||||
Add tokens manually:
|
||||
|
||||
1. Switch to Chain 138 in MetaMask.
|
||||
2. Go to **Tokens** tab → **Import tokens** (or the plus button).
|
||||
3. Select **Custom token**.
|
||||
4. Enter the token contract address (see [CHAIN138_TOKEN_ADDRESSES](../../../docs/11-references/CHAIN138_TOKEN_ADDRESSES.md) for cUSDC, cUSDT, etc.).
|
||||
5. MetaMask will fill symbol and decimals. Click **Import**.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Verify icons
|
||||
|
||||
If tokens show but icons are missing:
|
||||
|
||||
- **Token icons:** The token-aggregation API (`GET /api/v1/report/token-list`) must return a `logoURI` for each token. Operators: see [PRE_PUBLISH_TESTING.md](PRE_PUBLISH_TESTING.md) §4.3.
|
||||
- **Network icons:** The networks API (`GET /api/v1/networks`) must return `iconUrls` for each chain. Operators: see [PRE_PUBLISH_TESTING.md](PRE_PUBLISH_TESTING.md) §4.4.
|
||||
- **Logo URLs:** If logo URLs (e.g. raw.githubusercontent.com) are blocked or return 404, operators can host logos locally and update the token-aggregation config.
|
||||
|
||||
---
|
||||
|
||||
## Operators: verify API and icons
|
||||
|
||||
Run the verification script to check token list, networks, logoURIs, and iconUrls:
|
||||
|
||||
```bash
|
||||
./scripts/verify-snap-api-and-icons.sh [API_BASE_URL]
|
||||
# Example: ./scripts/verify-snap-api-and-icons.sh https://explorer.d-bis.org
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related docs
|
||||
|
||||
- [CHAIN138_SNAP_TROUBLESHOOTING.md](CHAIN138_SNAP_TROUBLESHOOTING.md) — §6 No icons or tokens showing
|
||||
- [PRE_PUBLISH_TESTING.md](PRE_PUBLISH_TESTING.md) — §4.3 Token list logoURI, §4.4 Network iconUrls
|
||||
1
chain138-snap/docs/images/.gitkeep
Normal file
1
chain138-snap/docs/images/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# Add screenshots here: connect.png, market-data-dialog.png, bridge-routes-dialog.png, swap-quote-dialog.png, dynamic-info-dialog.png (see docs/FEATURES.md)
|
||||
@@ -4,14 +4,23 @@ test.describe('Chain 138 Snap companion site', () => {
|
||||
test('loads and shows Connect, Install, or Reconnect', async ({ page }) => {
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
||||
// Connect and Reconnect are buttons; Install MetaMask Flask is a link
|
||||
const connectOrReconnect = page.getByRole('button', { name: /Connect|Reconnect/i });
|
||||
const installLink = page.getByRole('link', { name: /Install MetaMask Flask/i });
|
||||
await expect(connectOrReconnect.or(installLink).first()).toBeVisible({ timeout: 30_000 });
|
||||
const connectOrReconnect = page.getByRole('button', {
|
||||
name: /Connect|Reconnect/i,
|
||||
});
|
||||
const installLink = page.getByRole('link', {
|
||||
name: /Install MetaMask Flask/i,
|
||||
});
|
||||
await expect(connectOrReconnect.or(installLink).first()).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
});
|
||||
|
||||
test('page has Snap-related content', async ({ page }) => {
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
||||
const body = page.locator('body');
|
||||
await expect(body).toContainText(/Connect|Chain 138|Get started|Snap|Install|Reconnect/i, { timeout: 30_000 });
|
||||
await expect(body).toContainText(
|
||||
/Connect|Chain 138|Get started|Snap|Install|Reconnect/i,
|
||||
{ timeout: 30_000 },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,11 +23,21 @@
|
||||
"lint:eslint": "eslint . --cache",
|
||||
"lint:fix": "pnpm run lint:eslint --fix && pnpm run lint:misc --write",
|
||||
"lint:misc": "prettier '**/*.json' '**/*.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern",
|
||||
"publish:snap": "bash scripts/publish-snap-to-npm.sh",
|
||||
"start": "pnpm -r --parallel run start",
|
||||
"test": "pnpm --filter chain138-snap run build && pnpm --filter chain138-snap run test",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"publish:snap": "bash scripts/publish-snap-to-npm.sh"
|
||||
"test:e2e:ui": "playwright test --ui"
|
||||
},
|
||||
"resolutions": {
|
||||
"@sigmacomputing/babel-plugin-lodash>glob": "^7.2.0",
|
||||
"cookie": "^0.7.1",
|
||||
"gatsby>glob": "^7.2.0",
|
||||
"glob": "^10.5.0",
|
||||
"path-to-regexp@0.1.10": "^0.1.12",
|
||||
"sharp": "^0.34.5",
|
||||
"socket.io": "^4.8.1",
|
||||
"ws": "^8.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lavamoat/allow-scripts": "^3.4.2",
|
||||
@@ -57,31 +67,34 @@
|
||||
"node": ">=18.6.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"gatsby",
|
||||
"sharp"
|
||||
],
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
"eslint": "9",
|
||||
"react": "18",
|
||||
"@metamask/providers": "22",
|
||||
"@metamask/snaps-controllers": "17",
|
||||
"@typescript-eslint/eslint-plugin": "5",
|
||||
"@typescript-eslint/parser": "5",
|
||||
"eslint-plugin-jest": "28"
|
||||
"eslint": "9",
|
||||
"eslint-plugin-jest": "28",
|
||||
"react": "18"
|
||||
}
|
||||
},
|
||||
"onlyBuiltDependencies": [
|
||||
"gatsby",
|
||||
"sharp"
|
||||
],
|
||||
"overrides": {
|
||||
"@sigmacomputing/babel-plugin-lodash>glob": "^7.2.0",
|
||||
"cookie": "^0.7.1",
|
||||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"path-to-regexp@0.1.10": "^0.1.12",
|
||||
"sharp": "^0.33.5",
|
||||
"socket.io": "^4.8.1",
|
||||
"ws": "^8.17.1",
|
||||
"gatsby>eslint": "^7.32.0",
|
||||
"eslint-config-react-app>eslint": "^7.32.0",
|
||||
"eslint-plugin-flowtype>eslint": "^7.32.0"
|
||||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-plugin-flowtype>eslint": "^7.32.0",
|
||||
"gatsby>eslint": "^7.32.0",
|
||||
"gatsby>glob": "^7.2.0",
|
||||
"glob": "^10.5.0",
|
||||
"path-to-regexp@0.1.10": "^0.1.12",
|
||||
"sharp": "^0.34.5",
|
||||
"socket.io": "^4.8.1",
|
||||
"ws": "^8.17.1"
|
||||
}
|
||||
},
|
||||
"lavamoat": {
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
*/
|
||||
SNAP_ORIGIN=
|
||||
|
||||
# Snap origin for production (must be GATSBY_* so it is inlined into the client bundle).
|
||||
# E.g. npm:chain138-snap. Leave empty for local (local:http://localhost:8080).
|
||||
GATSBY_SNAP_ORIGIN=
|
||||
|
||||
# Token-aggregation API base URL for Snap (market data, token list, bridge routes, quotes).
|
||||
# E2E (local): http://localhost:3000
|
||||
# Production: set to your live token-aggregation API (e.g. https://api.example.com). No trailing slash.
|
||||
|
||||
@@ -4,6 +4,11 @@ import { StrictMode } from 'react';
|
||||
import { App } from './src/App';
|
||||
import { Root } from './src/Root';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options0
|
||||
* @param options0.element
|
||||
*/
|
||||
export const wrapRootElement: GatsbyBrowser['wrapRootElement'] = ({
|
||||
element,
|
||||
}) => (
|
||||
@@ -12,6 +17,11 @@ export const wrapRootElement: GatsbyBrowser['wrapRootElement'] = ({
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options0
|
||||
* @param options0.element
|
||||
*/
|
||||
export const wrapPageElement: GatsbyBrowser['wrapPageElement'] = ({
|
||||
element,
|
||||
}) => <App>{element}</App>;
|
||||
|
||||
@@ -12,12 +12,12 @@ const config: GatsbyConfig = {
|
||||
{
|
||||
resolve: 'gatsby-plugin-manifest',
|
||||
options: {
|
||||
name: 'Template Snap',
|
||||
name: 'Chain 138 Snap',
|
||||
icon: 'src/assets/logo.svg',
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
theme_color: '#6F4CFF',
|
||||
background_color: '#FFFFFF',
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
display: 'standalone',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,12 +4,22 @@ import { StrictMode } from 'react';
|
||||
import { App } from './src/App';
|
||||
import { Root } from './src/Root';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options0
|
||||
* @param options0.element
|
||||
*/
|
||||
export const wrapRootElement: GatsbySSR['wrapRootElement'] = ({ element }) => (
|
||||
<StrictMode>
|
||||
<Root>{element}</Root>
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options0
|
||||
* @param options0.element
|
||||
*/
|
||||
export const wrapPageElement: GatsbySSR['wrapPageElement'] = ({ element }) => (
|
||||
<App>{element}</App>
|
||||
);
|
||||
|
||||
@@ -14,10 +14,21 @@ const Wrapper = styled.div`
|
||||
max-width: 100vw;
|
||||
`;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export type AppProps = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options0
|
||||
* @param options0.children
|
||||
*/
|
||||
export const App: FunctionComponent<AppProps> = ({ children }) => {
|
||||
const toggleTheme = useContext(ToggleThemeContext);
|
||||
|
||||
|
||||
@@ -6,19 +6,36 @@ import { dark, light } from './config/theme';
|
||||
import { MetaMaskProvider } from './hooks';
|
||||
import { getThemePreference, setLocalStorage } from './utils';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export type RootProps = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
type ToggleTheme = () => void;
|
||||
|
||||
export const ToggleThemeContext = createContext<ToggleTheme>(
|
||||
(): void => undefined,
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options0
|
||||
* @param options0.children
|
||||
*/
|
||||
export const Root: FunctionComponent<RootProps> = ({ children }) => {
|
||||
const [darkTheme, setDarkTheme] = useState(getThemePreference());
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const toggleTheme: ToggleTheme = () => {
|
||||
setLocalStorage('theme', darkTheme ? 'light' : 'dark');
|
||||
setDarkTheme(!darkTheme);
|
||||
|
||||
@@ -70,6 +70,9 @@ const ConnectedIndicator = styled.div`
|
||||
background-color: green;
|
||||
`;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const InstallFlaskButton = () => (
|
||||
<Link href="https://metamask.io/flask/" target="_blank">
|
||||
<FlaskFox />
|
||||
@@ -77,15 +80,31 @@ export const InstallFlaskButton = () => (
|
||||
</Link>
|
||||
);
|
||||
|
||||
export const ConnectButton = (props: ComponentProps<typeof Button>) => {
|
||||
/**
|
||||
*
|
||||
* @param props
|
||||
*/
|
||||
export const ConnectButton = (
|
||||
props: ComponentProps<typeof Button> & {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
label?: string;
|
||||
},
|
||||
) => {
|
||||
const { label = 'Connect', ...rest } = props;
|
||||
return (
|
||||
<Button {...props}>
|
||||
<Button {...rest}>
|
||||
<FlaskFox />
|
||||
<ButtonText>Connect</ButtonText>
|
||||
<ButtonText>{label}</ButtonText>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param props
|
||||
*/
|
||||
export const ReconnectButton = (props: ComponentProps<typeof Button>) => {
|
||||
return (
|
||||
<Button {...props}>
|
||||
@@ -95,10 +114,17 @@ export const ReconnectButton = (props: ComponentProps<typeof Button>) => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param props
|
||||
*/
|
||||
export const SendHelloButton = (props: ComponentProps<typeof Button>) => {
|
||||
return <Button {...props}>Send message</Button>;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const HeaderButtons = () => {
|
||||
const requestSnap = useRequestSnap();
|
||||
const { isFlask, installedSnap } = useMetaMask();
|
||||
@@ -108,7 +134,12 @@ export const HeaderButtons = () => {
|
||||
}
|
||||
|
||||
if (!installedSnap) {
|
||||
return <ConnectButton onClick={requestSnap} />;
|
||||
return (
|
||||
<ConnectButton
|
||||
onClick={requestSnap}
|
||||
label={isFlask ? 'Connect MetaMask Flask' : 'Connect'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldDisplayReconnectButton(installedSnap)) {
|
||||
|
||||
@@ -1,18 +1,45 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
type CardProps = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
content: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
description: ReactNode;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
button?: ReactNode;
|
||||
};
|
||||
/**
|
||||
*
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
fullWidth?: boolean;
|
||||
};
|
||||
|
||||
const CardWrapper = styled.div<{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
fullWidth?: boolean | undefined;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
disabled?: boolean | undefined;
|
||||
}>`
|
||||
display: flex;
|
||||
@@ -48,6 +75,13 @@ const Description = styled.div`
|
||||
margin-bottom: 2.4rem;
|
||||
`;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options0
|
||||
* @param options0.content
|
||||
* @param options0.disabled
|
||||
* @param options0.fullWidth
|
||||
*/
|
||||
export const Card = ({ content, disabled = false, fullWidth }: CardProps) => {
|
||||
const { title, description, button } = content;
|
||||
return (
|
||||
|
||||
@@ -39,6 +39,9 @@ const PoweredByContainer = styled.div`
|
||||
margin-left: 1rem;
|
||||
`;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const Footer = () => {
|
||||
const theme = useTheme();
|
||||
const buildVersion = getBuildVersion();
|
||||
|
||||
@@ -36,9 +36,17 @@ const RightContainer = styled.div`
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options0
|
||||
* @param options0.handleToggleClick
|
||||
*/
|
||||
export const Header = ({
|
||||
handleToggleClick,
|
||||
}: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
handleToggleClick: () => void;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
@@ -47,7 +55,7 @@ export const Header = ({
|
||||
<HeaderWrapper>
|
||||
<LogoWrapper>
|
||||
<SnapLogo color={theme.colors.icon?.default} size={36} />
|
||||
<Title>template-snap</Title>
|
||||
<Title>Chain 138 Snap</Title>
|
||||
</LogoWrapper>
|
||||
<RightContainer>
|
||||
<Toggle
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
export const MetaMask = ({ color }: { color?: string | undefined }) => (
|
||||
/**
|
||||
*
|
||||
* @param options0
|
||||
* @param options0.color
|
||||
*/
|
||||
export const MetaMask = ({
|
||||
color,
|
||||
}: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
color?: string | undefined;
|
||||
}) => (
|
||||
<svg
|
||||
width="98"
|
||||
height="12"
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
export const PoweredBy = ({ color }: { color?: string | undefined }) => (
|
||||
/**
|
||||
*
|
||||
* @param options0
|
||||
* @param options0.color
|
||||
*/
|
||||
export const PoweredBy = ({
|
||||
color,
|
||||
}: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
color?: string | undefined;
|
||||
}) => (
|
||||
<svg
|
||||
width="60"
|
||||
height="12"
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
/**
|
||||
*
|
||||
* @param options0
|
||||
* @param options0.color
|
||||
* @param options0.size
|
||||
*/
|
||||
export const SnapLogo = ({
|
||||
color,
|
||||
size,
|
||||
}: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
color?: string | undefined;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
size: number;
|
||||
}) => (
|
||||
<svg
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
type CheckedProps = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
readonly checked: boolean;
|
||||
};
|
||||
|
||||
@@ -90,15 +96,30 @@ const ToggleCircle = styled.div<CheckedProps>`
|
||||
transition: all 0.25s ease;
|
||||
`;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param options0
|
||||
* @param options0.onToggle
|
||||
* @param options0.defaultChecked
|
||||
*/
|
||||
export const Toggle = ({
|
||||
onToggle,
|
||||
defaultChecked = false,
|
||||
}: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
onToggle: () => void;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
defaultChecked?: boolean;
|
||||
}) => {
|
||||
const [checked, setChecked] = useState(defaultChecked);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const handleChange = () => {
|
||||
onToggle();
|
||||
setChecked(!checked);
|
||||
|
||||
@@ -5,10 +5,16 @@ export { defaultSnapOrigin } from './snap';
|
||||
* Set GATSBY_SNAP_API_BASE_URL in .env or .env.production for production.
|
||||
*/
|
||||
export const getSnapApiBaseUrl = (): string =>
|
||||
(typeof process !== 'undefined' &&
|
||||
process.env?.GATSBY_SNAP_API_BASE_URL) ||
|
||||
(typeof process !== 'undefined' && process.env?.GATSBY_SNAP_API_BASE_URL) ||
|
||||
'';
|
||||
|
||||
/**
|
||||
* Public origin of the Snap companion site (e.g. https://explorer.d-bis.org).
|
||||
* Set GATSBY_SNAP_SITE_URL so "Send on Chain 138" link is absolute HTTPS and never redirects to HTTP.
|
||||
*/
|
||||
export const getSnapSiteUrl = (): string =>
|
||||
(typeof process !== 'undefined' && process.env?.GATSBY_SNAP_SITE_URL) || '';
|
||||
|
||||
/** Build ID or git SHA for support/debug (set at build: GATSBY_BUILD_SHA, GATSBY_APP_VERSION). */
|
||||
export const getBuildVersion = (): string =>
|
||||
(typeof process !== 'undefined' &&
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
* The snap origin to use.
|
||||
* Will default to the local hosted snap if no value is provided in environment.
|
||||
*
|
||||
* You may be tempted to change this to the URL where your production snap is hosted, but please
|
||||
* don't. Instead, rename `.env.production.dist` to `.env.production` and set the production URL
|
||||
* there. Running `yarn build` will automatically use the production environment variables.
|
||||
* Use GATSBY_SNAP_ORIGIN so Gatsby inlines it into the client bundle (only GATSBY_*
|
||||
* vars are exposed to the browser). E.g. GATSBY_SNAP_ORIGIN=npm:chain138-snap for production.
|
||||
*/
|
||||
export const defaultSnapOrigin =
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
process.env.SNAP_ORIGIN ?? `local:http://localhost:8080`;
|
||||
process.env.GATSBY_SNAP_ORIGIN ??
|
||||
process.env.SNAP_ORIGIN ??
|
||||
`local:http://localhost:8080`;
|
||||
|
||||
@@ -5,11 +5,29 @@ import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import type { Snap } from '../types';
|
||||
import { getSnapsProvider } from '../utils';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
type MetaMaskContextType = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
provider: MetaMaskInpageProvider | null;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
installedSnap: Snap | null;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
error: Error | null;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
setInstalledSnap: (snap: Snap | null) => void;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
setError: (error: Error) => void;
|
||||
};
|
||||
|
||||
@@ -17,9 +35,15 @@ export const MetaMaskContext = createContext<MetaMaskContextType>({
|
||||
provider: null,
|
||||
installedSnap: null,
|
||||
error: null,
|
||||
/**
|
||||
*
|
||||
*/
|
||||
setInstalledSnap: () => {
|
||||
/* no-op */
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
setError: () => {
|
||||
/* no-op */
|
||||
},
|
||||
@@ -32,7 +56,14 @@ export const MetaMaskContext = createContext<MetaMaskContextType>({
|
||||
* @param props.children - React component to be wrapped by the Provider.
|
||||
* @returns JSX.
|
||||
*/
|
||||
export const MetaMaskProvider = ({ children }: { children: ReactNode }) => {
|
||||
export const MetaMaskProvider = ({
|
||||
children,
|
||||
}: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
const [provider, setProvider] = useState<MetaMaskInpageProvider | null>(null);
|
||||
const [installedSnap, setInstalledSnap] = useState<Snap | null>(null);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { useRequest } from './useRequest';
|
||||
import { defaultSnapOrigin } from '../config';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export type InvokeSnapParams = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
method: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
params?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
|
||||
@@ -20,13 +20,17 @@ export const useMetaMask = () => {
|
||||
|
||||
/**
|
||||
* Detect if the version of MetaMask is Flask.
|
||||
* web3_clientVersion returns a string (e.g. "MetaMask/v11.0.0-flask").
|
||||
*/
|
||||
const detectFlask = async () => {
|
||||
const clientVersion = await request({
|
||||
method: 'web3_clientVersion',
|
||||
});
|
||||
|
||||
const isFlaskDetected = (clientVersion as string[])?.includes('flask');
|
||||
const versionStr = Array.isArray(clientVersion)
|
||||
? (clientVersion as string[]).join(' ')
|
||||
: String(clientVersion ?? '');
|
||||
const isFlaskDetected = versionStr.toLowerCase().includes('flask');
|
||||
|
||||
setIsFlask(isFlaskDetected);
|
||||
};
|
||||
@@ -43,6 +47,9 @@ export const useMetaMask = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const detect = async () => {
|
||||
if (provider) {
|
||||
await detectFlask();
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { RequestArguments } from '@metamask/providers';
|
||||
|
||||
import { useMetaMaskContext } from './MetamaskContext';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export type Request = (params: RequestArguments) => Promise<unknown | null>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
ConnectButton,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
SendHelloButton,
|
||||
Card,
|
||||
} from '../components';
|
||||
import { defaultSnapOrigin, getSnapApiBaseUrl } from '../config';
|
||||
import { defaultSnapOrigin, getSnapApiBaseUrl, getSnapSiteUrl } from '../config';
|
||||
import {
|
||||
useMetaMask,
|
||||
useInvokeSnap,
|
||||
@@ -109,6 +109,9 @@ const MarketSummaryList = styled.div`
|
||||
margin-top: 0.8rem;
|
||||
`;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const Index = () => {
|
||||
const { error } = useMetaMaskContext();
|
||||
const { isFlask, snapsDetected, installedSnap } = useMetaMask();
|
||||
@@ -116,17 +119,52 @@ const Index = () => {
|
||||
const invokeSnap = useInvokeSnap();
|
||||
const apiBaseUrl = getSnapApiBaseUrl();
|
||||
const [marketSummary, setMarketSummary] = useState<{
|
||||
tokens: Array<{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
tokens: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
symbol?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
address?: string;
|
||||
market?: { priceUsd?: number; volume24h?: number };
|
||||
}>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
market?: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
priceUsd?: number; /**
|
||||
*
|
||||
*/
|
||||
volume24h?: number;
|
||||
};
|
||||
}[];
|
||||
/**
|
||||
*
|
||||
*/
|
||||
error?: string;
|
||||
} | null>(null);
|
||||
const [swapQuote, setSwapQuote] = useState<{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
amountOut?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
error?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
poolAddress?: string | null;
|
||||
} | null>(null);
|
||||
const [swapTokenIn, setSwapTokenIn] = useState('');
|
||||
@@ -139,12 +177,20 @@ const Index = () => {
|
||||
|
||||
const snapParams = apiBaseUrl ? { apiBaseUrl } : undefined;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const handleSendHelloClick = async () => {
|
||||
await invokeSnap(
|
||||
snapParams ? { method: 'hello', params: snapParams } : { method: 'hello' },
|
||||
snapParams
|
||||
? { method: 'hello', params: snapParams }
|
||||
: { method: 'hello' },
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const handleShowMarketData = async () => {
|
||||
if (!apiBaseUrl) {
|
||||
return;
|
||||
@@ -155,6 +201,9 @@ const Index = () => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const handleGetMarketSummary = async () => {
|
||||
if (!apiBaseUrl) {
|
||||
setMarketSummary({ tokens: [], error: 'Set GATSBY_SNAP_API_BASE_URL' });
|
||||
@@ -165,12 +214,38 @@ const Index = () => {
|
||||
method: 'get_market_summary',
|
||||
params: { apiBaseUrl },
|
||||
})) as {
|
||||
tokens?: Array<{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
tokens?: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
symbol?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
address?: string;
|
||||
market?: { priceUsd?: number; volume24h?: number };
|
||||
}>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
market?: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
priceUsd?: number; /**
|
||||
*
|
||||
*/
|
||||
volume24h?: number;
|
||||
};
|
||||
}[];
|
||||
/**
|
||||
*
|
||||
*/
|
||||
error?: string;
|
||||
};
|
||||
if (result?.error) {
|
||||
@@ -186,6 +261,9 @@ const Index = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const handleShowBridgeRoutes = async () => {
|
||||
if (!apiBaseUrl) {
|
||||
return;
|
||||
@@ -196,6 +274,23 @@ const Index = () => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Show dynamic info (networks + token list URL) in a Snap dialog.
|
||||
* Use the token list URL in MetaMask Settings → Token list to get tokens and icons.
|
||||
*/
|
||||
const handleShowDynamicInfo = async () => {
|
||||
if (!apiBaseUrl) {
|
||||
return;
|
||||
}
|
||||
await invokeSnap({
|
||||
method: 'show_dynamic_info',
|
||||
params: { apiBaseUrl },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const handleGetSwapQuote = async () => {
|
||||
if (!apiBaseUrl) {
|
||||
setSwapQuote({ error: 'Set GATSBY_SNAP_API_BASE_URL' });
|
||||
@@ -216,8 +311,17 @@ const Index = () => {
|
||||
amountIn: swapAmountIn.trim(),
|
||||
},
|
||||
})) as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
amountOut?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
error?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
poolAddress?: string | null;
|
||||
};
|
||||
setSwapQuote({
|
||||
@@ -232,6 +336,9 @@ const Index = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const handleShowSwapQuote = async () => {
|
||||
if (!apiBaseUrl || !swapTokenIn || !swapTokenOut || !swapAmountIn) {
|
||||
return;
|
||||
@@ -251,10 +358,11 @@ const Index = () => {
|
||||
return (
|
||||
<Container>
|
||||
<Heading>
|
||||
Welcome to <Span>template-snap</Span>
|
||||
Welcome to <Span>Chain 138 Snap</Span>
|
||||
</Heading>
|
||||
<Subtitle>
|
||||
Get started by editing <code>src/index.tsx</code>
|
||||
Add Chain 138 (DeFi Oracle Meta) to MetaMask and use market data,
|
||||
bridge, and swap.
|
||||
</Subtitle>
|
||||
<Notice>
|
||||
<p>
|
||||
@@ -263,6 +371,22 @@ const Index = () => {
|
||||
Bridge, and Swap cards below (set GATSBY_SNAP_API_BASE_URL for
|
||||
production API).
|
||||
</p>
|
||||
<p style={{ marginTop: '1rem', marginBottom: 0 }}>
|
||||
<strong>Chain 138 Send:</strong> If MetaMask’s in-wallet
|
||||
"Send" errors with "No XChain Swaps native asset
|
||||
found", use{' '}
|
||||
<a
|
||||
href={
|
||||
getSnapSiteUrl()
|
||||
? `${getSnapSiteUrl()}${(typeof process !== 'undefined' && process.env?.GATSBY_PATH_PREFIX) || ''}/send`
|
||||
: './send'
|
||||
}
|
||||
style={{ color: 'var(--color-primary-default, #6F4CFF)' }}
|
||||
>
|
||||
Send on Chain 138
|
||||
</a>{' '}
|
||||
instead.
|
||||
</p>
|
||||
</Notice>
|
||||
<CardContainer>
|
||||
{error && (
|
||||
@@ -291,6 +415,7 @@ const Index = () => {
|
||||
<ConnectButton
|
||||
onClick={requestSnap}
|
||||
disabled={!isMetaMaskReady}
|
||||
label={isFlask ? 'Connect MetaMask Flask' : 'Connect'}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
@@ -370,7 +495,7 @@ const Index = () => {
|
||||
content={{
|
||||
title: 'Bridge',
|
||||
description: apiBaseUrl
|
||||
? 'Show CCIP bridge routes (WETH9 / WETH10) in a Snap dialog. Use explorer for executing transfers.'
|
||||
? 'Show CCIP and Trustless bridge routes in a Snap dialog. Use explorer for executing transfers.'
|
||||
: 'Set GATSBY_SNAP_API_BASE_URL to show bridge routes.',
|
||||
button: (
|
||||
<Button
|
||||
@@ -383,6 +508,23 @@ const Index = () => {
|
||||
}}
|
||||
disabled={!installedSnap}
|
||||
/>
|
||||
<Card
|
||||
content={{
|
||||
title: 'Token list URL',
|
||||
description: apiBaseUrl
|
||||
? 'Show networks and token list URL. Add the URL in MetaMask Settings → Token list to display tokens and icons.'
|
||||
: 'Set GATSBY_SNAP_API_BASE_URL to get the token list URL.',
|
||||
button: (
|
||||
<Button
|
||||
onClick={handleShowDynamicInfo}
|
||||
disabled={!installedSnap || !apiBaseUrl}
|
||||
>
|
||||
Show dynamic info
|
||||
</Button>
|
||||
),
|
||||
}}
|
||||
disabled={!installedSnap}
|
||||
/>
|
||||
<Card
|
||||
fullWidth
|
||||
content={{
|
||||
@@ -412,7 +554,9 @@ const Index = () => {
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
|
||||
<div
|
||||
style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}
|
||||
>
|
||||
<Button
|
||||
onClick={handleGetSwapQuote}
|
||||
disabled={!installedSnap}
|
||||
@@ -482,10 +626,11 @@ const Index = () => {
|
||||
)}
|
||||
<Notice>
|
||||
<p>
|
||||
Please note that the <b>snap.manifest.json</b> and{' '}
|
||||
<b>package.json</b> must be located in the server root directory and
|
||||
the bundle must be hosted at the location specified by the location
|
||||
field.
|
||||
When hosting the Snap yourself (e.g. for development),{' '}
|
||||
<b>snap.manifest.json</b> and <b>package.json</b> must be at the
|
||||
host’s root path, and the bundle must be served at the path given in{' '}
|
||||
<b>source.location</b> in the manifest. When installing from npm
|
||||
(production), the published package already satisfies this layout.
|
||||
</p>
|
||||
</Notice>
|
||||
</CardContainer>
|
||||
|
||||
329
chain138-snap/packages/site/src/pages/send.tsx
Normal file
329
chain138-snap/packages/site/src/pages/send.tsx
Normal file
@@ -0,0 +1,329 @@
|
||||
/**
|
||||
* Send page — Chain 138 native send (bypasses MetaMask's broken Send UI).
|
||||
*
|
||||
* MetaMask's in-wallet "Send" can throw "No XChain Swaps native asset found for chainId: eip155:138"
|
||||
* because Chain 138 is not in their LavaPack list. This page sends via eth_sendTransaction from the
|
||||
* dApp context, which uses a different code path and works on Chain 138.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Button } from '../components';
|
||||
import { useMetaMaskContext } from '../hooks/MetamaskContext';
|
||||
|
||||
const CHAIN_ID_138_HEX = '0x8a';
|
||||
const CHAIN_138_PARAMS = {
|
||||
chainId: CHAIN_ID_138_HEX,
|
||||
chainName: 'DeFi Oracle Meta Mainnet',
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
rpcUrls: ['https://rpc-http-pub.d-bis.org'],
|
||||
blockExplorerUrls: ['https://explorer.d-bis.org'],
|
||||
};
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-width: 32rem;
|
||||
margin: 4rem auto;
|
||||
padding: 0 2.4rem;
|
||||
${({ theme }) => theme.mediaQueries?.small} {
|
||||
margin: 2rem auto;
|
||||
padding: 0 1.2rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const Title = styled.h1`
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: ${({ theme }) => theme.fontSizes?.title ?? '1.5rem'};
|
||||
`;
|
||||
|
||||
const Subtitle = styled.p`
|
||||
margin: 0 0 2rem;
|
||||
color: ${({ theme }) => theme.colors?.text?.alternative};
|
||||
font-size: ${({ theme }) => theme.fontSizes?.small};
|
||||
`;
|
||||
|
||||
const Form = styled.form`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.2rem;
|
||||
`;
|
||||
|
||||
const Label = styled.label`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
font-size: ${({ theme }) => theme.fontSizes?.small};
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
const Input = styled.input`
|
||||
padding: 0.8rem 1rem;
|
||||
border: 1px solid ${({ theme }) => theme.colors?.border?.default};
|
||||
border-radius: ${({ theme }) => theme.radii?.default ?? '6px'};
|
||||
font-size: ${({ theme }) => theme.fontSizes?.text};
|
||||
background: ${({ theme }) => theme.colors?.background?.default};
|
||||
color: ${({ theme }) => theme.colors?.text?.default};
|
||||
font-family: inherit;
|
||||
|
||||
&::placeholder {
|
||||
color: ${({ theme }) => theme.colors?.text?.muted};
|
||||
}
|
||||
`;
|
||||
|
||||
const Message = styled.div<{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
$error?: boolean;
|
||||
}>`
|
||||
padding: 1rem;
|
||||
border-radius: ${({ theme }) => theme.radii?.default ?? '6px'};
|
||||
font-size: ${({ theme }) => theme.fontSizes?.small};
|
||||
background: ${({ theme, $error }) =>
|
||||
$error
|
||||
? theme.colors?.error?.muted
|
||||
: theme.colors?.background?.alternative};
|
||||
color: ${({ theme, $error }) =>
|
||||
$error ? theme.colors?.error?.default : theme.colors?.text?.default};
|
||||
word-break: break-all;
|
||||
`;
|
||||
|
||||
const Row = styled.div`
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
flex: 1;
|
||||
min-width: 8rem;
|
||||
`;
|
||||
|
||||
const BackLink = styled.a`
|
||||
margin-top: 2rem;
|
||||
font-size: ${({ theme }) => theme.fontSizes?.small};
|
||||
color: ${({ theme }) => theme.colors?.primary?.default};
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param eth
|
||||
*/
|
||||
function ethToWeiHex(eth: string): string {
|
||||
const parsed = parseFloat(eth);
|
||||
if (Number.isNaN(parsed) || parsed < 0) {
|
||||
return '0x0';
|
||||
}
|
||||
const wei = BigInt(Math.floor(parsed * 1e18));
|
||||
return `0x${wei.toString(16)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param addr
|
||||
*/
|
||||
function isValidAddress(addr: string): boolean {
|
||||
return /^0x[a-fA-F0-9]{40}$/.test(addr.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default function SendPage() {
|
||||
const { provider } = useMetaMaskContext();
|
||||
const [to, setTo] = useState('');
|
||||
const [amount, setAmount] = useState('');
|
||||
const [message, setMessage] = useState<{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
text: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
error?: boolean;
|
||||
} | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const ensureChain138 = async (): Promise<boolean> => {
|
||||
if (!provider) {
|
||||
setMessage({
|
||||
text: 'MetaMask not detected. Install and connect MetaMask.',
|
||||
error: true,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
await provider.request({
|
||||
method: 'wallet_switchEthereumChain',
|
||||
params: [{ chainId: CHAIN_ID_138_HEX }],
|
||||
});
|
||||
return true;
|
||||
} catch (err: unknown) {
|
||||
const code = (
|
||||
err as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
code?: number;
|
||||
}
|
||||
)?.code;
|
||||
if (code === 4902) {
|
||||
try {
|
||||
await provider.request({
|
||||
method: 'wallet_addEthereumChain',
|
||||
params: [CHAIN_138_PARAMS],
|
||||
});
|
||||
return true;
|
||||
} catch (addErr) {
|
||||
setMessage({
|
||||
text: `Failed to add Chain 138: ${addErr instanceof Error ? addErr.message : String(addErr)}`,
|
||||
error: true,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
setMessage({
|
||||
text: `Failed to switch to Chain 138: ${err instanceof Error ? err.message : String(err)}`,
|
||||
error: true,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const handleSwitchChain = async () => {
|
||||
setMessage(null);
|
||||
setLoading(true);
|
||||
const ok = await ensureChain138();
|
||||
setLoading(false);
|
||||
if (ok) {
|
||||
setMessage({ text: 'Switched to Chain 138.' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
*/
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setMessage(null);
|
||||
if (!provider) {
|
||||
setMessage({ text: 'MetaMask not detected.', error: true });
|
||||
return;
|
||||
}
|
||||
const toAddress = to.trim();
|
||||
if (!isValidAddress(toAddress)) {
|
||||
setMessage({ text: 'Enter a valid 0x address.', error: true });
|
||||
return;
|
||||
}
|
||||
const amountNum = parseFloat(amount);
|
||||
if (Number.isNaN(amountNum) || amountNum <= 0) {
|
||||
setMessage({ text: 'Enter a valid amount (ETH).', error: true });
|
||||
return;
|
||||
}
|
||||
const ok = await ensureChain138();
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const accounts = (await provider.request({
|
||||
method: 'eth_requestAccounts',
|
||||
params: [],
|
||||
})) as string[];
|
||||
const from = accounts?.[0];
|
||||
if (!from) {
|
||||
setMessage({ text: 'No account selected in MetaMask.', error: true });
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const txHash = (await provider.request({
|
||||
method: 'eth_sendTransaction',
|
||||
params: [
|
||||
{
|
||||
from,
|
||||
to: toAddress,
|
||||
value: ethToWeiHex(amount),
|
||||
gasLimit: '0x5208', // 21000 for simple transfer
|
||||
},
|
||||
],
|
||||
})) as string;
|
||||
setMessage({
|
||||
text: `Sent. Tx: ${txHash}. Confirm in MetaMask and check the block explorer.`,
|
||||
});
|
||||
setTo('');
|
||||
setAmount('');
|
||||
} catch (err) {
|
||||
setMessage({
|
||||
text: err instanceof Error ? err.message : String(err),
|
||||
error: true,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Title>Send on Chain 138</Title>
|
||||
<Subtitle>
|
||||
Use this page to send ETH on Chain 138. It bypasses MetaMask’s in-wallet
|
||||
Send button, which errors on custom chains.
|
||||
</Subtitle>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Label>
|
||||
Recipient address
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="0x..."
|
||||
value={to}
|
||||
onChange={(e) => setTo(e.target.value)}
|
||||
disabled={loading}
|
||||
/>
|
||||
</Label>
|
||||
<Label>
|
||||
Amount (ETH)
|
||||
<Input
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
placeholder="0.1"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
disabled={loading}
|
||||
/>
|
||||
</Label>
|
||||
{message && <Message $error={message.error}>{message.text}</Message>}
|
||||
<Row>
|
||||
<StyledButton
|
||||
type="button"
|
||||
onClick={handleSwitchChain}
|
||||
disabled={loading || !provider}
|
||||
>
|
||||
{loading ? '…' : 'Switch to Chain 138'}
|
||||
</StyledButton>
|
||||
<StyledButton type="submit" disabled={loading || !provider}>
|
||||
{loading ? '…' : 'Send'}
|
||||
</StyledButton>
|
||||
</Row>
|
||||
</Form>
|
||||
<BackLink href="./">← Back to Chain 138 Snap</BackLink>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -8,18 +8,40 @@ import type {
|
||||
* Window type extension to support ethereum
|
||||
*/
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
/**
|
||||
*
|
||||
*/
|
||||
interface Window {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
ethereum: MetaMaskInpageProvider & {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
setProvider?: (provider: MetaMaskInpageProvider) => void;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
detected?: MetaMaskInpageProvider[];
|
||||
/**
|
||||
*
|
||||
*/
|
||||
providers?: MetaMaskInpageProvider[];
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
/**
|
||||
*
|
||||
*/
|
||||
interface WindowEventMap {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
'eip6963:requestProvider': EIP6963RequestProviderEvent;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
'eip6963:announceProvider': EIP6963AnnounceProviderEvent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export type GetSnapsResponse = Record<string, Snap>;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export type Snap = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
permissionName: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
initialPermissions: Record<string, unknown>;
|
||||
};
|
||||
|
||||
@@ -7,13 +7,37 @@ import 'styled-components';
|
||||
*/
|
||||
declare module 'styled-components' {
|
||||
/* eslint-disable @typescript-eslint/consistent-type-definitions */
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface DefaultTheme {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
fonts: Record<string, string>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
fontSizes: Record<string, string>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
breakpoints: string[];
|
||||
/**
|
||||
*
|
||||
*/
|
||||
mediaQueries: Record<string, string>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
radii: Record<string, string>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
shadows: Record<string, string>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
colors: Record<string, Record<string, string>>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { isLocalSnap } from './snap';
|
||||
import type { Snap } from '../types';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param installedSnap
|
||||
*/
|
||||
export const shouldDisplayReconnectButton = (installedSnap: Snap | null) =>
|
||||
installedSnap && isLocalSnap(installedSnap?.id);
|
||||
|
||||
@@ -79,9 +79,31 @@ export async function getMetaMaskEIP6963Provider() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a provider reports as MetaMask Flask via web3_clientVersion.
|
||||
*
|
||||
* @param provider
|
||||
*/
|
||||
async function isFlaskProvider(
|
||||
provider: MetaMaskInpageProvider,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const clientVersion = (await provider.request({
|
||||
method: 'web3_clientVersion',
|
||||
})) as string | string[] | undefined;
|
||||
const versionStr = Array.isArray(clientVersion)
|
||||
? clientVersion.join(' ')
|
||||
: String(clientVersion ?? '');
|
||||
return versionStr.toLowerCase().includes('flask');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a provider that supports snaps. This will loop through all the detected
|
||||
* providers and return the first one that supports snaps.
|
||||
* providers and return the first one that supports snaps. Prefers Flask when
|
||||
* multiple snaps-capable providers exist so the UI can show "Connect MetaMask Flask".
|
||||
*
|
||||
* @returns The provider, or `null` if no provider supports snaps.
|
||||
*/
|
||||
@@ -103,11 +125,20 @@ export async function getSnapsProvider() {
|
||||
}
|
||||
|
||||
if (window.ethereum?.providers) {
|
||||
const snapsProviders: MetaMaskInpageProvider[] = [];
|
||||
for (const provider of window.ethereum.providers) {
|
||||
if (await hasSnapsSupport(provider)) {
|
||||
return provider;
|
||||
snapsProviders.push(provider);
|
||||
}
|
||||
}
|
||||
if (snapsProviders.length > 0) {
|
||||
for (const provider of snapsProviders) {
|
||||
if (await isFlaskProvider(provider)) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
return snapsProviders[0];
|
||||
}
|
||||
}
|
||||
|
||||
const eip6963Provider = await getMetaMaskEIP6963Provider();
|
||||
@@ -116,5 +147,23 @@ export async function getSnapsProvider() {
|
||||
return eip6963Provider;
|
||||
}
|
||||
|
||||
// Fallback: if window.ethereum reports Flask, use it (Flask supports snaps;
|
||||
// hasSnapsSupport may have failed e.g. if wallet was locked).
|
||||
if (window.ethereum) {
|
||||
try {
|
||||
const clientVersion = (await window.ethereum.request({
|
||||
method: 'web3_clientVersion',
|
||||
})) as string | string[] | undefined;
|
||||
const versionStr = Array.isArray(clientVersion)
|
||||
? clientVersion.join(' ')
|
||||
: String(clientVersion ?? '');
|
||||
if (versionStr.toLowerCase().includes('flask')) {
|
||||
return window.ethereum as MetaMaskInpageProvider;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":"dev","buildTime":"2026-02-16T06:07:18.288Z"}
|
||||
{"version":"dev","buildTime":"2026-02-22T19:21:30.615Z"}
|
||||
@@ -1,6 +1,6 @@
|
||||
# chain138-snap
|
||||
|
||||
**Chain 138 Snap** adds [DeFi Oracle Meta Mainnet](https://chainlist.org/chain/138) (ChainID 138) and **ALL Mainnet** (651940) support inside MetaMask: network params, token list, market data, swap quotes, and CCIP bridge routes.
|
||||
**Chain 138 Snap** adds [DeFi Oracle Meta Mainnet](https://chainlist.org/chain/138) (ChainID 138) and **ALL Mainnet** (651940) support inside MetaMask: network params, token list, market data, swap quotes, and bridge routes (CCIP and Trustless).
|
||||
|
||||
MetaMask already supports Chain 138 as a custom EVM network, but native **Swaps**, **Portfolio Bridge**, and **USD pricing** do not include Chain 138. This Snap provides in-wallet swap quotes, bridge routes, and market data by calling your token-aggregation (or compatible) API.
|
||||
|
||||
@@ -41,18 +41,18 @@ For **market data**, **swap quotes**, and **bridge routes**, the dApp must pass
|
||||
|
||||
### RPC methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `hello` | Basic test; returns a greeting. |
|
||||
| `get_networks` | Full EIP-3085 chain params (Chain 138, Ethereum, ALL Mainnet). |
|
||||
| `get_chain138_config` | Chain 138 config from API. |
|
||||
| `get_chain138_market_chains` | Market chains list. |
|
||||
| `get_token_list` / `get_token_list_url` | Token list (optional `chainId`). |
|
||||
| `get_oracles` | Oracles config. |
|
||||
| `show_dynamic_info` | In-Snap dialog with networks and token list URL. |
|
||||
| `get_market_summary` / `show_market_data` | Tokens and USD prices. |
|
||||
| `get_bridge_routes` / `show_bridge_routes` | CCIP bridge routes. |
|
||||
| `get_swap_quote` / `show_swap_quote` | Swap quote (requires `tokenIn`, `tokenOut`, `amountIn`). |
|
||||
| Method | Description |
|
||||
| ------------------------------------------ | -------------------------------------------------------------- |
|
||||
| `hello` | Basic test; returns a greeting. |
|
||||
| `get_networks` | Full EIP-3085 chain params (Chain 138, Ethereum, ALL Mainnet). |
|
||||
| `get_chain138_config` | Chain 138 config from API. |
|
||||
| `get_chain138_market_chains` | Market chains list. |
|
||||
| `get_token_list` / `get_token_list_url` | Token list (optional `chainId`). |
|
||||
| `get_oracles` | Oracles config. |
|
||||
| `show_dynamic_info` | In-Snap dialog with networks and token list URL. |
|
||||
| `get_market_summary` / `show_market_data` | Tokens and USD prices. |
|
||||
| `get_bridge_routes` / `show_bridge_routes` | CCIP and Trustless bridge routes. |
|
||||
| `get_swap_quote` / `show_swap_quote` | Swap quote (requires `tokenIn`, `tokenOut`, `amountIn`). |
|
||||
|
||||
## Repository and docs
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "chain138-snap",
|
||||
"version": "0.1.1",
|
||||
"description": "Chain 138 (DeFi Oracle Meta Mainnet) and ALL Mainnet Snap: networks, token list, market data, swap quotes, CCIP bridge routes for MetaMask.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bis-innovations/chain138-snap.git"
|
||||
},
|
||||
"version": "0.1.2",
|
||||
"description": "Chain 138 (DeFi Oracle Meta Mainnet) and ALL Mainnet Snap: networks, token list, market data, swap quotes, CCIP and Trustless bridge routes for MetaMask.",
|
||||
"homepage": "https://github.com/bis-innovations/chain138-snap#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/bis-innovations/chain138-snap/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bis-innovations/chain138-snap.git"
|
||||
},
|
||||
"license": "(MIT-0 OR Apache-2.0)",
|
||||
"main": "./dist/bundle.js",
|
||||
"files": [
|
||||
@@ -39,6 +39,7 @@
|
||||
"@metamask/providers": "^22.1.0",
|
||||
"@metamask/snaps-cli": "^8.3.0",
|
||||
"@metamask/snaps-jest": "^9.8.0",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/react": "18.2.4",
|
||||
"@types/react-dom": "18.2.4",
|
||||
"eslint": "^9.39.2",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"version": "0.1.1",
|
||||
"description": "Chain 138 (DeFi Oracle Meta Mainnet) and ALL Mainnet: networks, token list, market data, swap quotes, and CCIP bridge routes for MetaMask.",
|
||||
"version": "0.1.2",
|
||||
"description": "Chain 138 (DeFi Oracle Meta Mainnet) and ALL Mainnet: networks, token list, market data, swap quotes, and CCIP and Trustless bridge routes for MetaMask.",
|
||||
"proposedName": "Chain 138",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bis-innovations/chain138-snap.git"
|
||||
},
|
||||
"source": {
|
||||
"shasum": "6CuMlWe0q/GCAHp8l6U+niT/Um5DHEYex4GPhbs5bkg=",
|
||||
"shasum": "8GYAFlgbiR/jXAwnprqqE4jTIvQv/Uhkn3MiH23g/tQ=",
|
||||
"location": {
|
||||
"npm": {
|
||||
"filePath": "dist/bundle.js",
|
||||
|
||||
@@ -3,9 +3,21 @@ import type { SnapConfirmationInterface } from '@metamask/snaps-jest';
|
||||
import { installSnap } from '@metamask/snaps-jest';
|
||||
import { Box, Text, Bold } from '@metamask/snaps-sdk/jsx';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
type SnapsJestExpect = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
toRender: (expected: unknown) => void;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
toRespondWith: (expected: unknown) => void;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
toRespondWithError: (expected: unknown) => void;
|
||||
};
|
||||
|
||||
@@ -36,7 +48,9 @@ describe('onRpcRequest', () => {
|
||||
|
||||
await ui.ok();
|
||||
|
||||
(expect(await response) as unknown as SnapsJestExpect).toRespondWith(true);
|
||||
(expect(await response) as unknown as SnapsJestExpect).toRespondWith(
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ const DEFAULT_MARKET_API_BASE = '';
|
||||
|
||||
/** RPC params: apiBaseUrl and optional override URLs for networks, token list, bridge list */
|
||||
export type SnapRpcParams = {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
apiBaseUrl?: string;
|
||||
/** When set, get_networks / get_chain138_config fetch this URL instead of apiBaseUrl */
|
||||
networksUrl?: string;
|
||||
@@ -13,6 +16,15 @@ export type SnapRpcParams = {
|
||||
tokenListUrl?: string;
|
||||
/** When set, get_bridge_routes / show_bridge_routes fetch this URL instead of apiBaseUrl */
|
||||
bridgeListUrl?: string;
|
||||
/** For get_token_mapping: source chain ID (e.g. 138) */
|
||||
fromChain?: number;
|
||||
/** For get_token_mapping: destination chain ID (e.g. 651940) */
|
||||
toChain?: number;
|
||||
/** For get_token_mapping (resolve): token address on source chain to resolve to destination */
|
||||
address?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
chainId?: number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
@@ -23,7 +35,16 @@ export type SnapRpcParams = {
|
||||
* @param params - Request params with optional apiBaseUrl.
|
||||
* @returns API base URL string.
|
||||
*/
|
||||
function getApiBase(params: { apiBaseUrl?: string } | undefined): string {
|
||||
function getApiBase(
|
||||
params:
|
||||
| {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
apiBaseUrl?: string;
|
||||
}
|
||||
| undefined,
|
||||
): string {
|
||||
return (params?.apiBaseUrl ?? DEFAULT_MARKET_API_BASE).replace(/\/$/u, '');
|
||||
}
|
||||
|
||||
@@ -39,16 +60,65 @@ async function fetchNetworks(apiBase: string) {
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
return res.json() as Promise<{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
networks?: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
chainId: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
chainIdDecimal: number;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
chainName: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
rpcUrls: string[];
|
||||
nativeCurrency: { name: string; symbol: string; decimals: number };
|
||||
/**
|
||||
*
|
||||
*/
|
||||
nativeCurrency: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
name: string; /**
|
||||
*
|
||||
*/
|
||||
symbol: string; /**
|
||||
*
|
||||
*/
|
||||
decimals: number;
|
||||
};
|
||||
/**
|
||||
*
|
||||
*/
|
||||
blockExplorerUrls: string[];
|
||||
/**
|
||||
*
|
||||
*/
|
||||
iconUrls?: string[];
|
||||
oracles?: { name: string; address: string }[];
|
||||
/**
|
||||
*
|
||||
*/
|
||||
oracles?: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
name: string; /**
|
||||
*
|
||||
*/
|
||||
address: string;
|
||||
}[];
|
||||
}[];
|
||||
}>;
|
||||
}
|
||||
@@ -76,12 +146,26 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
if (networksUrl) {
|
||||
try {
|
||||
const res = await fetch(networksUrl);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const data = (await res.json()) as { version?: string; networks?: unknown[] };
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
const data = (await res.json()) as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
networks?: unknown[];
|
||||
};
|
||||
return { version: data.version, networks: data.networks ?? [] };
|
||||
} catch (error) {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch networks URL',
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Failed to fetch networks URL',
|
||||
version: undefined,
|
||||
networks: [],
|
||||
};
|
||||
@@ -89,8 +173,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
}
|
||||
if (!base) {
|
||||
return {
|
||||
error:
|
||||
'Pass apiBaseUrl or networksUrl to fetch networks',
|
||||
error: 'Pass apiBaseUrl or networksUrl to fetch networks',
|
||||
version: undefined,
|
||||
networks: [],
|
||||
};
|
||||
@@ -109,11 +192,26 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
}
|
||||
|
||||
case 'get_chain138_config': {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const loadNetworks = async () => {
|
||||
if (networksUrl) {
|
||||
const res = await fetch(networksUrl);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const data = (await res.json()) as { networks?: { chainIdDecimal?: number }[] };
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
const data = (await res.json()) as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
networks?: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
chainIdDecimal?: number;
|
||||
}[];
|
||||
};
|
||||
return data.networks ?? [];
|
||||
}
|
||||
if (base) {
|
||||
@@ -125,20 +223,71 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
try {
|
||||
const networks = await loadNetworks();
|
||||
if (networks === null) {
|
||||
return { error: 'Pass apiBaseUrl or networksUrl to fetch chain config' };
|
||||
return {
|
||||
error: 'Pass apiBaseUrl or networksUrl to fetch chain config',
|
||||
};
|
||||
}
|
||||
const chain138 = networks.find((net: { chainIdDecimal?: number }) => net.chainIdDecimal === 138);
|
||||
const chain138 = networks.find(
|
||||
(net: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
chainIdDecimal?: number;
|
||||
}) => net.chainIdDecimal === 138,
|
||||
);
|
||||
if (!chain138) {
|
||||
return { error: 'Chain 138 not found in networks response' };
|
||||
}
|
||||
return {
|
||||
chainId: (chain138 as { chainId?: string }).chainId,
|
||||
chainId: (
|
||||
chain138 as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
chainId?: string;
|
||||
}
|
||||
).chainId,
|
||||
chainIdDecimal: chain138.chainIdDecimal,
|
||||
chainName: (chain138 as { chainName?: string }).chainName,
|
||||
rpcUrls: (chain138 as { rpcUrls?: string[] }).rpcUrls,
|
||||
nativeCurrency: (chain138 as { nativeCurrency?: unknown }).nativeCurrency,
|
||||
blockExplorerUrls: (chain138 as { blockExplorerUrls?: string[] }).blockExplorerUrls,
|
||||
oracles: (chain138 as { oracles?: unknown }).oracles,
|
||||
chainName: (
|
||||
chain138 as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
chainName?: string;
|
||||
}
|
||||
).chainName,
|
||||
rpcUrls: (
|
||||
chain138 as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
rpcUrls?: string[];
|
||||
}
|
||||
).rpcUrls,
|
||||
nativeCurrency: (
|
||||
chain138 as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
nativeCurrency?: unknown;
|
||||
}
|
||||
).nativeCurrency,
|
||||
blockExplorerUrls: (
|
||||
chain138 as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
blockExplorerUrls?: string[];
|
||||
}
|
||||
).blockExplorerUrls,
|
||||
oracles: (
|
||||
chain138 as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
oracles?: unknown;
|
||||
}
|
||||
).oracles,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
@@ -197,18 +346,23 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
|
||||
case 'get_token_list': {
|
||||
if (tokenListUrl) {
|
||||
const chainIdParam = params?.chainId as number | undefined;
|
||||
const chainIdParam = params?.chainId;
|
||||
const url = chainIdParam
|
||||
? `${tokenListUrl}${tokenListUrl.includes('?') ? '&' : '?'}chainId=${chainIdParam}`
|
||||
: tokenListUrl;
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
const data = await res.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch token list URL',
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Failed to fetch token list URL',
|
||||
tokens: [],
|
||||
};
|
||||
}
|
||||
@@ -219,7 +373,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
tokens: [],
|
||||
};
|
||||
}
|
||||
const chainIdParam = params?.chainId as number | undefined;
|
||||
const chainIdParam = params?.chainId;
|
||||
const url = chainIdParam
|
||||
? `${base}/api/v1/report/token-list?chainId=${chainIdParam}`
|
||||
: `${base}/api/v1/report/token-list`;
|
||||
@@ -245,7 +399,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
if (!base) {
|
||||
return { error: 'Pass apiBaseUrl to fetch oracles config', chains: [] };
|
||||
}
|
||||
const chainIdParam = params?.chainId as number | undefined;
|
||||
const chainIdParam = params?.chainId;
|
||||
const configUrl = chainIdParam
|
||||
? `${base}/api/v1/config?chainId=${chainIdParam}`
|
||||
: `${base}/api/v1/config`;
|
||||
@@ -274,7 +428,8 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
content: (
|
||||
<Box>
|
||||
<Text>
|
||||
Pass apiBaseUrl, networksUrl, or tokenListUrl to see dynamic networks and token list URL.
|
||||
Pass apiBaseUrl, networksUrl, or tokenListUrl to see dynamic
|
||||
networks and token list URL.
|
||||
</Text>
|
||||
</Box>
|
||||
),
|
||||
@@ -286,16 +441,36 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
if (networksUrl) {
|
||||
const res = await fetch(networksUrl);
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as { networks?: { chainName?: string; chainIdDecimal?: number }[] };
|
||||
const data = (await res.json()) as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
networks?: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
chainName?: string; /**
|
||||
*
|
||||
*/
|
||||
chainIdDecimal?: number;
|
||||
}[];
|
||||
};
|
||||
const nets = data.networks ?? [];
|
||||
chainNames = nets.map((n) => `${n.chainName ?? ''} (${n.chainIdDecimal ?? ''})`).join(', ') || 'None';
|
||||
chainNames =
|
||||
nets
|
||||
.map((n) => `${n.chainName ?? ''} (${n.chainIdDecimal ?? ''})`)
|
||||
.join(', ') || 'None';
|
||||
}
|
||||
} else if (base) {
|
||||
const data = await fetchNetworks(base);
|
||||
const networks = data.networks ?? [];
|
||||
chainNames = networks.map((net) => `${net.chainName} (${net.chainIdDecimal})`).join(', ') || 'None';
|
||||
chainNames =
|
||||
networks
|
||||
.map((net) => `${net.chainName} (${net.chainIdDecimal})`)
|
||||
.join(', ') || 'None';
|
||||
}
|
||||
const displayTokenListUrl = tokenListUrl || (base ? `${base}/api/v1/report/token-list` : '');
|
||||
const displayTokenListUrl =
|
||||
tokenListUrl || (base ? `${base}/api/v1/report/token-list` : '');
|
||||
return snap.request({
|
||||
method: 'snap_dialog',
|
||||
params: {
|
||||
@@ -344,7 +519,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
tokens: [],
|
||||
};
|
||||
}
|
||||
const chainIdParam = (params?.chainId as number | undefined) ?? 138;
|
||||
const chainIdParam = params?.chainId ?? 138;
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${base}/api/v1/tokens?chainId=${chainIdParam}&limit=50`,
|
||||
@@ -353,12 +528,35 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
const data = (await res.json()) as {
|
||||
tokens?: Array<{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
tokens?: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
symbol?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
address?: string;
|
||||
market?: { priceUsd?: number; volume24h?: number };
|
||||
}>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
market?: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
priceUsd?: number; /**
|
||||
*
|
||||
*/
|
||||
volume24h?: number;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
const tokens = (data.tokens ?? []).map((t) => ({
|
||||
symbol: t.symbol,
|
||||
@@ -388,7 +586,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
amountOut: undefined,
|
||||
};
|
||||
}
|
||||
const chainIdParam = (params?.chainId as number | undefined) ?? 138;
|
||||
const chainIdParam = params?.chainId ?? 138;
|
||||
const tokenIn = params?.tokenIn as string | undefined;
|
||||
const tokenOut = params?.tokenOut as string | undefined;
|
||||
const amountIn = params?.amountIn as string | undefined;
|
||||
@@ -406,8 +604,17 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
url.searchParams.set('amountIn', String(amountIn));
|
||||
const res = await fetch(url.toString());
|
||||
const data = (await res.json()) as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
amountOut?: string | null;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
error?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
poolAddress?: string | null;
|
||||
};
|
||||
if (!res.ok) {
|
||||
@@ -449,7 +656,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
},
|
||||
});
|
||||
}
|
||||
const chainIdParam = (params?.chainId as number | undefined) ?? 138;
|
||||
const chainIdParam = params?.chainId ?? 138;
|
||||
const tokenIn = params?.tokenIn as string | undefined;
|
||||
const tokenOut = params?.tokenOut as string | undefined;
|
||||
const amountIn = params?.amountIn as string | undefined;
|
||||
@@ -477,7 +684,13 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
url.searchParams.set('amountIn', String(amountIn));
|
||||
const res = await fetch(url.toString());
|
||||
const data = (await res.json()) as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
amountOut?: string | null;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
error?: string;
|
||||
};
|
||||
if (!res.ok || data.error) {
|
||||
@@ -533,7 +746,8 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
}
|
||||
|
||||
case 'get_bridge_routes': {
|
||||
const bridgeUrl = bridgeListUrl || (base ? `${base}/api/v1/bridge/routes` : '');
|
||||
const bridgeUrl =
|
||||
bridgeListUrl || (base ? `${base}/api/v1/bridge/routes` : '');
|
||||
if (!bridgeUrl) {
|
||||
return {
|
||||
error: 'Pass apiBaseUrl or bridgeListUrl to fetch bridge routes',
|
||||
@@ -543,9 +757,17 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
}
|
||||
try {
|
||||
const res = await fetch(bridgeUrl);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
const data = (await res.json()) as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
routes?: Record<string, Record<string, string>>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
chain138Bridges?: Record<string, string>;
|
||||
};
|
||||
return {
|
||||
@@ -565,7 +787,8 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
}
|
||||
|
||||
case 'show_bridge_routes': {
|
||||
const showBridgeUrl = bridgeListUrl || (base ? `${base}/api/v1/bridge/routes` : '');
|
||||
const showBridgeUrl =
|
||||
bridgeListUrl || (base ? `${base}/api/v1/bridge/routes` : '');
|
||||
if (!showBridgeUrl) {
|
||||
return snap.request({
|
||||
method: 'snap_dialog',
|
||||
@@ -574,7 +797,8 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
content: (
|
||||
<Box>
|
||||
<Text>
|
||||
Pass apiBaseUrl or bridgeListUrl to see bridge routes (CCIP WETH9 / WETH10).
|
||||
Pass apiBaseUrl or bridgeListUrl to see bridge routes (CCIP
|
||||
and Trustless).
|
||||
</Text>
|
||||
</Box>
|
||||
),
|
||||
@@ -587,25 +811,50 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
const data = (await res.json()) as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
routes?: Record<string, Record<string, string>>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
chain138Bridges?: Record<string, string>;
|
||||
};
|
||||
const lines: string[] = [];
|
||||
if (data.chain138Bridges) {
|
||||
// CCIP (WETH9 / WETH10)
|
||||
if (data.chain138Bridges?.weth9 || data.chain138Bridges?.weth10) {
|
||||
lines.push('CCIP');
|
||||
if (data.chain138Bridges.weth9) {
|
||||
lines.push(
|
||||
` WETH9 (138): ${String(data.chain138Bridges.weth9).slice(0, 10)}...`,
|
||||
);
|
||||
}
|
||||
if (data.chain138Bridges.weth10) {
|
||||
lines.push(
|
||||
` WETH10 (138): ${String(data.chain138Bridges.weth10).slice(0, 10)}...`,
|
||||
);
|
||||
}
|
||||
if (data.routes?.weth9?.['Ethereum Mainnet (1)']) {
|
||||
lines.push(' WETH9 → Ethereum Mainnet');
|
||||
}
|
||||
if (data.routes?.weth10?.['Ethereum Mainnet (1)']) {
|
||||
lines.push(' WETH10 → Ethereum Mainnet');
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
// Trustless bridge (Lockbox on 138)
|
||||
if (data.chain138Bridges?.trustless) {
|
||||
lines.push('Trustless');
|
||||
lines.push(
|
||||
`WETH9 Bridge (138): ${String(data.chain138Bridges.weth9 ?? '').slice(0, 10)}...`,
|
||||
);
|
||||
lines.push(
|
||||
`WETH10 Bridge (138): ${String(data.chain138Bridges.weth10 ?? '').slice(0, 10)}...`,
|
||||
` Lockbox (138): ${String(data.chain138Bridges.trustless).slice(0, 10)}...`,
|
||||
);
|
||||
if (data.routes?.trustless?.['Ethereum Mainnet (1)']) {
|
||||
lines.push(' Trustless → Ethereum Mainnet');
|
||||
} else {
|
||||
lines.push(' → Ethereum Mainnet (use explorer to transfer)');
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
if (data.routes?.weth9?.['Ethereum Mainnet (1)']) {
|
||||
lines.push('WETH9 → Ethereum Mainnet');
|
||||
}
|
||||
if (data.routes?.weth10?.['Ethereum Mainnet (1)']) {
|
||||
lines.push('WETH10 → Ethereum Mainnet');
|
||||
}
|
||||
lines.push('');
|
||||
lines.push('Use the explorer to execute bridge transfers.');
|
||||
return snap.request({
|
||||
method: 'snap_dialog',
|
||||
@@ -614,7 +863,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
content: (
|
||||
<Box>
|
||||
<Text>
|
||||
<Bold>CCIP Bridge Routes</Bold>
|
||||
<Bold>Bridge Routes (CCIP + Trustless)</Bold>
|
||||
</Text>
|
||||
<Text>{lines.join('\n')}</Text>
|
||||
</Box>
|
||||
@@ -639,6 +888,92 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
}
|
||||
}
|
||||
|
||||
case 'get_token_mapping': {
|
||||
const fromChain = params?.fromChain ?? 138;
|
||||
const toChain = params?.toChain ?? 651940;
|
||||
const address = params?.address?.trim();
|
||||
if (!base) {
|
||||
return {
|
||||
error:
|
||||
'Pass apiBaseUrl to use token-mapping API (cross-chain address resolution).',
|
||||
mapping: undefined,
|
||||
resolvedAddress: undefined,
|
||||
};
|
||||
}
|
||||
try {
|
||||
if (address) {
|
||||
const res = await fetch(
|
||||
`${base}/api/v1/token-mapping/resolve?fromChain=${fromChain}&toChain=${toChain}&address=${encodeURIComponent(address)}`,
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
const data = (await res.json()) as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
addressOnTarget?: string | null;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
addressOnSource?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
error?: string;
|
||||
};
|
||||
return {
|
||||
resolvedAddress: data.addressOnTarget ?? undefined,
|
||||
error: data.error,
|
||||
};
|
||||
}
|
||||
const res = await fetch(
|
||||
`${base}/api/v1/token-mapping?fromChain=${fromChain}&toChain=${toChain}`,
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
const data = (await res.json()) as {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
tokens?: unknown[];
|
||||
/**
|
||||
*
|
||||
*/
|
||||
addressMapFromTo?: Record<string, string>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
addressMapToFrom?: Record<string, string>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
error?: string;
|
||||
};
|
||||
if (data.error) {
|
||||
return { error: data.error, mapping: undefined };
|
||||
}
|
||||
return {
|
||||
mapping: {
|
||||
tokens: data.tokens,
|
||||
addressMapFromTo: data.addressMapFromTo,
|
||||
addressMapToFrom: data.addressMapToFrom,
|
||||
},
|
||||
error: undefined,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Failed to fetch token mapping',
|
||||
mapping: undefined,
|
||||
resolvedAddress: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
case 'show_market_data': {
|
||||
if (!base) {
|
||||
return snap.request({
|
||||
@@ -655,7 +990,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
},
|
||||
});
|
||||
}
|
||||
const chainIdParam = (params?.chainId as number | undefined) ?? 138;
|
||||
const chainIdParam = params?.chainId ?? 138;
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${base}/api/v1/tokens?chainId=${chainIdParam}&limit=20`,
|
||||
@@ -664,11 +999,28 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
const data = (await res.json()) as {
|
||||
tokens?: Array<{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
tokens?: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
symbol?: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
name?: string;
|
||||
market?: { priceUsd?: number };
|
||||
}>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
market?: {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
priceUsd?: number;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
const tokens = data.tokens ?? [];
|
||||
const lines =
|
||||
|
||||
@@ -8,7 +8,7 @@ import { defineConfig, devices } from '@playwright/test';
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
forbidOnly: Boolean(process.env.CI),
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: 1,
|
||||
reporter: 'html',
|
||||
|
||||
21
chain138-snap/scripts/build-snap-site-for-explorer.sh
Normal file
21
chain138-snap/scripts/build-snap-site-for-explorer.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the Snap companion site for https://explorer.d-bis.org/snap/
|
||||
# Uses GATSBY_SNAP_API_BASE_URL=https://explorer.d-bis.org so Market data, Bridge, Swap cards work.
|
||||
# For that to work, explorer.d-bis.org must serve the token-aggregation API at /api/v1/... (deploy
|
||||
# smom-dbis-138/services/token-aggregation and proxy it, or set GATSBY_SNAP_API_BASE_URL to the API host).
|
||||
# Output: packages/site/public/ (upload to /var/www/html/snap/ on VMID 5000).
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
export GATSBY_PATH_PREFIX=/snap
|
||||
export GATSBY_SNAP_API_BASE_URL="${GATSBY_SNAP_API_BASE_URL:-https://explorer.d-bis.org}"
|
||||
# So "Send on Chain 138" link is absolute HTTPS (avoids redirect to http and mixed-content).
|
||||
export GATSBY_SNAP_SITE_URL="${GATSBY_SNAP_SITE_URL:-https://explorer.d-bis.org}"
|
||||
# Production: use npm snap so MetaMask does not try to fetch localhost:8080 (GATSBY_* is inlined into client bundle).
|
||||
export GATSBY_SNAP_ORIGIN="${GATSBY_SNAP_ORIGIN:-npm:chain138-snap}"
|
||||
# Required for Gatsby to apply pathPrefix to script/asset URLs (see path prefix docs).
|
||||
export PREFIX_PATHS=1
|
||||
echo "Building Snap site: GATSBY_PATH_PREFIX=$GATSBY_PATH_PREFIX GATSBY_SNAP_SITE_URL=$GATSBY_SNAP_SITE_URL PREFIX_PATHS=$PREFIX_PATHS GATSBY_SNAP_ORIGIN=$GATSBY_SNAP_ORIGIN GATSBY_SNAP_API_BASE_URL=$GATSBY_SNAP_API_BASE_URL"
|
||||
pnpm --filter site run build
|
||||
echo "Done. Output in packages/site/public/ — deploy to /var/www/html/snap/ on explorer VM (VMID 5000)."
|
||||
@@ -1,144 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Deploy Chain 138 Snap companion site to VMID 5000 (explorer host).
|
||||
# Serves the site at https://explorer.d-bis.org/snap/
|
||||
# Requires: built site (run with --build to build first), Proxmox host with pct or SSH.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
VMID=5000
|
||||
VM_IP="${EXPLORER_VM_IP:-192.168.11.140}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SNAP_REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
SITE_PUBLIC="${SNAP_REPO_ROOT}/packages/site/public"
|
||||
PROXMOX_HOST="${PROXMOX_HOST_R630_02:-192.168.11.12}"
|
||||
|
||||
BUILD_FIRST=false
|
||||
for arg in "$@"; do
|
||||
[ "$arg" = "--build" ] && BUILD_FIRST=true
|
||||
done
|
||||
|
||||
echo "=========================================="
|
||||
echo "Deploy Chain 138 Snap site to VMID $VMID"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
if [ "$BUILD_FIRST" = true ]; then
|
||||
echo "=== Building site (pathPrefix=/snap) ==="
|
||||
BUILD_ENV="GATSBY_PATH_PREFIX=/snap GATSBY_BUILD_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')"
|
||||
[ -n "${GATSBY_SNAP_API_BASE_URL:-}" ] && BUILD_ENV="$BUILD_ENV GATSBY_SNAP_API_BASE_URL=$GATSBY_SNAP_API_BASE_URL"
|
||||
(cd "$SNAP_REPO_ROOT" && eval "$BUILD_ENV" pnpm --filter site run build)
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ ! -f "${SITE_PUBLIC}/index.html" ]; then
|
||||
echo "❌ Site not built. Run from repo root: GATSBY_PATH_PREFIX=/snap pnpm --filter site run build"
|
||||
echo " Or run this script with: $0 --build"
|
||||
echo " For production API (market/bridge/swap): GATSBY_SNAP_API_BASE_URL=https://your-api.com $0 --build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Detect run context
|
||||
if [ -f "/proc/1/cgroup" ] && grep -q "lxc" /proc/1/cgroup 2>/dev/null; then
|
||||
echo "Running inside VMID $VMID"
|
||||
DEPLOY_METHOD="direct"
|
||||
run_in_vm() { "$@"; }
|
||||
elif command -v pct &>/dev/null; then
|
||||
echo "Running from Proxmox host (pct exec $VMID)"
|
||||
DEPLOY_METHOD="pct"
|
||||
run_in_vm() { pct exec $VMID -- "$@"; }
|
||||
else
|
||||
echo "Running from remote (SSH to $PROXMOX_HOST, then pct to $VMID)"
|
||||
DEPLOY_METHOD="remote"
|
||||
run_in_vm() { ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@"${PROXMOX_HOST}" "pct exec $VMID -- $*"; }
|
||||
fi
|
||||
|
||||
echo "=== Creating tarball of site ==="
|
||||
TARBALL="/tmp/snap-site-deploy-$$.tar"
|
||||
(cd "$SITE_PUBLIC" && tar -cf "$TARBALL" .)
|
||||
cleanup_tarball() { rm -f "$TARBALL"; }
|
||||
trap cleanup_tarball EXIT
|
||||
echo "✅ Tarball: $TARBALL"
|
||||
# Keep last tarball for rollback (on host: /tmp/snap-site-last.tar; in VM: previous files overwritten)
|
||||
LAST_TARBALL="/tmp/snap-site-last.tar"
|
||||
cp "$TARBALL" "$LAST_TARBALL" 2>/dev/null || true
|
||||
echo "✅ Rollback tarball saved: $LAST_TARBALL"
|
||||
echo ""
|
||||
|
||||
echo "=== Deploying to /var/www/html/snap/ ==="
|
||||
# Optional: backup current deploy for rollback (inside VM)
|
||||
run_in_vm "mkdir -p /var/www/html/snap"
|
||||
run_in_vm "tar -cf /var/www/html/snap-rollback.tar -C /var/www/html/snap . 2>/dev/null || true"
|
||||
if [ "$DEPLOY_METHOD" = "direct" ]; then
|
||||
tar -xf "$TARBALL" -C /var/www/html/snap
|
||||
chown -R www-data:www-data /var/www/html/snap
|
||||
elif [ "$DEPLOY_METHOD" = "remote" ]; then
|
||||
TARNAME="$(basename "$TARBALL")"
|
||||
scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$TARBALL" root@"${PROXMOX_HOST}":/tmp/
|
||||
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@"${PROXMOX_HOST}" "pct push $VMID /tmp/$TARNAME /tmp/snap-deploy.tar"
|
||||
run_in_vm "tar -xf /tmp/snap-deploy.tar -C /var/www/html/snap"
|
||||
run_in_vm "rm -f /tmp/snap-deploy.tar"
|
||||
run_in_vm "chown -R www-data:www-data /var/www/html/snap"
|
||||
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@"${PROXMOX_HOST}" "rm -f /tmp/$TARNAME"
|
||||
else
|
||||
pct push $VMID "$TARBALL" /tmp/snap-deploy.tar
|
||||
run_in_vm "tar -xf /tmp/snap-deploy.tar -C /var/www/html/snap"
|
||||
run_in_vm "rm -f /tmp/snap-deploy.tar"
|
||||
run_in_vm "chown -R www-data:www-data /var/www/html/snap"
|
||||
fi
|
||||
echo "✅ Files deployed"
|
||||
echo ""
|
||||
|
||||
echo "=== Nginx: ensure /snap/ is served ==="
|
||||
if run_in_vm "grep -q 'location /snap/' /etc/nginx/sites-available/blockscout 2>/dev/null"; then
|
||||
echo "✅ Nginx already has location /snap/"
|
||||
run_in_vm "nginx -t && systemctl reload nginx" 2>/dev/null || true
|
||||
else
|
||||
echo "⚠️ Add location /snap/ to nginx on VMID $VMID (e.g. run explorer-monorepo scripts/fix-nginx-serve-custom-frontend.sh inside the VM)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "=== Verification checks ==="
|
||||
VERIFY_FAIL=0
|
||||
if run_in_vm "test -f /var/www/html/snap/index.html"; then
|
||||
echo "✅ /var/www/html/snap/index.html exists"
|
||||
else
|
||||
echo "❌ /var/www/html/snap/index.html missing"
|
||||
VERIFY_FAIL=1
|
||||
fi
|
||||
if run_in_vm "grep -q 'location /snap/' /etc/nginx/sites-available/blockscout 2>/dev/null"; then
|
||||
echo "✅ Nginx config has location /snap/"
|
||||
else
|
||||
echo "❌ Nginx config missing location /snap/"
|
||||
VERIFY_FAIL=1
|
||||
fi
|
||||
SNAP_CODE="$(run_in_vm "curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 5 http://127.0.0.1/snap/ 2>/dev/null" 2>/dev/null || echo "000")"
|
||||
if [ "$SNAP_CODE" = "200" ]; then
|
||||
echo "✅ http://localhost/snap/ returns 200"
|
||||
else
|
||||
echo "❌ http://localhost/snap/ returned $SNAP_CODE (expected 200)"
|
||||
VERIFY_FAIL=1
|
||||
fi
|
||||
SNAP_BODY="$(run_in_vm "curl -sS --connect-timeout 5 http://127.0.0.1/snap/ 2>/dev/null | head -c 4096" 2>/dev/null || true)"
|
||||
if echo "$SNAP_BODY" | grep -qE 'Connect|template-snap|Snap|MetaMask'; then
|
||||
echo "✅ /snap/ response contains Snap app content"
|
||||
else
|
||||
echo "⚠️ /snap/ response may not contain expected content (check in browser)"
|
||||
fi
|
||||
if [ "$VERIFY_FAIL" -eq 1 ]; then
|
||||
echo ""
|
||||
echo "⚠️ Some checks failed; see above. Snap may still work if nginx is updated."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "Deployment complete"
|
||||
echo "=========================================="
|
||||
echo "Snap site should be available at:"
|
||||
echo " - https://explorer.d-bis.org/snap/"
|
||||
echo " - http://${VM_IP}/snap/"
|
||||
echo ""
|
||||
echo "Run full verification: metamask-integration/chain138-snap/scripts/verify-snap-site-vmid5000.sh"
|
||||
echo "Or explorer + snap: explorer-monorepo/scripts/verify-vmid5000-all.sh"
|
||||
echo ""
|
||||
echo "Rollback: re-deploy previous build with: run_in_vm 'tar -xf /var/www/html/snap-rollback.tar -C /var/www/html/snap' (or use $LAST_TARBALL from host)."
|
||||
echo ""
|
||||
55
chain138-snap/scripts/verify-pre-publish.sh
Executable file
55
chain138-snap/scripts/verify-pre-publish.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run automatable items from docs/PRE_PUBLISH_TESTING.md (build, test, package contents, optional lint).
|
||||
# Usage: [SKIP_LINT=1] [SKIP_E2E=1] bash scripts/verify-pre-publish.sh
|
||||
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
ROOT="$PWD"
|
||||
|
||||
echo "=== Verify pre-publish (automatable items) ==="
|
||||
|
||||
echo "[1/5] Build..."
|
||||
pnpm run build
|
||||
|
||||
echo "[2/5] Unit tests..."
|
||||
pnpm run test
|
||||
|
||||
echo "[3/5] Package contents..."
|
||||
SNAP_DIR="$ROOT/packages/snap"
|
||||
for f in dist/bundle.js images/icon.svg snap.manifest.json; do
|
||||
if [ ! -f "$SNAP_DIR/$f" ]; then
|
||||
echo "Missing: packages/snap/$f"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo " dist/bundle.js, images/icon.svg, snap.manifest.json OK"
|
||||
|
||||
echo "[4/5] Manifest vs package.json version..."
|
||||
MANIFEST_VER=$(jq -r .version "$SNAP_DIR/snap.manifest.json")
|
||||
PKG_VER=$(jq -r .version "$SNAP_DIR/package.json")
|
||||
if [ "$MANIFEST_VER" != "$PKG_VER" ]; then
|
||||
echo "Version mismatch: snap.manifest.json=$MANIFEST_VER package.json=$PKG_VER"
|
||||
exit 1
|
||||
fi
|
||||
echo " Version $MANIFEST_VER OK"
|
||||
|
||||
if [ "${SKIP_LINT:-0}" != "1" ]; then
|
||||
echo "[5/5] Lint (Prettier only; ESLint may have existing rules)..."
|
||||
pnpm run lint:misc --check
|
||||
else
|
||||
echo "[5/5] Lint skipped (SKIP_LINT=1)"
|
||||
fi
|
||||
|
||||
if [ "${SKIP_E2E:-0}" != "1" ]; then
|
||||
echo "[E2E] Playwright (optional)..."
|
||||
if pnpm run test:e2e 2>/dev/null; then
|
||||
echo " E2E passed"
|
||||
else
|
||||
echo " E2E failed or not run (run 'npx playwright install' once if needed)"
|
||||
fi
|
||||
else
|
||||
echo "[E2E] Skipped (SKIP_E2E=1)"
|
||||
fi
|
||||
|
||||
echo "=== Automatable checks done. Complete manual items in docs/PRE_PUBLISH_TESTING.md ==="
|
||||
159
chain138-snap/scripts/verify-snap-api-and-icons.sh
Executable file
159
chain138-snap/scripts/verify-snap-api-and-icons.sh
Executable file
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verify token-aggregation API reachability, CORS, token logoURIs, and network iconUrls.
|
||||
# Usage: ./verify-snap-api-and-icons.sh [API_BASE_URL]
|
||||
# API_BASE_URL defaults to https://explorer.d-bis.org or from GATSBY_SNAP_API_BASE_URL.
|
||||
# Requires: curl, jq
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
API_BASE="${1:-${GATSBY_SNAP_API_BASE_URL:-https://explorer.d-bis.org}}"
|
||||
API_BASE="${API_BASE%/}"
|
||||
TOKEN_LIST_URL="${API_BASE}/api/v1/report/token-list?chainId=138"
|
||||
NETWORKS_URL="${API_BASE}/api/v1/networks"
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
check() {
|
||||
if "$@"; then
|
||||
((PASS++)) || true
|
||||
return 0
|
||||
else
|
||||
((FAIL++)) || true
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "=============================================="
|
||||
echo "Chain 138 Snap — API and Icons Verification"
|
||||
echo "API base: $API_BASE"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
# 1. Token list reachable
|
||||
echo "1. Token list API reachable"
|
||||
if body=$(curl -sS -L --connect-timeout 15 --max-time 30 "$TOKEN_LIST_URL" 2>/dev/null) && [ -n "$body" ]; then
|
||||
if echo "$body" | jq -e . &>/dev/null; then
|
||||
if echo "$body" | jq -e '.tokens' &>/dev/null; then
|
||||
echo " ✅ $TOKEN_LIST_URL returns valid token list JSON"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo " ❌ $TOKEN_LIST_URL returns JSON but no .tokens (proxy may route to wrong backend)"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
else
|
||||
echo " ❌ $TOKEN_LIST_URL returns invalid JSON"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
else
|
||||
echo " ❌ $TOKEN_LIST_URL failed to fetch"
|
||||
((FAIL++)) || true
|
||||
body=""
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 2. Networks API reachable
|
||||
echo "2. Networks API reachable"
|
||||
if net_body=$(curl -sS -L --connect-timeout 15 --max-time 30 "$NETWORKS_URL" 2>/dev/null) && [ -n "$net_body" ]; then
|
||||
if echo "$net_body" | jq -e . &>/dev/null; then
|
||||
if echo "$net_body" | jq -e '.networks' &>/dev/null; then
|
||||
echo " ✅ $NETWORKS_URL returns valid networks JSON"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo " ❌ $NETWORKS_URL returns JSON but no .networks (proxy may route to wrong backend)"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
else
|
||||
echo " ❌ $NETWORKS_URL returns invalid JSON"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
else
|
||||
echo " ❌ $NETWORKS_URL failed to fetch"
|
||||
((FAIL++)) || true
|
||||
net_body=""
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 3. CORS headers (allow browser/MetaMask fetch)
|
||||
echo "3. CORS headers"
|
||||
cors_headers=$(curl -sS -I -X OPTIONS -H "Origin: https://explorer.d-bis.org" -H "Access-Control-Request-Method: GET" "$TOKEN_LIST_URL" 2>/dev/null || true)
|
||||
if echo "$cors_headers" | grep -qi "access-control-allow-origin"; then
|
||||
echo " ✅ CORS headers present (token-aggregation uses cors())"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo " ⚠ CORS headers not detected (OPTIONS preflight). GET may still work if server allows *."
|
||||
echo " Token-aggregation uses cors() by default; verify in browser if issues occur."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 4. Every token has logoURI
|
||||
echo "4. Token logoURI"
|
||||
if [ -n "$body" ]; then
|
||||
missing=$(echo "$body" | jq -r '.tokens[]? | select(.logoURI == null or .logoURI == "") | .symbol' 2>/dev/null || true)
|
||||
if [ -z "$missing" ]; then
|
||||
count=$(echo "$body" | jq '.tokens | length' 2>/dev/null || echo 0)
|
||||
echo " ✅ All $count tokens have logoURI"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo " ❌ Tokens missing logoURI: $missing"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
else
|
||||
echo " ⏭ Skipped (token list not fetched)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 5. List-level logoURI
|
||||
echo "5. List-level logoURI"
|
||||
if [ -n "$body" ]; then
|
||||
list_logo=$(echo "$body" | jq -r '.logoURI // empty' 2>/dev/null)
|
||||
if [ -n "$list_logo" ]; then
|
||||
echo " ✅ List logoURI: $list_logo"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo " ⚠ List-level logoURI missing (optional)"
|
||||
fi
|
||||
else
|
||||
echo " ⏭ Skipped (token list not fetched)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 6. Network iconUrls
|
||||
echo "6. Network iconUrls"
|
||||
if [ -n "$net_body" ]; then
|
||||
missing=$(echo "$net_body" | jq -r '.networks[]? | select(.iconUrls == null or (.iconUrls | length) == 0) | "\(.chainName) (\(.chainIdDecimal))"' 2>/dev/null || true)
|
||||
if [ -z "$missing" ]; then
|
||||
count=$(echo "$net_body" | jq '.networks | length' 2>/dev/null || echo 0)
|
||||
echo " ✅ All $count networks have iconUrls"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo " ❌ Networks missing iconUrls: $missing"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
else
|
||||
echo " ⏭ Skipped (networks not fetched)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 7. Sample logo URL reachable
|
||||
echo "7. Sample logo URLs"
|
||||
if [ -n "$body" ]; then
|
||||
sample_logo=$(echo "$body" | jq -r '.tokens[0].logoURI // empty' 2>/dev/null)
|
||||
if [ -n "$sample_logo" ]; then
|
||||
if curl -sS -o /dev/null -w "%{http_code}" -L --connect-timeout 10 "$sample_logo" 2>/dev/null | grep -qE "^(200|301|302)$"; then
|
||||
echo " ✅ Sample logo reachable: ${sample_logo:0:60}..."
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo " ⚠ Sample logo may be unreachable: $sample_logo"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "=============================================="
|
||||
echo "Result: $PASS passed, $FAIL failed"
|
||||
echo "=============================================="
|
||||
if [ "$FAIL" -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
@@ -1,82 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verify Chain 138 Snap site deployment on VMID 5000.
|
||||
# Usage: ./verify-snap-site-vmid5000.sh [BASE_URL]
|
||||
# BASE_URL defaults to https://explorer.d-bis.org (or use http://192.168.11.140 for LAN)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BASE_URL="${1:-https://explorer.d-bis.org}"
|
||||
BASE_URL="${BASE_URL%/}"
|
||||
VMID=5000
|
||||
VM_IP="${EXPLORER_VM_IP:-192.168.11.140}"
|
||||
PROXMOX_HOST="${PROXMOX_HOST_R630_02:-192.168.11.12}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
check() {
|
||||
local name="$1"
|
||||
if eval "$2"; then
|
||||
echo "✅ $name"
|
||||
((PASS++)) || true
|
||||
return 0
|
||||
else
|
||||
echo "❌ $name"
|
||||
((FAIL++)) || true
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "=============================================="
|
||||
echo "Snap site (VMID $VMID) verification"
|
||||
echo "BASE_URL=$BASE_URL"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
# 1) Public URL /snap/ returns 200 (follow redirects)
|
||||
HTTP_CODE="$(curl -sS -L -o /dev/null -w "%{http_code}" --connect-timeout 10 "$BASE_URL/snap/" 2>/dev/null || echo 000)"
|
||||
check "$BASE_URL/snap/ returns 200" "[ \"$HTTP_CODE\" = \"200\" ]"
|
||||
|
||||
# 2) /snap/ response contains Snap app content (follow redirects)
|
||||
SNAP_BODY="$(curl -sS -L --connect-timeout 10 "$BASE_URL/snap/" 2>/dev/null | head -c 8192)" || true
|
||||
check "/snap/ contains Snap app content (Connect|Snap|MetaMask)" "echo \"$SNAP_BODY\" | grep -qE 'Connect|template-snap|Snap|MetaMask'"
|
||||
|
||||
# 3) /snap/index.html returns 200 (follow redirects)
|
||||
HTTP_CODE="$(curl -sS -L -o /dev/null -w "%{http_code}" --connect-timeout 10 "$BASE_URL/snap/index.html" 2>/dev/null || echo 000)"
|
||||
check "$BASE_URL/snap/index.html returns 200" "[ \"$HTTP_CODE\" = \"200\" ]"
|
||||
|
||||
# 4) Optional: /snap/version.json returns 200 and valid JSON (build version/health)
|
||||
VERSION_CODE="$(curl -sS -L -o /dev/null -w "%{http_code}" --connect-timeout 5 "$BASE_URL/snap/version.json" 2>/dev/null || echo 000)"
|
||||
if [ "$VERSION_CODE" = "200" ]; then
|
||||
echo "✅ $BASE_URL/snap/version.json returns 200 (build version/health)"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo "⏭ $BASE_URL/snap/version.json returned $VERSION_CODE (optional; set prebuild to generate)"
|
||||
fi
|
||||
|
||||
# 6) Optional: when pct or SSH available, check inside VM
|
||||
if command -v pct &>/dev/null; then
|
||||
if pct exec $VMID -- test -f /var/www/html/snap/index.html 2>/dev/null; then
|
||||
echo "✅ /var/www/html/snap/index.html exists in VM"
|
||||
((PASS++)) || true
|
||||
fi
|
||||
if pct exec $VMID -- grep -q 'location /snap/' /etc/nginx/sites-available/blockscout 2>/dev/null; then
|
||||
echo "✅ Nginx has location /snap/ in VM"
|
||||
((PASS++)) || true
|
||||
fi
|
||||
elif ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@"${PROXMOX_HOST}" "pct exec $VMID -- test -f /var/www/html/snap/index.html" 2>/dev/null; then
|
||||
echo "✅ /var/www/html/snap/index.html exists in VM (via SSH)"
|
||||
((PASS++)) || true
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
echo "Result: $PASS passed, $FAIL failed"
|
||||
echo "=============================================="
|
||||
if [ "$FAIL" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "See: $SCRIPT_DIR/../DEPLOY_VMID5000.md"
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
Reference in New Issue
Block a user