Complete markdown files cleanup and organization

- Organized 252 files across project
- Root directory: 187 → 2 files (98.9% reduction)
- Moved configuration guides to docs/04-configuration/
- Moved troubleshooting guides to docs/09-troubleshooting/
- Moved quick start guides to docs/01-getting-started/
- Moved reports to reports/ directory
- Archived temporary files
- Generated comprehensive reports and documentation
- Created maintenance scripts and guides

All files organized according to established standards.
This commit is contained in:
defiQUG
2026-01-06 01:46:25 -08:00
parent 1edcec953c
commit cb47cce074
1327 changed files with 217220 additions and 801 deletions

View File

@@ -0,0 +1,229 @@
# Token List Infrastructure Implementation Status
**Date**: 2025-12-22
**Status**: ✅ **COMPLETE**
---
## Implementation Summary
The DBIS Chain 138 Token List infrastructure has been successfully upgraded to production standards according to the detailed technical plan.
---
## ✅ Completed Components
### Phase 1: Foundation & Structure ✅
- ✅ Created organized directory structure (`token-lists/`)
- ✅ Migrated token list to `token-lists/lists/dbis-138.tokenlist.json`
- ✅ Enhanced token list with:
- `keywords` field
- Updated name to "DBIS Chain 138 Token List"
- All addresses EIP-55 checksummed
- Tag system for categorization
- ✅ Logo management structure created (logos/ directory)
### Phase 2: Enhanced Validation ✅
- ✅ Enhanced validation script with:
- EIP-55 checksum validation
- Duplicate detection (addresses and symbols)
- Chain ID strict validation (must be 138)
- Logo URL validation
- Semantic versioning validation
- ✅ Address checksum script (`checksum-addresses.js`)
- Validates and fixes checksummed addresses
- ✅ Logo validation script (`validate-logos.js`)
- Validates logo URL accessibility and MIME types
- ✅ On-chain verification script (`verify-on-chain.js`)
- Supports dual RPC endpoints with fallback
- ERC-20 token verification
- Oracle contract verification
- Optional/required modes
### Phase 3: CI/CD Infrastructure ✅
- ✅ PR validation workflow (`.github/workflows/validate-pr.yml`)
- Schema validation
- Checksum validation
- Duplicate detection
- Logo validation (non-blocking)
- On-chain verification (optional)
- ✅ Release workflow (`.github/workflows/release.yml`)
- Full validation (required)
- On-chain verification (required)
- Checksum generation
- minisign signing
- GitHub Release creation
- ✅ Release automation script (`release.sh`)
- Version bumping (semantic versioning)
- Timestamp updates
- Validation orchestration
### Phase 4: Signing & Security ✅
- ✅ minisign signing script (`sign-list.sh`)
- Keypair generation
- Signing functionality
- Signature verification
- CI/CD integration via environment variables
- ✅ CODEOWNERS file (`.github/CODEOWNERS`)
- Token list maintainers defined
- PR approval requirements
### Phase 5: Hosting & Distribution ✅
- ✅ GitHub Pages configuration (via workflows)
- ✅ GitHub Releases integration
- ✅ Hosting script updated for new structure
- ✅ Documentation for DBIS domain hosting (if available)
### Phase 6: Documentation ✅
- ✅ Policy documentation (`TOKEN_LIST_POLICY.md`)
- Inclusion requirements
- Delisting criteria
- Governance process
- ✅ Integration guide (`INTEGRATION_GUIDE.md`)
- MetaMask integration
- Ledger integration
- dApp integration
- Explorer/indexer integration
- Signature verification
- ✅ Changelog (`CHANGELOG.md`)
- Version history tracking
- Keep a Changelog format
- ✅ Main README (`README.md`)
- Quick start guide
- Directory structure
- Usage examples
- ✅ Updated authoring guide (`docs/TOKEN_LIST_AUTHORING_GUIDE.md`)
- References to new structure
- Updated validation commands
### Phase 7: Migration ✅
- ✅ New structure created
- ✅ Token list migrated and enhanced
- ✅ All addresses validated and checksummed
- ✅ Existing scripts updated for backward compatibility
- ✅ Documentation cross-referenced
---
## 📁 Directory Structure
```
token-lists/
├── lists/
│ └── dbis-138.tokenlist.json ✅ Main token list
├── logos/ ✅ Logo directory (ready)
├── scripts/
│ ├── validate-token-list.js ✅ Enhanced validation
│ ├── checksum-addresses.js ✅ Checksum validator
│ ├── validate-logos.js ✅ Logo validator
│ ├── verify-on-chain.js ✅ On-chain verifier
│ ├── release.sh ✅ Release automation
│ └── sign-list.sh ✅ Signing script
├── docs/
│ ├── TOKEN_LIST_POLICY.md ✅ Policy documentation
│ ├── INTEGRATION_GUIDE.md ✅ Integration guide
│ ├── CHANGELOG.md ✅ Version history
│ └── README.md ✅ Main README
├── minisign.pub ✅ Public key placeholder
└── README.md ✅ Project README
.github/
├── workflows/
│ ├── validate-pr.yml ✅ PR validation
│ └── release.yml ✅ Release workflow
└── CODEOWNERS ✅ Code owners
```
---
## 🔧 Dependencies Installed
-`ajv` - JSON schema validation
-`ajv-formats` - Format validation for AJV
-`ethers` - Address validation and on-chain verification (already installed)
---
## 🧪 Validation Status
All validation scripts tested and working:
- ✅ Schema validation (with AJV)
- ✅ EIP-55 checksum validation
- ✅ Duplicate detection
- ✅ Chain ID validation
- ✅ Address checksum fixing
---
## 📝 Next Steps (Optional Enhancements)
1. **Logo Management**
- Download and host logos in `token-lists/logos/`
- Update logoURI fields to use controlled hosting
2. **minisign Keypair Generation**
- Run `./scripts/sign-list.sh --generate-key`
- Store private key in GitHub Secrets
- Commit public key to repository
3. **GitHub Pages Setup**
- Enable GitHub Pages in repository settings
- Configure to serve from `token-lists/lists/` directory
4. **DBIS Domain Configuration** (if available)
- Configure `tokens.d-bis.org` domain
- Setup nginx with CORS headers
- Mirror GitHub releases
5. **First Production Release**
- Review token list contents
- Run release script: `./scripts/release.sh patch`
- Create git tag and push
- Verify GitHub Actions release workflow
---
## 🎯 Success Criteria Met
- ✅ Public HTTPS token list endpoint structure ready
- ✅ CI validation workflows blocking invalid changes
- ✅ On-chain verification implemented
- ✅ Signing infrastructure ready
- ✅ Comprehensive documentation
- ✅ Semantic versioning support
- ✅ Governance and policy documentation
---
## 📚 Key Files Reference
### Token List
- **Main**: `token-lists/lists/dbis-138.tokenlist.json`
- **Legacy**: `docs/METAMASK_TOKEN_LIST.json` (backward compatibility)
### Scripts
- **Validation**: `token-lists/scripts/validate-token-list.js`
- **Release**: `token-lists/scripts/release.sh`
- **Signing**: `token-lists/scripts/sign-list.sh`
### Documentation
- **Policy**: `token-lists/docs/TOKEN_LIST_POLICY.md`
- **Integration**: `token-lists/docs/INTEGRATION_GUIDE.md`
- **Changelog**: `token-lists/docs/CHANGELOG.md`
### CI/CD
- **PR Validation**: `.github/workflows/validate-pr.yml`
- **Release**: `.github/workflows/release.yml`
---
**Implementation Complete**: 2025-12-22

280
token-lists/README.md Normal file
View File

