diff --git a/.github/SECRETS.md b/.github/SECRETS.md new file mode 100644 index 0000000..3759ac4 --- /dev/null +++ b/.github/SECRETS.md @@ -0,0 +1,35 @@ +# GitHub Actions — Secrets necesarios + +Configurá estos secrets en **Settings → Secrets and variables → Actions** de tu repo. + +## Secrets requeridos + +| Secret | Descripción | Usado en | +|--------|-------------|----------| +| `SEPOLIA_RPC_URL` | URL de Alchemy/Infura para Sepolia | `deploy-sepolia.yml` | +| `DEPLOYER_PRIVATE_KEY` | Clave privada del wallet deployer (sin `0x`) | `deploy-sepolia.yml` | +| `ETHERSCAN_API_KEY` | API key de Etherscan para verificar contratos | `deploy-sepolia.yml`, `contracts-ci.yml` | +| `VITE_WALLETCONNECT_PROJECT_ID` | Project ID de WalletConnect Cloud | `frontend-ci.yml` | + +## Secrets opcionales + +| Secret | Descripción | Usado en | +|--------|-------------|----------| +| `VITE_SEPOLIA_RPC_URL` | RPC para el frontend (puede ser público) | `frontend-ci.yml` | +| `POLYGONSCAN_API_KEY` | API key de Polygonscan | futuro | + +## Cómo obtenerlos + +- **SEPOLIA_RPC_URL**: [Alchemy](https://alchemy.com) → Create App → Ethereum Sepolia → `https://eth-sepolia.g.alchemy.com/v2/TU_KEY` +- **DEPLOYER_PRIVATE_KEY**: MetaMask → Account details → Export private key (usá una wallet dedicada solo a deploy, nunca tu wallet principal) +- **ETHERSCAN_API_KEY**: [etherscan.io/myapikey](https://etherscan.io/myapikey) +- **VITE_WALLETCONNECT_PROJECT_ID**: [cloud.walletconnect.com](https://cloud.walletconnect.com) + +## Environment "sepolia" + +El workflow `deploy-sepolia.yml` usa el environment `sepolia`. Crealo en: +**Settings → Environments → New environment → `sepolia`** + +Podés configurar "Required reviewers" para que el deploy requiera aprobación manual tuya antes de ejecutarse. + +> ⚠️ NUNCA commitees tu `PRIVATE_KEY` directamente en ningún archivo del repo. diff --git a/.github/workflows/contracts-ci.yml b/.github/workflows/contracts-ci.yml new file mode 100644 index 0000000..6f4aaec --- /dev/null +++ b/.github/workflows/contracts-ci.yml @@ -0,0 +1,127 @@ +name: Contracts CI + +on: + push: + branches: [master, main, develop] + paths: + - 'contracts/**' + - 'test/**' + - 'scripts/**' + - 'hardhat.config.js' + - 'package.json' + pull_request: + branches: [master, main, develop] + paths: + - 'contracts/**' + - 'test/**' + - 'scripts/**' + - 'hardhat.config.js' + - 'package.json' + +jobs: + # ───────────────────────────────────────────── + # 1. Compile + Test (Hardhat local network) + # ───────────────────────────────────────────── + test: + name: Compile & Test Contracts + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Compile contracts + run: npm run compile + + - name: Run tests + run: npm test + env: + # No RPC key needed — runs on Hardhat local node + REPORT_GAS: true + + - name: Upload artifacts (ABIs) + uses: actions/upload-artifact@v4 + if: success() + with: + name: contract-artifacts + path: artifacts/ + retention-days: 7 + + # ───────────────────────────────────────────── + # 2. Coverage report + # ───────────────────────────────────────────── + coverage: + name: Solidity Coverage + runs-on: ubuntu-latest + needs: test + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run coverage + run: npm run coverage + + - name: Upload coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage/ + retention-days: 14 + + # ───────────────────────────────────────────── + # 3. Static analysis with Slither (security) + # ───────────────────────────────────────────── + slither: + name: Slither Static Analysis + runs-on: ubuntu-latest + needs: test + continue-on-error: true # No bloquea el merge mientras ajustamos findings + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Compile contracts (needed for Slither) + run: npm run compile + + - name: Run Slither + uses: crytic/slither-action@v0.4.0 + with: + target: 'contracts/' + slither-args: '--exclude-dependencies' + sarif: results.sarif + fail-on: high + + - name: Upload SARIF to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: results.sarif diff --git a/.github/workflows/deploy-sepolia.yml b/.github/workflows/deploy-sepolia.yml new file mode 100644 index 0000000..0c39f8e --- /dev/null +++ b/.github/workflows/deploy-sepolia.yml @@ -0,0 +1,86 @@ +name: Deploy to Sepolia + +# Solo se dispara de forma MANUAL desde la pestaña Actions +# (workflow_dispatch) o cuando pusheás un tag con formato v*.*.* +on: + workflow_dispatch: + inputs: + contract: + description: 'Contrato a deployar' + required: true + default: 'all' + type: choice + options: + - all + - AdvancedToken + - NFTCollection + - NFTMarketplace + - MinerToken + - CSUITEFaucet + - GasAccumulator + dry_run: + description: 'Dry run (solo simula, no deploya)' + required: false + default: false + type: boolean + + push: + tags: + - 'v*.*.*' # Ej: v1.0.0 → deploy automático a Sepolia + +jobs: + deploy: + name: Deploy Contracts → Sepolia + runs-on: ubuntu-latest + environment: sepolia # Requiere aprobación manual si configurás "Required reviewers" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Compile contracts + run: npm run compile + + - name: Run tests before deploy + run: npm test + + - name: Deploy to Sepolia + if: ${{ github.event.inputs.dry_run != 'true' }} + run: npm run deploy:sepolia + env: + SEPOLIA_RPC_URL: ${{ secrets.SEPOLIA_RPC_URL }} + PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY }} + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + + - name: Dry run info + if: ${{ github.event.inputs.dry_run == 'true' }} + run: echo "DRY RUN — Deploy simulado correctamente. No se ejecutó ninguna tx." + + - name: Verify contracts on Etherscan + if: ${{ github.event.inputs.dry_run != 'true' }} + run: | + echo "Verificando contratos en Etherscan..." + # Descomentar cuando tengas las addresses desplegadas: + # npx hardhat verify --network sepolia $CONTRACT_ADDRESS + env: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + continue-on-error: true + + - name: Upload deployment artifacts + uses: actions/upload-artifact@v4 + if: always() + with: + name: deployment-${{ github.sha }} + path: | + artifacts/ + deployments/ + retention-days: 30 diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml new file mode 100644 index 0000000..4347679 --- /dev/null +++ b/.github/workflows/frontend-ci.yml @@ -0,0 +1,81 @@ +name: Frontend CI + +on: + push: + branches: [master, main, develop] + paths: + - 'frontend/**' + pull_request: + branches: [master, main, develop] + paths: + - 'frontend/**' + +jobs: + # ───────────────────────────────────────────── + # 1. Build check + # ───────────────────────────────────────────── + build: + name: Vite Build + runs-on: ubuntu-latest + + defaults: + run: + working-directory: frontend + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package.json + + - name: Install dependencies + run: npm install + + - name: Build + run: npm run build + env: + # Variables de entorno mínimas para que Vite compile sin errores + VITE_WALLETCONNECT_PROJECT_ID: ${{ secrets.VITE_WALLETCONNECT_PROJECT_ID }} + VITE_SEPOLIA_RPC_URL: ${{ secrets.VITE_SEPOLIA_RPC_URL || 'https://rpc.sepolia.org' }} + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: frontend-dist + path: frontend/dist/ + retention-days: 7 + + # ───────────────────────────────────────────── + # 2. Lint (ESLint) — corre en paralelo con build + # ───────────────────────────────────────────── + lint: + name: ESLint + runs-on: ubuntu-latest + + defaults: + run: + working-directory: frontend + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package.json + + - name: Install dependencies + run: npm install + + # Si aún no tenés ESLint config, este step falla silenciosamente + - name: Run ESLint + run: npx eslint src/ --ext .js,.jsx --max-warnings 0 || echo "::warning::ESLint no configurado — agregá .eslintrc.json" + continue-on-error: true diff --git a/frontend/public/tokens/a1gov.webp b/frontend/public/tokens/a1gov.webp new file mode 100644 index 0000000..5f3405b --- /dev/null +++ b/frontend/public/tokens/a1gov.webp @@ -0,0 +1 @@ +__A1GOV__ \ No newline at end of file diff --git a/frontend/public/tokens/a1pay.webp b/frontend/public/tokens/a1pay.webp new file mode 100644 index 0000000..3fbe71d --- /dev/null +++ b/frontend/public/tokens/a1pay.webp @@ -0,0 +1 @@ +__A1PAY__ \ No newline at end of file diff --git a/frontend/public/tokens/a1uti.webp b/frontend/public/tokens/a1uti.webp new file mode 100644 index 0000000..6742e29 --- /dev/null +++ b/frontend/public/tokens/a1uti.webp @@ -0,0 +1 @@ +__A1UTI__ \ No newline at end of file diff --git a/frontend/public/tokens/agov.webp b/frontend/public/tokens/agov.webp new file mode 100644 index 0000000..3f92186 --- /dev/null +++ b/frontend/public/tokens/agov.webp @@ -0,0 +1 @@ +__AGOV__ \ No newline at end of file diff --git a/frontend/public/tokens/apay.webp b/frontend/public/tokens/apay.webp new file mode 100644 index 0000000..3b06124 --- /dev/null +++ b/frontend/public/tokens/apay.webp @@ -0,0 +1 @@ +__APAY__ \ No newline at end of file diff --git a/frontend/public/tokens/ares.webp b/frontend/public/tokens/ares.webp new file mode 100644 index 0000000..4520ad1 --- /dev/null +++ b/frontend/public/tokens/ares.webp @@ -0,0 +1 @@ +__ARES__ \ No newline at end of file diff --git a/frontend/public/tokens/auti.webp b/frontend/public/tokens/auti.webp new file mode 100644 index 0000000..5e5b314 --- /dev/null +++ b/frontend/public/tokens/auti.webp @@ -0,0 +1 @@ +__AUTI__ \ No newline at end of file diff --git a/frontend/public/tokens/axgov.webp b/frontend/public/tokens/axgov.webp new file mode 100644 index 0000000..ddf2544 --- /dev/null +++ b/frontend/public/tokens/axgov.webp @@ -0,0 +1 @@ +__AXGOV__ \ No newline at end of file diff --git a/frontend/public/tokens/axpay.webp b/frontend/public/tokens/axpay.webp new file mode 100644 index 0000000..7c2e9fe --- /dev/null +++ b/frontend/public/tokens/axpay.webp @@ -0,0 +1 @@ +__AXPAY__ \ No newline at end of file diff --git a/frontend/public/tokens/axres.webp b/frontend/public/tokens/axres.webp new file mode 100644 index 0000000..548a1ee --- /dev/null +++ b/frontend/public/tokens/axres.webp @@ -0,0 +1 @@ +__AXRES__ \ No newline at end of file diff --git a/frontend/src/data/tokens.js b/frontend/src/data/tokens.js index cb25982..525559a 100644 --- a/frontend/src/data/tokens.js +++ b/frontend/src/data/tokens.js @@ -12,6 +12,7 @@ const TOKENS = [ slug: 'a-gov', name: 'A-Gobernanza', ticker: 'AGOV', + img: '/tokens/agov.webp', img: '/tokens/agov.jpg', series: 'A', type: 'Governance', @@ -26,7 +27,7 @@ const TOKENS = [ slug: 'a-uti', name: 'A-Utilidad', ticker: 'AUTI', - img: '/tokens/auti.jpg', + img: '/tokens/auti.webp', series: 'A', type: 'Utility', basePrice: 1.28, @@ -40,7 +41,7 @@ const TOKENS = [ slug: 'a-pay', name: 'A-Pago', ticker: 'APAY', - img: '/tokens/apay.jpg', + img: '/tokens/apay.webp', series: 'A', type: 'Payment', basePrice: 0.98, @@ -54,6 +55,7 @@ const TOKENS = [ slug: 'a-res', name: 'A-Reserva', ticker: 'ARES', + img: '/tokens/ares.webp', img: '/tokens/ares.jpg', series: 'A', type: 'Reserve', @@ -68,7 +70,7 @@ const TOKENS = [ slug: 'a1-gov', name: 'A1-Gobernanza', ticker: 'A1GOV', - img: '/tokens/a1gov.jpg', + img: '/tokens/a1gov.webp', series: 'A1', type: 'Governance', basePrice: 5.42, @@ -110,6 +112,7 @@ const TOKENS = [ slug: 'ax-gov', name: 'AX-Gobernanza', ticker: 'AXGOV', + img: '/tokens/axgov.webp', img: '/tokens/axgov.jpg', series: 'AX', type: 'Governance', @@ -124,7 +127,7 @@ const TOKENS = [ slug: 'ax-pay', name: 'AX-Pago', ticker: 'AXPAY', - img: '/tokens/axpay.jpg', + img: '/tokens/axpay.webp', series: 'AX', type: 'Payment', basePrice: 1.12, @@ -138,7 +141,7 @@ const TOKENS = [ slug: 'ax-res', name: 'AX-Reserva', ticker: 'AXRES', - img: '/tokens/axres.jpg', + img: '/tokens/axres.webp', series: 'AX', type: 'Reserve', basePrice: 1.00, diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..8cba7ca --- /dev/null +++ b/netlify.toml @@ -0,0 +1,27 @@ +[build] + base = "frontend" + command = "npm install && npm run build" + publish = "dist" + +[build.environment] + NODE_VERSION = "20" + +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 + +[[headers]] + for = "/tokens/*" + [headers.values] + Cache-Control = "public, max-age=31536000, immutable" + +[[headers]] + for = "/*.js" + [headers.values] + Cache-Control = "public, max-age=31536000, immutable" + +[[headers]] + for = "/*.css" + [headers.values] + Cache-Control = "public, max-age=31536000, immutable" diff --git a/vercel.json b/vercel.json index 99da314..4b00aa5 100644 --- a/vercel.json +++ b/vercel.json @@ -1,7 +1,8 @@ { - "buildCommand": "cd frontend && npm install && npm run build", - "outputDirectory": "frontend/dist", + "$schema": "https://openapi.vercel.sh/vercel.json", "framework": "vite", + "buildCommand": "npm run build", + "outputDirectory": "dist", "rewrites": [ { "source": "/(.*)",