@@ -0,0 +1,280 @@
# DBIS Chain 138 Token Lists
Production-ready token lists for ChainID 138 (DBIS Chain), following the [Uniswap Token Lists Specification](https://github.com/Uniswap/token-lists).
---
## Quick Start
### Validate Token List
```bash
node scripts/validate-token-list.js lists/dbis-138.tokenlist.json
```
### Verify On-Chain
```bash
node scripts/verify-on-chain.js lists/dbis-138.tokenlist.json
```
### Prepare Release
```bash
./scripts/release.sh patch # or minor, major
```
### Sign Token List
```bash
./scripts/sign-list.sh sign
```
---
## Directory Structure
```
token-lists/
├── lists/
│ └── dbis-138.tokenlist.json # Main token list
├── logos/ # Token logos (future)
├── scripts/
│ ├── validate-token-list.js # Schema & validation
│ ├── checksum-addresses.js # EIP-55 checksum validation
│ ├── validate-logos.js # Logo URL validation
│ ├── verify-on-chain.js # On-chain verification
│ ├── release.sh # Release automation
│ └── sign-list.sh # minisign signing
├── docs/
│ ├── TOKEN_LIST_POLICY.md # Inclusion/delisting policy
│ ├── INTEGRATION_GUIDE.md # Integration instructions
│ ├── CHANGELOG.md # Version history
│ └── TOKEN_LIST_AUTHORING_GUIDE.md # Authoring guide
└── minisign.pub # Public key for verification
```
---
## Token List Contents
Current version: **1.1.0**
### Tokens
1. **WETH** (Wrapped Ether)
- Address: `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2`
- Decimals: 18
- Category: DeFi, Wrapped
2. **WETH10** (Wrapped Ether v10)
- Address: `0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F`
- Decimals: 18
- Category: DeFi, Wrapped
3. **ETH/USD Price Feed** (Oracle)
- Address: `0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6`
- Decimals: 8
- Category: Oracle, Price Feed
---
## Validation
All token lists are validated against:
- Uniswap Token Lists JSON Schema
- EIP-55 address checksumming
- Chain ID strict validation (must be 138)
- Duplicate detection (addresses and symbols)
- Logo URL accessibility
- On-chain contract verification
### Running Validations
```bash
# Full validation
node scripts/validate-token-list.js lists/dbis-138.tokenlist.json
# Check address checksums
node scripts/checksum-addresses.js lists/dbis-138.tokenlist.json
# Fix checksummed addresses
node scripts/checksum-addresses.js lists/dbis-138.tokenlist.json --fix
# Validate logos
node scripts/validate-logos.js lists/dbis-138.tokenlist.json
# Verify on-chain
node scripts/verify-on-chain.js lists/dbis-138.tokenlist.json
```
---
## CI/CD
### PR Validation
GitHub Actions automatically validates token lists on pull requests:
- JSON schema validation
- Address checksum validation
- Duplicate detection
- Logo validation (non-blocking)
- On-chain verification (optional, non-blocking)
### Release Process
1. Update version using release script:
```bash
./scripts/release.sh patch # or minor, major
```
2. Create git tag:
```bash
git tag -a v1.2.0 -m "Release v1.2.0"
git push --tags
```
3. GitHub Actions release workflow will:
- Run all validations (required)
- Perform on-chain verification (required)
- Generate checksums
- Sign token list
- Create GitHub Release
---
## Hosting
Token lists are hosted at:
- **GitHub Pages**: `https://{user}.github.io/{repo}/token-lists/lists/dbis-138.tokenlist.json`
- **GitHub Raw**: `https://raw.githubusercontent.com/{user}/{repo}/main/token-lists/lists/dbis-138.tokenlist.json`
- **DBIS Domain** (if configured): `https://tokens.d-bis.org/lists/dbis-138.tokenlist.json`
---
## Integration
See [INTEGRATION_GUIDE.md](docs/INTEGRATION_GUIDE.md) for detailed integration instructions for:
- MetaMask
- Ledger
- dApps
- Explorers/Indexers
- Custom applications
---
## Policy
See [TOKEN_LIST_POLICY.md](docs/TOKEN_LIST_POLICY.md) for:
- Inclusion requirements
- Delisting criteria
- Governance process
- Versioning policy
---
## Security
### Signature Verification
Token lists are signed with minisign for integrity verification:
```bash
# Verify signature
minisign -V -p minisign.pub -m lists/dbis-138.tokenlist.json -x lists/dbis-138.tokenlist.json.sig
```
### Public Key
The public key is included in the repository: `minisign.pub`
Private key is stored securely in GitHub Secrets for CI/CD signing.
---
## Contributing
### Adding Tokens
1. Create issue or pull request with token details
2. Ensure token meets inclusion requirements
3. Code owners review and approve
4. Validations run automatically via CI/CD
### Updating Tokens
1. Update token metadata in `lists/dbis-138.tokenlist.json`
2. Run validations locally
3. Create pull request
4. CI/CD validates changes
### Removing Tokens
1. Create issue explaining removal reason
2. Code owners review and approve
3. Update token list (major version bump)
4. Update CHANGELOG.md
---
## Dependencies
- Node.js >= 16.0.0
- pnpm (package manager)
- ethers.js (for address validation and on-chain verification)
- ajv & ajv-formats (for JSON schema validation)
- minisign (for signing, optional for verification)
Install dependencies:
```bash
pnpm install
```
---
## Chainlists Submission
Files for submitting to Chainlists.org are in the `chainlists/` directory:
- `chainlists/chain-138.json` - Chain configuration in Chainlists format
- `chainlists/SUBMISSION_GUIDE.md` - Submission instructions
### Validate Chain Configuration
```bash
node scripts/validate-chainlists.js chainlists/chain-138.json
```
### Submit to Chainlists
See `chainlists/SUBMISSION_GUIDE.md` for detailed submission instructions.
The chain configuration includes:
- Chain ID: 138
- RPC URLs (primary and fallback)
- Block explorer
- Native currency information
- Chain metadata
---
## Links
- [Uniswap Token Lists Specification](https://github.com/Uniswap/token-lists)
- [JSON Schema](https://uniswap.org/tokenlist.schema.json)
- [Chainlists Repository](https://github.com/ethereum-lists/chains)
- [Chainlists Website](https://chainlist.org)
- [EIP-55: Mixed-case checksum address encoding](https://eips.ethereum.org/EIPS/eip-55)
- [EIP-155: Simple replay attack protection](https://eips.ethereum.org/EIPS/eip-155)
- [Semantic Versioning](https://semver.org/)
---
**Last Updated**: 2025-12-22

View File

@@ -0,0 +1,208 @@
# Chainlists Submission Guide
This directory contains files needed to submit DBIS Chain 138 to Chainlists.org.
---
## Files
- `chain-138.json` - Chain configuration in Chainlists format
- `SUBMISSION_GUIDE.md` - This file
## Token List
The token list is available at:
- `../lists/dbis-138.tokenlist.json`
- Public URL (when hosted): `https://raw.githubusercontent.com/{user}/{repo}/main/token-lists/lists/dbis-138.tokenlist.json`
---
## Submission Process
### Option 1: GitHub Pull Request (Recommended)
1. **Fork the Chainlists repository**
- Go to: https://github.com/ethereum-lists/chains
- Click "Fork" to create your fork
2. **Clone your fork**
```bash
git clone https://github.com/{your-username}/chains.git
cd chains
```
3. **Add chain configuration**
- Copy `chain-138.json` to `_data/chains/eip155-138/chain.json`
- The directory structure should be: `_data/chains/eip155-{chainId}/chain.json`
```bash
mkdir -p _data/chains/eip155-138
cp /path/to/token-lists/chainlists/chain-138.json _data/chains/eip155-138/chain.json
```
4. **Validate the configuration**
- Review the JSON structure
- Ensure all URLs are accessible
- Verify RPC endpoints are working
5. **Create pull request**
```bash
git checkout -b add-dbis-chain-138
git add _data/chains/eip155-138/chain.json
git commit -m "Add DBIS Chain (ChainID 138)"
git push origin add-dbis-chain-138
```
- Then create a pull request on GitHub
6. **PR Requirements**
- Provide brief description of the chain
- Include links to documentation/website
- Ensure all URLs in the config are publicly accessible
- Mention any special features or use cases
### Option 2: Direct Submission
If Chainlists provides a submission form on their website, use it with the information from `chain-138.json`.
---
## Required Information
- **Chain ID**: 138 (0x8a)
- **Network Name**: DBIS Chain / SMOM-DBIS-138
- **RPC URLs**:
- Primary: `https://rpc-core.d-bis.org`
- Secondary: `https://rpc-http-pub.d-bis.org`
- **Block Explorer**: `https://explorer.d-bis.org`
- **Native Currency**: ETH (18 decimals)
- **Website**: `https://d-bis.org` (if available)
- **Short Name**: `dbis`
---
## Token List Submission
Token lists can be submitted separately:
1. **Host token list at public URL**
- Use GitHub Pages or GitHub Raw URL
- Ensure proper CORS headers
- Verify accessibility
2. **Submit to Token List Registry**
- Ensure token list follows Uniswap Token Lists specification
- Validate using: `node ../scripts/validate-token-list.js ../lists/dbis-138.tokenlist.json`
- Submit URL to Chainlists token list registry (if available)
3. **Link to Chain Configuration**
- Token list should reference ChainID 138
- Ensure consistency between chain config and token list
---
## Validation
Before submitting, validate the chain configuration:
```bash
# Validate JSON structure
cat chain-138.json | jq .
# Verify required fields
jq -e '.chainId == 138' chain-138.json
jq -e '.rpc | length > 0' chain-138.json
jq -e '.nativeCurrency.symbol == "ETH"' chain-138.json
# Use validation script
node ../scripts/validate-chainlists.js chain-138.json
```
### Test RPC Endpoints
```bash
# Test primary RPC
curl -X POST https://rpc-core.d-bis.org \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
# Should return: {"jsonrpc":"2.0","id":1,"result":"0x8a"}
# Test secondary RPC
curl -X POST https://rpc-http-pub.d-bis.org \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
```
### Test Block Explorer
```bash
# Test explorer accessibility
curl -I https://explorer.d-bis.org
# Should return HTTP 200 or 301/302 redirect
```
---
## Chainlists Format Reference
The chain configuration follows the Chainlists format:
```json
{
"name": "Display name",
"chain": "Chain identifier",
"rpc": ["RPC URL array"],
"faucets": ["Faucet URLs if available"],
"nativeCurrency": {
"name": "Currency name",
"symbol": "Currency symbol",
"decimals": 18
},
"infoURL": "Information website URL",
"shortName": "Short identifier",
"chainId": 138,
"networkId": 138,
"explorers": [{
"name": "Explorer name",
"url": "Explorer URL",
"standard": "EIP3091"
}],
"icon": "Icon URL (optional)"
}
```
---
## Post-Submission
After your chain is added to Chainlists:
1. **Verify on Chainlists.org**
- Search for "DBIS" or ChainID 138
- Verify all information displays correctly
- Test "Add to MetaMask" functionality
2. **Update Documentation**
- Reference Chainlists in your documentation
- Link to Chainlists entry
- Update integration guides
3. **Monitor**
- Check for user feedback
- Update if RPC URLs or explorer URLs change
- Keep chain information current
---
## References
- [Chainlists Repository](https://github.com/ethereum-lists/chains)
- [Chainlists Website](https://chainlist.org)
- [Uniswap Token Lists](https://github.com/Uniswap/token-lists)
- [EIP-155: Simple replay attack protection](https://eips.ethereum.org/EIPS/eip-155)
- [EIP-3091: Block Explorer API](https://eips.ethereum.org/EIPS/eip-3091)
---
**Last Updated**: 2025-12-22

View File

@@ -0,0 +1,27 @@
{
"name": "DBIS Chain",
"chain": "DBIS",
"rpc": [
"https://rpc-http-pub.d-bis.org",
"https://rpc-http-prv.d-bis.org"
],
"faucets": [],
"nativeCurrency": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"infoURL": "https://d-bis.org",
"shortName": "dbis",
"chainId": 138,
"networkId": 138,
"explorers": [
{
"name": "Blockscout",
"url": "https://explorer.d-bis.org",
"standard": "EIP3091"
}
],
"icon": "https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png"
}

View File

@@ -0,0 +1,35 @@
# Changelog
All notable changes to the DBIS Chain 138 Token List will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0] - 2025-12-22
### Added
- Initial token list for ChainID 138
- WETH9 token (Wrapped Ether) at `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2`
- WETH10 token (Wrapped Ether v10) at `0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F`
- ETH/USD Price Feed oracle at `0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6`
### Changed
- N/A (initial release)
### Security
- All addresses validated and checksummed (EIP-55)
- On-chain verification completed for all tokens
---
## [Unreleased]
### Planned
- Additional token entries as requested
- Enhanced metadata and documentation
- Logo updates for controlled hosting
---
[1.1.0]: https://github.com/dbis/token-lists/releases/tag/v1.1.0

View File

@@ -0,0 +1,320 @@
# Token List Integration Guide
**Network**: ChainID 138 (DBIS Chain)
**Token List**: DBIS Chain 138 Token List
**Last Updated**: 2025-12-22
---
## Overview
This guide explains how to integrate the DBIS Chain 138 Token List into various applications, wallets, and services.
---
## Token List Endpoints
### Primary Endpoints
- **GitHub Pages**: `https://{user}.github.io/{repo}/token-lists/lists/dbis-138.tokenlist.json`
- **GitHub Raw**: `https://raw.githubusercontent.com/{user}/{repo}/main/token-lists/lists/dbis-138.tokenlist.json`
- **DBIS Domain** (if configured): `https://tokens.d-bis.org/lists/dbis-138.tokenlist.json`
### Verification
- **Signature**: `{token-list-url}.sig`
- **Checksums**: See GitHub Releases for `SHA256SUMS`
- **Public Key**: `token-lists/minisign.pub`
---
## MetaMask Integration
### Method 1: Manual Addition
1. Open MetaMask
2. Go to **Settings****Security & Privacy****Token Lists**
3. Click **"Add custom token list"**
4. Enter token list URL:
```
https://raw.githubusercontent.com/{user}/{repo}/main/token-lists/lists/dbis-138.tokenlist.json
```
5. Click **Add**
### Method 2: Programmatic Addition
```javascript
// Add network first (if not already added)
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: '0x8a', // 138 in hex
chainName: 'SMOM-DBIS-138',
rpcUrls: ['https://rpc-core.d-bis.org'],
nativeCurrency: {
name: 'Ether',
symbol: 'ETH',
decimals: 18
},
blockExplorerUrls: ['https://explorer.d-bis.org']
}]
});
// Token list is added via MetaMask UI (Settings → Token Lists)
// MetaMask doesn't provide API for programmatic token list addition
```
### Verifying Token List in MetaMask
1. Ensure you're connected to ChainID 138
2. Go to **Assets** tab
3. Click **"Import tokens"**
4. Tokens from the list should appear automatically
---
## Ledger Integration
Ledger Live doesn't directly support token lists. Use MetaMask with Ledger:
1. **Connect Ledger to MetaMask**
- Connect Ledger device
- Enable "Ethereum" app on Ledger
- In MetaMask, select "Connect Hardware Wallet" → "Ledger"
2. **Add DBIS Network**
- Follow MetaMask network addition steps above
- Network will use Ledger for signing
3. **Add Token List**
- Follow MetaMask token list addition steps above
- Tokens will be available when using Ledger with MetaMask
---
## dApp Integration
### Using Web3.js
```javascript
const Web3 = require('web3');
const web3 = new Web3('https://rpc-core.d-bis.org');
// Fetch token list
async function getTokenList() {
const response = await fetch('https://raw.githubusercontent.com/{user}/{repo}/main/token-lists/lists/dbis-138.tokenlist.json');
const tokenList = await response.json();
return tokenList.tokens;
}
// Use token metadata
const tokens = await getTokenList();
const wethToken = tokens.find(t => t.symbol === 'WETH');
console.log(`WETH address: ${wethToken.address}`);
console.log(`WETH decimals: ${wethToken.decimals}`);
```
### Using Ethers.js
```javascript
const { ethers } = require('ethers');
const provider = new ethers.JsonRpcProvider('https://rpc-core.d-bis.org');
// Fetch token list
async function getTokenList() {
const response = await fetch('https://raw.githubusercontent.com/{user}/{repo}/main/token-lists/lists/dbis-138.tokenlist.json');
const tokenList = await response.json();
return tokenList.tokens;
}
// Create contract instance using token metadata
const tokens = await getTokenList();
const wethToken = tokens.find(t => t.symbol === 'WETH');
const erc20ABI = [
'function balanceOf(address) view returns (uint256)',
'function decimals() view returns (uint8)'
];
const contract = new ethers.Contract(wethToken.address, erc20ABI, provider);
const balance = await contract.balanceOf(userAddress);
const decimals = await contract.decimals();
console.log(`Balance: ${ethers.formatUnits(balance, decimals)} WETH`);
```
---
## Explorer/Indexer Integration
### Blockscout Integration
1. **Configure Token List URL**
- Add token list URL to Blockscout configuration
- Blockscout will fetch token metadata automatically
2. **Manual Token Addition**
- Use Blockscout admin interface
- Add tokens from the list manually
### Custom Indexer
```javascript
// Fetch and cache token list
async function fetchTokenList() {
const response = await fetch('https://raw.githubusercontent.com/{user}/{repo}/main/token-lists/lists/dbis-138.tokenlist.json');
const tokenList = await response.json();
// Create lookup map
const tokenMap = new Map();
tokenList.tokens.forEach(token => {
tokenMap.set(token.address.toLowerCase(), token);
});
return tokenMap;
}
// Use in indexing
const tokenMap = await fetchTokenList();
const token = tokenMap.get(contractAddress.toLowerCase());
if (token) {
console.log(`Token: ${token.symbol} (${token.name})`);
console.log(`Decimals: ${token.decimals}`);
console.log(`Logo: ${token.logoURI}`);
}
```
---
## Signature Verification
Verify token list integrity using minisign:
```bash
# Install minisign (if not installed)
# macOS: brew install minisign
# Ubuntu: apt-get install minisign
# Download files
curl -O https://raw.githubusercontent.com/{user}/{repo}/main/token-lists/lists/dbis-138.tokenlist.json
curl -O https://raw.githubusercontent.com/{user}/{repo}/main/token-lists/lists/dbis-138.tokenlist.json.sig
curl -O https://raw.githubusercontent.com/{user}/{repo}/main/token-lists/minisign.pub
# Verify signature
minisign -V -p minisign.pub -m dbis-138.tokenlist.json -x dbis-138.tokenlist.json.sig
```
---
## CORS Configuration
If hosting the token list on a custom domain, ensure CORS headers are configured:
### Nginx Example
```nginx
location /lists/dbis-138.tokenlist.json {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, OPTIONS";
add_header Content-Type application/json;
add_header Cache-Control "public, max-age=3600";
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, OPTIONS";
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
}
```
### Apache Example
```apache
<Location "/lists/dbis-138.tokenlist.json">
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET, OPTIONS"
Header set Content-Type "application/json"
Header set Cache-Control "public, max-age=3600"
</Location>
```
---
## Rate Limiting
When fetching the token list:
- Use appropriate caching (recommended: 1 hour)
- Respect rate limits if using GitHub API
- Consider mirroring to CDN for high-traffic applications
### Caching Example
```javascript
const CACHE_KEY = 'dbis-token-list-v1.1.0';
const CACHE_TTL = 3600000; // 1 hour
async function getTokenListCached() {
const cached = localStorage.getItem(CACHE_KEY);
const cachedTime = localStorage.getItem(`${CACHE_KEY}-time`);
if (cached && cachedTime && Date.now() - parseInt(cachedTime) < CACHE_TTL) {
return JSON.parse(cached);
}
const response = await fetch('https://raw.githubusercontent.com/{user}/{repo}/main/token-lists/lists/dbis-138.tokenlist.json');
const tokenList = await response.json();
localStorage.setItem(CACHE_KEY, JSON.stringify(tokenList));
localStorage.setItem(`${CACHE_KEY}-time`, Date.now().toString());
return tokenList;
}
```
---
## Error Handling
Always handle errors when fetching token lists:
```javascript
async function getTokenList() {
try {
const response = await fetch('https://raw.githubusercontent.com/{user}/{repo}/main/token-lists/lists/dbis-138.tokenlist.json');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const tokenList = await response.json();
// Validate structure
if (!tokenList.tokens || !Array.isArray(tokenList.tokens)) {
throw new Error('Invalid token list format');
}
return tokenList;
} catch (error) {
console.error('Failed to fetch token list:', error);
// Fallback to cached version or default list
return null;
}
}
```
---
## Support
For integration questions or issues:
- Create an issue in the repository
- Check existing documentation
- Contact DBIS team
---
**Last Updated**: 2025-12-22

View File

@@ -0,0 +1,161 @@
# Token List Inclusion Policy
**Version**: 1.0
**Last Updated**: 2025-12-22
**Network**: ChainID 138 (DBIS Chain)
---
## Overview
This document defines the inclusion and delisting policy for the DBIS Chain 138 Token List. All tokens must meet these requirements to be included in the list.
---
## Inclusion Requirements
### Required Criteria
1. **Contract Verification**
- Contract source code must be verified on the block explorer (if available)
- Contract address must have deployed bytecode
2. **Chain ID**
- Token must be deployed on ChainID 138
- Chain ID must be correctly specified in the token list entry
3. **Contract Standards**
- ERC-20 tokens must implement standard ERC-20 interface
- Oracle feeds must implement Chainlink-compatible interface
- All functions (decimals, symbol, name, etc.) must be callable
4. **Metadata Accuracy**
- `decimals`, `symbol`, and `name` must match on-chain values
- Address must be EIP-55 checksummed
- Logo URL must be accessible and return valid image
5. **Security**
- No known security vulnerabilities or exploits
- No fake branding or impersonation
- Contract must be audited (preferred) or have disclosed upgradeability
### Preferred Criteria
1. **Audit Status**
- Public security audit reports preferred
- Links to audit reports should be provided
2. **Upgradeability**
- Immutable contracts preferred
- If upgradeable, admin keys and upgrade process must be disclosed
3. **Governance**
- Decentralized governance preferred
- Centralized admin keys should be disclosed
4. **Liquidity**
- Sufficient liquidity for trading (if applicable)
- Active trading volume (if applicable)
---
## Token Categories
Tokens are categorized using tags:
- **core**: Native-wrapped tokens (e.g., WETH), stablecoins
- **defi**: DeFi protocol tokens (DEX LP tokens, lending tokens)
- **oracle**: Oracle price feed contracts
- **experimental**: New or unproven tokens (use with caution)
---
## Delisting Criteria
Tokens will be removed from the list if:
1. **Security Issues**
- Exploit or security vulnerability discovered
- Rug pull or exit scam
- Malicious contract behavior
2. **Compliance**
- Legal or regulatory issues
- DMCA takedown requests
- Court orders or legal injunctions
3. **Misrepresentation**
- Fake branding or impersonation
- Incorrect metadata that cannot be corrected
- Violation of trademark or copyright
4. **Technical Issues**
- Contract no longer functional (e.g., permanently paused)
- Migration to new contract address (old address removed)
- Chain migration (token no longer on ChainID 138)
5. **Inactivity**
- Token no longer in use
- No trading activity for extended period
- Project abandoned
---
## Governance Process
### Adding Tokens
1. **Request Submission**
- Create issue or pull request with token details
- Include contract address, metadata, and verification evidence
- Provide links to audits, documentation, or verification
2. **Review Process**
- Code owners review submission
- On-chain verification performed
- Security assessment (if applicable)
3. **Approval**
- Requires approval from at least one code owner
- All validations must pass
- Version bump (minor for additions)
### Removing Tokens
1. **Delisting Request**
- Create issue explaining reason for delisting
- Provide evidence (exploit reports, legal notices, etc.)
2. **Review Process**
- Code owners review delisting request
- Verify claims and evidence
- Consider impact on users
3. **Execution**
- Requires approval from at least one code owner
- Version bump (major for removals)
- Update CHANGELOG.md with reason
---
## Versioning Policy
Token list versions follow semantic versioning:
- **Major** (x.0.0): Token removals, breaking changes
- **Minor** (x.y.0): Token additions
- **Patch** (x.y.z): Metadata fixes (name, symbol, logo), no address changes
---
## Contact
For questions or concerns about the token list policy:
- Create an issue in the repository
- Contact DBIS team maintainers
---
**Last Updated**: 2025-12-22

View File

@@ -0,0 +1,84 @@
{
"name": "DBIS Chain 138 Token List",
"version": {
"major": 1,
"minor": 1,
"patch": 2
},
"timestamp": "2025-12-24T00:00:00.000Z",
"keywords": [
"dbis",
"chain138",
"defi oracle meta"
],
"logoURI": "https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png",
"tokens": [
{
"chainId": 138,
"address": "0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6",
"name": "ETH/USD Price Feed",
"symbol": "ETH-USD",
"decimals": 8,
"logoURI": "https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png",
"tags": [
"oracle",
"pricefeed"
]
},
{
"chainId": 138,
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"name": "Wrapped Ether",
"symbol": "WETH",
"decimals": 18,
"logoURI": "https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png",
"tags": [
"defi",
"wrapped"
]
},
{
"chainId": 138,
"address": "0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F",
"name": "Wrapped Ether v10",
"symbol": "WETH10",
"decimals": 18,
"logoURI": "https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png",
"tags": [
"defi",
"wrapped"
]
},
{
"chainId": 138,
"address": "0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03",
"name": "Chainlink Token",
"symbol": "LINK",
"decimals": 18,
"logoURI": "https://raw.githubusercontent.com/chainlink/chainlink-docs/main/docs/images/chainlink-logo.svg",
"tags": [
"defi",
"oracle",
"ccip"
]
}
],
"tags": {
"defi": {
"name": "DeFi",
"description": "Decentralized Finance tokens"
},
"wrapped": {
"name": "Wrapped",
"description": "Wrapped tokens representing native assets"
},
"oracle": {
"name": "Oracle",
"description": "Oracle price feed tokens"
},
"pricefeed": {
"name": "Price Feed",
"description": "Price feed oracle contracts"
}
}
}

12
token-lists/minisign.pub Normal file
View File

@@ -0,0 +1,12 @@
# minisign public key
#
# This file will contain the public key for verifying token list signatures.
# To generate a keypair, run:
# ./scripts/sign-list.sh --generate-key
#
# The private key should be stored securely (e.g., in GitHub Secrets)
# and never committed to the repository.
# Placeholder - replace with actual public key after keypair generation
# Format: untrusted comment: <base64 public key>

View File

@@ -0,0 +1,129 @@
#!/usr/bin/env node
/**
* Address Checksum Validator and Fixer
* Validates and optionally fixes EIP-55 checksummed addresses in token lists
*/
import { readFileSync, writeFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
import { ethers } from 'ethers';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
function isChecksummed(address) {
try {
return ethers.isAddress(address) && address === ethers.getAddress(address);
} catch {
return false;
}
}
function checksumAddress(address) {
try {
// Convert to lowercase first if it's not valid, then checksum
return ethers.getAddress(address.toLowerCase());
} catch {
return null;
}
}
function validateAndFixAddresses(filePath, dryRun = true) {
console.log(`\n🔍 ${dryRun ? 'Validating' : 'Fixing'} addresses in: ${filePath}\n`);
// Read token list file
let tokenList;
try {
const fileContent = readFileSync(filePath, 'utf-8');
tokenList = JSON.parse(fileContent);
} catch (error) {
console.error('❌ Error reading or parsing token list file:');
console.error(` ${error.message}`);
process.exit(1);
}
const issues = [];
const fixed = [];
// Check all addresses
if (tokenList.tokens && Array.isArray(tokenList.tokens)) {
tokenList.tokens.forEach((token, index) => {
if (token.address) {
if (!isChecksummed(token.address)) {
const checksummed = checksumAddress(token.address);
if (checksummed) {
issues.push({
index,
token: token.symbol || token.name,
original: token.address,
checksummed,
type: 'non-checksummed'
});
if (!dryRun) {
token.address = checksummed;
fixed.push({
index,
token: token.symbol || token.name,
original: token.address,
fixed: checksummed
});
}
} else {
issues.push({
index,
token: token.symbol || token.name,
original: token.address,
checksummed: null,
type: 'invalid'
});
}
}
}
});
}
// Report results
if (issues.length === 0) {
console.log('✅ All addresses are properly checksummed!\n');
return 0;
}
console.log(`Found ${issues.length} address issue(s):\n`);
issues.forEach(issue => {
if (issue.type === 'invalid') {
console.error(`❌ Token[${issue.index}] (${issue.token}): Invalid address format: ${issue.original}`);
} else {
console.log(`⚠️ Token[${issue.index}] (${issue.token}):`);
console.log(` Original: ${issue.original}`);
console.log(` Checksummed: ${issue.checksummed}`);
}
});
if (!dryRun && fixed.length > 0) {
console.log(`\n✏️ Fixed ${fixed.length} address(es)\n`);
// Write back to file
writeFileSync(filePath, JSON.stringify(tokenList, null, 2) + '\n', 'utf-8');
console.log(`✅ Updated file: ${filePath}\n`);
} else if (dryRun && issues.some(i => i.type !== 'invalid')) {
console.log(`\n💡 Run with --fix to automatically fix checksummed addresses\n`);
}
return issues.some(i => i.type === 'invalid') ? 1 : 0;
}
// Main
const args = process.argv.slice(2);
const filePath = args.find(arg => !arg.startsWith('--')) || resolve(__dirname, '../lists/dbis-138.tokenlist.json');
const dryRun = !args.includes('--fix');
if (!filePath) {
console.error('Usage: node checksum-addresses.js [path/to/token-list.json] [--fix]');
process.exit(1);
}
const exitCode = validateAndFixAddresses(filePath, dryRun);
process.exit(exitCode);

202
token-lists/scripts/release.sh Executable file
View File

@@ -0,0 +1,202 @@
#!/usr/bin/env bash
# Release automation script for token lists
# Handles version bumping, validation, and release preparation
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TOKEN_LISTS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
LISTS_DIR="$TOKEN_LISTS_DIR/lists"
TOKEN_LIST_FILE="$LISTS_DIR/dbis-138.tokenlist.json"
CHANGELOG_FILE="$TOKEN_LISTS_DIR/docs/CHANGELOG.md"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Check if jq is available
if ! command -v jq &> /dev/null; then
log_error "jq is required but not installed"
exit 1
fi
# Check if node is available
if ! command -v node &> /dev/null; then
log_error "node is required but not installed"
exit 1
fi
# Get current version
get_current_version() {
jq -r '.version | "\(.major).\(.minor).\(.patch)"' "$TOKEN_LIST_FILE"
}
# Bump version
bump_version() {
local bump_type="${1:-patch}" # major, minor, patch
local current_major current_minor current_patch
current_major=$(jq -r '.version.major' "$TOKEN_LIST_FILE")
current_minor=$(jq -r '.version.minor' "$TOKEN_LIST_FILE")
current_patch=$(jq -r '.version.patch' "$TOKEN_LIST_FILE")
case "$bump_type" in
major)
((current_major++))
current_minor=0
current_patch=0
;;
minor)
((current_minor++))
current_patch=0
;;
patch)
((current_patch++))
;;
*)
log_error "Invalid bump type: $bump_type (must be major, minor, or patch)"
exit 1
;;
esac
# Update version in JSON
local tmp_file=$(mktemp)
jq --arg major "$current_major" --arg minor "$current_minor" --arg patch "$current_patch" \
'.version.major = ($major | tonumber) | .version.minor = ($minor | tonumber) | .version.patch = ($patch | tonumber)' \
"$TOKEN_LIST_FILE" > "$tmp_file"
mv "$tmp_file" "$TOKEN_LIST_FILE"
echo "$current_major.$current_minor.$current_patch"
}
# Update timestamp
update_timestamp() {
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
local tmp_file=$(mktemp)
jq --arg ts "$timestamp" '.timestamp = $ts' "$TOKEN_LIST_FILE" > "$tmp_file"
mv "$tmp_file" "$TOKEN_LIST_FILE"
log_success "Updated timestamp to: $timestamp"
}
# Run all validations
run_validations() {
log_info "Running validations..."
# JSON schema and basic validation
log_info "Validating token list schema..."
if ! node "$SCRIPT_DIR/validate-token-list.js" "$TOKEN_LIST_FILE"; then
log_error "Schema validation failed"
return 1
fi
# Checksum validation
log_info "Validating address checksums..."
if ! node "$SCRIPT_DIR/checksum-addresses.js" "$TOKEN_LIST_FILE"; then
log_error "Checksum validation failed"
return 1
fi
# Logo validation (non-blocking)
log_info "Validating logos..."
node "$SCRIPT_DIR/validate-logos.js" "$TOKEN_LIST_FILE" || log_warn "Logo validation had issues (continuing)"
log_success "All critical validations passed"
return 0
}
# Generate release notes from CHANGELOG
generate_release_notes() {
local version="$1"
if [[ ! -f "$CHANGELOG_FILE" ]]; then
log_warn "CHANGELOG.md not found, skipping release notes generation"
return
fi
# Extract section for this version
log_info "Extracting release notes for version $version from CHANGELOG.md"
# This is a simple implementation - you might want to use a more sophisticated parser
if grep -q "## \[$version\]" "$CHANGELOG_FILE"; then
log_success "Release notes found in CHANGELOG.md"
else
log_warn "No release notes found for version $version in CHANGELOG.md"
fi
}
# Main release function
main() {
local bump_type="${1:-patch}"
local skip_validation="${2:-}"
log_info "========================================="
log_info "Token List Release Preparation"
log_info "========================================="
log_info ""
# Check if file exists
if [[ ! -f "$TOKEN_LIST_FILE" ]]; then
log_error "Token list file not found: $TOKEN_LIST_FILE"
exit 1
fi
local current_version=$(get_current_version)
log_info "Current version: $current_version"
# Bump version
log_info "Bumping $bump_type version..."
local new_version=$(bump_version "$bump_type")
log_success "New version: $new_version"
# Update timestamp
update_timestamp
# Run validations (unless skipped)
if [[ -z "$skip_validation" ]]; then
if ! run_validations; then
log_error "Validation failed. Release aborted."
log_info "You can fix the issues and run again, or use --skip-validation to bypass"
exit 1
fi
else
log_warn "Skipping validations (--skip-validation flag)"
fi
# Generate release notes
generate_release_notes "$new_version"
log_info ""
log_success "Release preparation complete!"
log_info ""
log_info "Next steps:"
log_info "1. Review changes: git diff $TOKEN_LIST_FILE"
log_info "2. Commit: git add $TOKEN_LIST_FILE"
log_info "3. Create tag: git tag -a v$new_version -m \"Release v$new_version\""
log_info "4. Push: git push && git push --tags"
log_info ""
log_info "Or run the signing script: $SCRIPT_DIR/sign-list.sh"
}
# Parse arguments
if [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then
echo "Usage: $0 [bump-type] [--skip-validation]"
echo ""
echo "Bump types: major, minor, patch (default: patch)"
echo ""
echo "Examples:"
echo " $0 patch # Bump patch version"
echo " $0 minor # Bump minor version"
echo " $0 major # Bump major version"
echo " $0 patch --skip-validation # Skip validation checks"
exit 0
fi
main "${1:-patch}" "${2:-}"

180
token-lists/scripts/sign-list.sh Executable file
View File

@@ -0,0 +1,180 @@
#!/usr/bin/env bash
# minisign signing script for token lists
# Signs token list files for integrity verification
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TOKEN_LISTS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
LISTS_DIR="$TOKEN_LISTS_DIR/lists"
TOKEN_LIST_FILE="$LISTS_DIR/dbis-138.tokenlist.json"
PUBLIC_KEY_FILE="$TOKEN_LISTS_DIR/minisign.pub"
SIGNATURE_FILE="${TOKEN_LIST_FILE}.sig"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Check if minisign is available
if ! command -v minisign &> /dev/null; then
log_error "minisign is required but not installed"
log_info "Installation:"
log_info " macOS: brew install minisign"
log_info " Ubuntu/Debian: apt-get install minisign"
log_info " From source: https://github.com/jedisct1/minisign"
exit 1
fi
# Generate keypair (only if keys don't exist)
generate_keypair() {
local private_key_file="${MINISIGN_PRIVATE_KEY_FILE:-$TOKEN_LISTS_DIR/minisign.key}"
if [[ -f "$private_key_file" ]]; then
log_warn "Private key already exists: $private_key_file"
log_info "Skipping key generation"
return 0
fi
log_info "Generating minisign keypair..."
log_info "Private key will be saved to: $private_key_file"
log_info "Public key will be saved to: $PUBLIC_KEY_FILE"
log_warn "Keep the private key secure and never commit it to the repository!"
# Generate keypair (minisign will prompt for password)
if minisign -G -s "$private_key_file" -p "$PUBLIC_KEY_FILE"; then
log_success "Keypair generated successfully"
log_info ""
log_info "Next steps:"
log_info "1. Store the private key securely (e.g., password manager, secure vault)"
log_info "2. Add private key to GitHub Secrets as MINISIGN_PRIVATE_KEY"
log_info "3. Commit the public key: git add $PUBLIC_KEY_FILE"
log_info "4. Set MINISIGN_PRIVATE_KEY_FILE environment variable if using custom path"
else
log_error "Failed to generate keypair"
exit 1
fi
}
# Sign token list
sign_list() {
local private_key_file="${MINISIGN_PRIVATE_KEY_FILE:-$TOKEN_LISTS_DIR/minisign.key}"
local private_key_content="${MINISIGN_PRIVATE_KEY:-}"
if [[ ! -f "$TOKEN_LIST_FILE" ]]; then
log_error "Token list file not found: $TOKEN_LIST_FILE"
exit 1
fi
log_info "Signing token list: $TOKEN_LIST_FILE"
# Check if private key exists or is provided via environment
if [[ -n "$private_key_content" ]]; then
# Use private key from environment variable
log_info "Using private key from MINISIGN_PRIVATE_KEY environment variable"
echo "$private_key_content" | minisign -S -s /dev/stdin -m "$TOKEN_LIST_FILE" -x "$SIGNATURE_FILE" || {
log_error "Failed to sign token list"
exit 1
}
elif [[ -f "$private_key_file" ]]; then
# Use private key file
minisign -S -s "$private_key_file" -m "$TOKEN_LIST_FILE" -x "$SIGNATURE_FILE" || {
log_error "Failed to sign token list"
exit 1
}
else
log_error "Private key not found"
log_info "Provide private key via:"
log_info " 1. File: Set MINISIGN_PRIVATE_KEY_FILE environment variable"
log_info " 2. Environment: Set MINISIGN_PRIVATE_KEY environment variable"
log_info " 3. Generate new: Run '$0 --generate-key'"
exit 1
fi
log_success "Token list signed successfully"
log_info "Signature file: $SIGNATURE_FILE"
# Display signature info
if [[ -f "$SIGNATURE_FILE" ]]; then
log_info ""
log_info "Signature preview:"
head -n 2 "$SIGNATURE_FILE" | head -c 100
echo "..."
log_info ""
fi
}
# Verify signature
verify_signature() {
if [[ ! -f "$TOKEN_LIST_FILE" ]]; then
log_error "Token list file not found: $TOKEN_LIST_FILE"
exit 1
fi
if [[ ! -f "$SIGNATURE_FILE" ]]; then
log_error "Signature file not found: $SIGNATURE_FILE"
exit 1
fi
if [[ ! -f "$PUBLIC_KEY_FILE" ]]; then
log_error "Public key file not found: $PUBLIC_KEY_FILE"
log_info "Public key should be at: $PUBLIC_KEY_FILE"
exit 1
fi
log_info "Verifying signature..."
if minisign -V -p "$PUBLIC_KEY_FILE" -m "$TOKEN_LIST_FILE" -x "$SIGNATURE_FILE"; then
log_success "Signature verification passed!"
return 0
else
log_error "Signature verification failed!"
return 1
fi
}
# Main
main() {
local command="${1:-sign}"
case "$command" in
--generate-key|-g)
generate_keypair
;;
--sign|-s)
sign_list
;;
--verify|-v)
verify_signature
;;
sign)
sign_list
;;
verify)
verify_signature
;;
*)
echo "Usage: $0 [command]"
echo ""
echo "Commands:"
echo " sign, -s Sign the token list (default)"
echo " verify, -v Verify the signature"
echo " --generate-key, -g Generate a new keypair"
echo ""
echo "Environment variables:"
echo " MINISIGN_PRIVATE_KEY_FILE Path to private key file"
echo " MINISIGN_PRIVATE_KEY Private key content (for CI/CD)"
exit 1
;;
esac
}
main "${1:-sign}"

View File

@@ -0,0 +1,176 @@
#!/usr/bin/env node
/**
* Validates chain configuration for Chainlists submission
*/
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const REQUIRED_FIELDS = [
'name',
'chain',
'chainId',
'networkId',
'rpc',
'nativeCurrency'
];
function validateChainConfig(filePath) {
console.log(`\n🔍 Validating chain configuration: ${filePath}\n`);
let config;
try {
const fileContent = readFileSync(filePath, 'utf-8');
config = JSON.parse(fileContent);
} catch (error) {
console.error('❌ Error reading or parsing chain config file:');
console.error(` ${error.message}`);
process.exit(1);
}
const errors = [];
const warnings = [];
// Validate required fields
REQUIRED_FIELDS.forEach(field => {
if (!(field in config)) {
errors.push(`Missing required field: ${field}`);
}
});
// Validate chainId
if (config.chainId !== 138) {
errors.push(`chainId must be 138, got ${config.chainId}`);
}
// Validate networkId
if (config.networkId && config.networkId !== 138) {
warnings.push(`networkId should match chainId (138), got ${config.networkId}`);
}
// Validate RPC URLs
if (!Array.isArray(config.rpc) || config.rpc.length === 0) {
errors.push('rpc must be a non-empty array');
} else {
config.rpc.forEach((url, index) => {
if (typeof url !== 'string' || (!url.startsWith('http://') && !url.startsWith('https://'))) {
errors.push(`rpc[${index}] must be a valid HTTP/HTTPS URL`);
}
if (url.startsWith('http://')) {
warnings.push(`rpc[${index}] should use HTTPS, not HTTP: ${url}`);
}
});
}
// Validate nativeCurrency
if (config.nativeCurrency) {
if (!config.nativeCurrency.symbol) {
errors.push('nativeCurrency.symbol is required');
}
if (typeof config.nativeCurrency.decimals !== 'number') {
errors.push('nativeCurrency.decimals must be a number');
}
if (config.nativeCurrency.symbol !== 'ETH') {
warnings.push(`Expected nativeCurrency.symbol to be "ETH", got "${config.nativeCurrency.symbol}"`);
}
if (config.nativeCurrency.decimals !== 18) {
warnings.push(`Expected nativeCurrency.decimals to be 18, got ${config.nativeCurrency.decimals}`);
}
}
// Validate explorers (optional but recommended)
if (config.explorers && Array.isArray(config.explorers)) {
config.explorers.forEach((explorer, index) => {
if (!explorer.url) {
errors.push(`explorers[${index}].url is required`);
}
if (explorer.url && !explorer.url.startsWith('https://')) {
warnings.push(`explorers[${index}].url should use HTTPS: ${explorer.url}`);
}
if (!explorer.name) {
warnings.push(`explorers[${index}].name is recommended`);
}
if (!explorer.standard) {
warnings.push(`explorers[${index}].standard is recommended (e.g., "EIP3091")`);
}
});
} else {
warnings.push('No explorers configured (recommended for better UX)');
}
// Validate shortName
if (config.shortName && typeof config.shortName !== 'string') {
errors.push('shortName must be a string');
} else if (!config.shortName) {
warnings.push('shortName is recommended');
}
// Validate icon (optional)
if (config.icon && !config.icon.startsWith('https://') && !config.icon.startsWith('ipfs://')) {
warnings.push(`icon should use HTTPS or IPFS URL: ${config.icon}`);
}
// Report results
if (errors.length > 0) {
console.error('❌ Validation failed!\n');
console.error('Errors:');
errors.forEach(error => console.error(`${error}`));
console.log('');
if (warnings.length > 0) {
console.log('⚠️ Warnings:');
warnings.forEach(warning => console.log(` ⚠️ ${warning}`));
console.log('');
}
process.exit(1);
}
console.log('✅ Chain configuration is valid!\n');
if (warnings.length > 0) {
console.log('⚠️ Warnings:');
warnings.forEach(warning => console.log(` ⚠️ ${warning}`));
console.log('');
}
console.log('📋 Configuration Summary:');
console.log(` Name: ${config.name}`);
console.log(` Chain: ${config.chain}`);
console.log(` Short Name: ${config.shortName || '(not set)'}`);
console.log(` Chain ID: ${config.chainId}`);
console.log(` Network ID: ${config.networkId || '(not set)'}`);
console.log(` RPC URLs: ${config.rpc.length}`);
config.rpc.forEach((url, i) => console.log(` ${i + 1}. ${url}`));
if (config.explorers && config.explorers.length > 0) {
console.log(` Explorers: ${config.explorers.length}`);
config.explorers.forEach((exp, i) => {
console.log(` ${i + 1}. ${exp.name || '(unnamed)'}: ${exp.url}`);
if (exp.standard) {
console.log(` Standard: ${exp.standard}`);
}
});
}
if (config.nativeCurrency) {
console.log(` Native Currency: ${config.nativeCurrency.symbol} (${config.nativeCurrency.decimals} decimals)`);
}
if (config.infoURL) {
console.log(` Info URL: ${config.infoURL}`);
}
console.log('');
process.exit(0);
}
// Main
const filePath = process.argv[2] || resolve(__dirname, '../chainlists/chain-138.json');
if (!filePath) {
console.error('Usage: node validate-chainlists.js [path/to/chain.json]');
process.exit(1);
}
validateChainConfig(filePath);

View File

@@ -0,0 +1,135 @@
#!/usr/bin/env node
/**
* Logo URL Validator
* Validates that all logoURI URLs are accessible and return image content
*/
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const MAX_LOGO_SIZE = 500 * 1024; // 500KB
const IMAGE_MIME_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/svg+xml', 'image/webp', 'image/gif'];
async function validateLogo(logoURI, tokenInfo) {
const issues = [];
// Check protocol
if (!logoURI.startsWith('https://') && !logoURI.startsWith('ipfs://')) {
issues.push(`URL should use HTTPS or IPFS (got: ${logoURI.substring(0, 20)}...)`);
}
// For HTTPS URLs, validate accessibility
if (logoURI.startsWith('https://')) {
try {
const response = await fetch(logoURI, { method: 'HEAD' });
if (!response.ok) {
issues.push(`HTTP ${response.status}: ${response.statusText}`);
} else {
const contentType = response.headers.get('content-type');
const contentLength = response.headers.get('content-length');
if (contentType && !IMAGE_MIME_TYPES.some(mime => contentType.includes(mime))) {
issues.push(`Invalid Content-Type: ${contentType} (expected image/*)`);
}
if (contentLength && parseInt(contentLength) > MAX_LOGO_SIZE) {
issues.push(`Logo too large: ${(parseInt(contentLength) / 1024).toFixed(2)}KB (max ${MAX_LOGO_SIZE / 1024}KB)`);
}
}
} catch (error) {
issues.push(`Failed to fetch: ${error.message}`);
}
} else if (logoURI.startsWith('ipfs://')) {
// IPFS URLs are valid but we can't easily validate them
// Just check format
if (!logoURI.match(/^ipfs:\/\/[a-zA-Z0-9]+/)) {
issues.push('Invalid IPFS URL format');
}
}
return issues;
}
async function validateLogos(filePath) {
console.log(`\n🖼️ Validating logos in: ${filePath}\n`);
// Read token list file
let tokenList;
try {
const fileContent = readFileSync(filePath, 'utf-8');
tokenList = JSON.parse(fileContent);
} catch (error) {
console.error('❌ Error reading or parsing token list file:');
console.error(` ${error.message}`);
process.exit(1);
}
const results = [];
let totalIssues = 0;
// Validate top-level logoURI
if (tokenList.logoURI) {
console.log('Validating list logoURI...');
const issues = await validateLogo(tokenList.logoURI, 'List');
if (issues.length > 0) {
results.push({ type: 'list', uri: tokenList.logoURI, issues });
totalIssues += issues.length;
}
}
// Validate token logos
if (tokenList.tokens && Array.isArray(tokenList.tokens)) {
for (const [index, token] of tokenList.tokens.entries()) {
if (token.logoURI) {
const tokenInfo = `${token.symbol || token.name} (Token[${index}])`;
const issues = await validateLogo(token.logoURI, tokenInfo);
if (issues.length > 0) {
results.push({ type: 'token', token: tokenInfo, uri: token.logoURI, issues });
totalIssues += issues.length;
}
}
}
}
// Report results
if (totalIssues === 0) {
console.log('✅ All logos are valid!\n');
return 0;
}
console.log(`Found ${totalIssues} logo issue(s):\n`);
results.forEach(result => {
if (result.type === 'list') {
console.log(`❌ List logoURI: ${result.uri}`);
} else {
console.log(`${result.token}: ${result.uri}`);
}
result.issues.forEach(issue => {
console.log(` - ${issue}`);
});
console.log('');
});
return 1;
}
// Main
const filePath = process.argv[2] || resolve(__dirname, '../lists/dbis-138.tokenlist.json');
if (!filePath) {
console.error('Usage: node validate-logos.js [path/to/token-list.json]');
process.exit(1);
}
validateLogos(filePath).then(exitCode => {
process.exit(exitCode);
}).catch(error => {
console.error('Unexpected error:', error);
process.exit(1);
});

View File

@@ -0,0 +1,287 @@
#!/usr/bin/env node
/**
* Enhanced Token List Validator
* Validates token lists against the Uniswap Token Lists JSON schema
* Based on: https://github.com/Uniswap/token-lists
* Schema: https://uniswap.org/tokenlist.schema.json
*
* Enhanced with:
* - EIP-55 checksum validation
* - Duplicate detection
* - Logo URL validation
* - Chain ID strict validation
* - Semantic versioning validation
*/
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
import { ethers } from 'ethers';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Required chain ID
const REQUIRED_CHAIN_ID = 138;
// Fetch schema from Uniswap
const SCHEMA_URL = 'https://uniswap.org/tokenlist.schema.json';
async function fetchSchema() {
try {
const response = await fetch(SCHEMA_URL);
if (!response.ok) {
throw new Error(`Failed to fetch schema: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Error fetching schema:', error.message);
console.error('Falling back to basic validation...');
return null;
}
}
// Validate EIP-55 checksum
function isChecksummed(address) {
try {
return ethers.isAddress(address) && address === ethers.getAddress(address);
} catch {
return false;
}
}
// Basic validation with enhanced checks
function enhancedValidation(tokenList) {
const errors = [];
const warnings = [];
const seenAddresses = new Set();
const seenSymbols = new Map(); // chainId -> Set of symbols
// Required fields
if (!tokenList.name || typeof tokenList.name !== 'string') {
errors.push('Missing or invalid "name" field');
}
if (!tokenList.version) {
errors.push('Missing "version" field');
} else {
if (typeof tokenList.version.major !== 'number') {
errors.push('version.major must be a number');
}
if (typeof tokenList.version.minor !== 'number') {
errors.push('version.minor must be a number');
}
if (typeof tokenList.version.patch !== 'number') {
errors.push('version.patch must be a number');
}
}
if (!tokenList.tokens || !Array.isArray(tokenList.tokens)) {
errors.push('Missing or invalid "tokens" array');
return { errors, warnings, valid: false };
}
// Validate each token
tokenList.tokens.forEach((token, index) => {
const prefix = `Token[${index}]`;
// Required token fields
if (typeof token.chainId !== 'number') {
errors.push(`${prefix}: Missing or invalid "chainId"`);
} else {
// Strict chain ID validation
if (token.chainId !== REQUIRED_CHAIN_ID) {
errors.push(`${prefix}: chainId must be ${REQUIRED_CHAIN_ID}, got ${token.chainId}`);
}
}
if (!token.address || typeof token.address !== 'string') {
errors.push(`${prefix}: Missing or invalid "address"`);
} else {
// Validate Ethereum address format
if (!/^0x[a-fA-F0-9]{40}$/.test(token.address)) {
errors.push(`${prefix}: Invalid Ethereum address format: ${token.address}`);
} else {
// EIP-55 checksum validation
if (!isChecksummed(token.address)) {
errors.push(`${prefix}: Address not EIP-55 checksummed: ${token.address}`);
}
// Duplicate address detection
const addressLower = token.address.toLowerCase();
if (seenAddresses.has(addressLower)) {
errors.push(`${prefix}: Duplicate address: ${token.address}`);
}
seenAddresses.add(addressLower);
}
}
if (!token.name || typeof token.name !== 'string') {
errors.push(`${prefix}: Missing or invalid "name"`);
}
if (!token.symbol || typeof token.symbol !== 'string') {
errors.push(`${prefix}: Missing or invalid "symbol"`);
} else {
// Symbol uniqueness per chainId
const chainId = token.chainId || 0;
if (!seenSymbols.has(chainId)) {
seenSymbols.set(chainId, new Set());
}
const symbolSet = seenSymbols.get(chainId);
if (symbolSet.has(token.symbol.toUpperCase())) {
warnings.push(`${prefix}: Duplicate symbol "${token.symbol}" on chainId ${chainId}`);
}
symbolSet.add(token.symbol.toUpperCase());
}
if (typeof token.decimals !== 'number' || token.decimals < 0 || token.decimals > 255) {
errors.push(`${prefix}: Invalid "decimals" (must be 0-255), got ${token.decimals}`);
}
// Optional fields (warnings)
if (!token.logoURI) {
warnings.push(`${prefix}: Missing "logoURI" (optional but recommended)`);
} else if (typeof token.logoURI !== 'string') {
warnings.push(`${prefix}: Invalid "logoURI" type`);
} else if (!token.logoURI.startsWith('http://') &&
!token.logoURI.startsWith('https://') &&
!token.logoURI.startsWith('ipfs://')) {
warnings.push(`${prefix}: Invalid "logoURI" format (should be HTTP/HTTPS/IPFS URL): ${token.logoURI}`);
} else if (!token.logoURI.startsWith('https://') && !token.logoURI.startsWith('ipfs://')) {
warnings.push(`${prefix}: logoURI should use HTTPS (not HTTP): ${token.logoURI}`);
}
});
return { errors, warnings, valid: errors.length === 0 };
}
async function validateTokenList(filePath) {
console.log(`\n🔍 Validating token list: ${filePath}\n`);
// Read token list file
let tokenList;
try {
const fileContent = readFileSync(filePath, 'utf-8');
tokenList = JSON.parse(fileContent);
} catch (error) {
console.error('❌ Error reading or parsing token list file:');
console.error(` ${error.message}`);
process.exit(1);
}
// Try to fetch and use Uniswap schema
const schema = await fetchSchema();
let validationResult;
if (schema) {
// Use AJV if available, otherwise fall back to enhanced validation
try {
// Try to use dynamic import for ajv (if installed)
const { default: Ajv } = await import('ajv');
const addFormats = (await import('ajv-formats')).default;
const ajv = new Ajv({ allErrors: true, verbose: true });
addFormats(ajv);
const validate = ajv.compile(schema);
const valid = validate(tokenList);
if (valid) {
// Schema validation passed, now run enhanced checks
validationResult = enhancedValidation(tokenList);
} else {
const schemaErrors = validate.errors?.map(err => {
const path = err.instancePath || err.schemaPath || '';
return `${path}: ${err.message}`;
}) || [];
const enhanced = enhancedValidation(tokenList);
validationResult = {
errors: [...schemaErrors, ...enhanced.errors],
warnings: enhanced.warnings,
valid: false
};
}
} catch (importError) {
// AJV not available, use enhanced validation
console.log('⚠️ AJV not available, using enhanced validation');
validationResult = enhancedValidation(tokenList);
}
} else {
// Schema fetch failed, use enhanced validation
validationResult = enhancedValidation(tokenList);
}
// Display results
if (validationResult.valid) {
console.log('✅ Token list is valid!\n');
// Display token list info
console.log('📋 Token List Info:');
console.log(` Name: ${tokenList.name}`);
if (tokenList.version) {
console.log(` Version: ${tokenList.version.major}.${tokenList.version.minor}.${tokenList.version.patch}`);
}
if (tokenList.timestamp) {
console.log(` Timestamp: ${tokenList.timestamp}`);
}
console.log(` Tokens: ${tokenList.tokens.length}`);
console.log('');
// List tokens
console.log('🪙 Tokens:');
tokenList.tokens.forEach((token, index) => {
console.log(` ${index + 1}. ${token.symbol} (${token.name})`);
console.log(` Address: ${token.address}`);
console.log(` Chain ID: ${token.chainId}`);
console.log(` Decimals: ${token.decimals}`);
if (token.logoURI) {
console.log(` Logo: ${token.logoURI}`);
}
console.log('');
});
if (validationResult.warnings.length > 0) {
console.log('⚠️ Warnings:');
validationResult.warnings.forEach(warning => {
console.log(` - ${warning}`);
});
console.log('');
}
process.exit(0);
} else {
console.error('❌ Token list validation failed!\n');
if (validationResult.errors.length > 0) {
console.error('Errors:');
validationResult.errors.forEach(error => {
console.error(`${error}`);
});
console.log('');
}
if (validationResult.warnings.length > 0) {
console.log('Warnings:');
validationResult.warnings.forEach(warning => {
console.log(` ⚠️ ${warning}`);
});
console.log('');
}
process.exit(1);
}
}
// Main
const filePath = process.argv[2] || resolve(__dirname, '../lists/dbis-138.tokenlist.json');
if (!filePath) {
console.error('Usage: node validate-token-list.js [path/to/token-list.json]');
process.exit(1);
}
validateTokenList(filePath).catch(error => {
console.error('Unexpected error:', error);
process.exit(1);
});

View File

@@ -0,0 +1,284 @@
#!/usr/bin/env node
/**
* On-Chain Verification Script
* Verifies token list entries against on-chain contracts using RPC calls
*
* RPC endpoints (fallback order):
* 1. https://rpc-http-pub.d-bis.org (primary)
* 2. https://rpc-core.d-bis.org (fallback)
*/
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
import { ethers } from 'ethers';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const RPC_ENDPOINTS = [
'https://rpc-http-pub.d-bis.org',
'https://rpc-core.d-bis.org'
];
const REQUIRED_CHAIN_ID = 138;
const REQUIRED_CHAIN_ID_HEX = '0x8a';
// ERC-20 ABI (minimal)
const ERC20_ABI = [
'function decimals() view returns (uint8)',
'function symbol() view returns (string)',
'function name() view returns (string)',
'function totalSupply() view returns (uint256)'
];
// Oracle ABI (Chainlink-compatible)
const ORACLE_ABI = [
'function latestRoundData() view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)',
'function decimals() view returns (uint8)',
'function description() view returns (string)'
];
async function getProvider() {
let lastError;
for (const rpcUrl of RPC_ENDPOINTS) {
try {
const provider = new ethers.JsonRpcProvider(rpcUrl);
// Verify chain ID
const network = await provider.getNetwork();
const chainId = Number(network.chainId);
if (chainId !== REQUIRED_CHAIN_ID) {
throw new Error(`Chain ID mismatch: expected ${REQUIRED_CHAIN_ID}, got ${chainId}`);
}
// Test connection
await provider.getBlockNumber();
console.log(`✅ Connected to RPC: ${rpcUrl} (Chain ID: ${chainId})\n`);
return provider;
} catch (error) {
lastError = error;
console.log(`⚠️ Failed to connect to ${rpcUrl}: ${error.message}`);
continue;
}
}
throw new Error(`Failed to connect to any RPC endpoint. Last error: ${lastError?.message}`);
}
async function verifyERC20Token(provider, token, index) {
const results = [];
const prefix = `Token[${index}] ${token.symbol || token.name}`;
try {
// Check if contract exists
const code = await provider.getCode(token.address);
if (code === '0x') {
results.push({ type: 'error', message: `${prefix}: No contract code at address ${token.address}` });
return results;
}
const contract = new ethers.Contract(token.address, ERC20_ABI, provider);
// Verify decimals
try {
const onChainDecimals = await contract.decimals();
if (Number(onChainDecimals) !== token.decimals) {
results.push({
type: 'error',
message: `${prefix}: Decimals mismatch - list: ${token.decimals}, on-chain: ${onChainDecimals}`
});
} else {
results.push({ type: 'success', message: `${prefix}: Decimals verified (${token.decimals})` });
}
} catch (error) {
results.push({ type: 'warning', message: `${prefix}: Failed to read decimals: ${error.message}` });
}
// Verify symbol (warn if different)
try {
const onChainSymbol = await contract.symbol();
if (onChainSymbol !== token.symbol) {
results.push({
type: 'warning',
message: `${prefix}: Symbol mismatch - list: "${token.symbol}", on-chain: "${onChainSymbol}"`
});
}
} catch (error) {
results.push({ type: 'warning', message: `${prefix}: Failed to read symbol: ${error.message}` });
}
// Verify name (warn if different)
try {
const onChainName = await contract.name();
if (onChainName !== token.name) {
results.push({
type: 'warning',
message: `${prefix}: Name mismatch - list: "${token.name}", on-chain: "${onChainName}"`
});
}
} catch (error) {
results.push({ type: 'warning', message: `${prefix}: Failed to read name: ${error.message}` });
}
// Verify totalSupply (optional)
try {
await contract.totalSupply();
results.push({ type: 'success', message: `${prefix}: totalSupply() callable` });
} catch (error) {
results.push({ type: 'warning', message: `${prefix}: totalSupply() failed: ${error.message}` });
}
} catch (error) {
results.push({ type: 'error', message: `${prefix}: Verification failed: ${error.message}` });
}
return results;
}
async function verifyOracleToken(provider, token, index) {
const results = [];
const prefix = `Token[${index}] ${token.symbol || token.name} (Oracle)`;
try {
// Check if contract exists
const code = await provider.getCode(token.address);
if (code === '0x') {
results.push({ type: 'error', message: `${prefix}: No contract code at address ${token.address}` });
return results;
}
const contract = new ethers.Contract(token.address, ORACLE_ABI, provider);
// Verify latestRoundData
try {
await contract.latestRoundData();
results.push({ type: 'success', message: `${prefix}: latestRoundData() callable` });
} catch (error) {
results.push({ type: 'error', message: `${prefix}: latestRoundData() failed: ${error.message}` });
}
// Verify decimals
try {
const onChainDecimals = await contract.decimals();
if (Number(onChainDecimals) !== token.decimals) {
results.push({
type: 'error',
message: `${prefix}: Decimals mismatch - list: ${token.decimals}, on-chain: ${onChainDecimals}`
});
} else {
results.push({ type: 'success', message: `${prefix}: Decimals verified (${token.decimals})` });
}
} catch (error) {
results.push({ type: 'warning', message: `${prefix}: Failed to read decimals: ${error.message}` });
}
} catch (error) {
results.push({ type: 'error', message: `${prefix}: Verification failed: ${error.message}` });
}
return results;
}
function isOracleToken(token) {
return token.tags && (token.tags.includes('oracle') || token.tags.includes('pricefeed'));
}
async function verifyOnChain(filePath, required = false) {
console.log(`\n🔗 Verifying on-chain contracts: ${filePath}\n`);
// Read token list file
let tokenList;
try {
const fileContent = readFileSync(filePath, 'utf-8');
tokenList = JSON.parse(fileContent);
} catch (error) {
console.error('❌ Error reading or parsing token list file:');
console.error(` ${error.message}`);
process.exit(1);
}
let provider;
try {
provider = await getProvider();
} catch (error) {
if (required) {
console.error(`${error.message}`);
console.error('On-chain verification is required but RPC connection failed.');
process.exit(1);
} else {
console.log(`⚠️ ${error.message}`);
console.log('Skipping on-chain verification (optional mode)\n');
return 0;
}
}
if (!tokenList.tokens || !Array.isArray(tokenList.tokens)) {
console.log('No tokens to verify.\n');
return 0;
}
const allResults = [];
for (const [index, token] of tokenList.tokens.entries()) {
let results;
if (isOracleToken(token)) {
results = await verifyOracleToken(provider, token, index);
} else {
results = await verifyERC20Token(provider, token, index);
}
allResults.push(...results);
}
// Report results
const errors = allResults.filter(r => r.type === 'error');
const warnings = allResults.filter(r => r.type === 'warning');
const successes = allResults.filter(r => r.type === 'success');
if (errors.length > 0) {
console.log('❌ Errors:');
errors.forEach(r => console.log(` ${r.message}`));
console.log('');
}
if (warnings.length > 0) {
console.log('⚠️ Warnings:');
warnings.forEach(r => console.log(` ${r.message}`));
console.log('');
}
if (successes.length > 0) {
console.log('✅ Verified:');
successes.forEach(r => console.log(` ${r.message}`));
console.log('');
}
if (errors.length > 0) {
console.log(`❌ Verification failed with ${errors.length} error(s)\n`);
return 1;
}
console.log('✅ All on-chain verifications passed!\n');
return 0;
}
// Main
const args = process.argv.slice(2);
const filePath = args.find(arg => !arg.startsWith('--')) || resolve(__dirname, '../lists/dbis-138.tokenlist.json');
const required = args.includes('--required');
if (!filePath) {
console.error('Usage: node verify-on-chain.js [path/to/token-list.json] [--required]');
process.exit(1);
}
verifyOnChain(filePath, required).then(exitCode => {
process.exit(exitCode);
}).catch(error => {
console.error('Unexpected error:', error);
process.exit(1);
});