diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..38eef04 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,115 @@ +# CLAUDE.md — Xahau Academy Course Portal + +## Project Overview +Xahau Academy is an open-source, multilingual (ES/EN/JP) web-based training portal for teaching Xahau blockchain development. It features theory content, copyable code blocks, fullscreen presentation slides, and student progress tracking. + +## Tech Stack +- **React 18** + Vite +- **Tailwind CSS** for styling +- **No backend** — all content is stored as JSON/JS modules in `src/data/` + +## Project Structure +``` +xahau-academy/ +├── CLAUDE.md # This file — project context for Claude Code +├── README.md # Public documentation +├── package.json +├── vite.config.js +├── tailwind.config.js +├── index.html +├── public/ +│ └── favicon.svg +├── src/ +│ ├── main.jsx # App entry point +│ ├── App.jsx # Main app component (router, state) +│ ├── components/ +│ │ ├── Header.jsx # Top bar with lang switcher + progress +│ │ ├── Overview.jsx # Module listing / course overview +│ │ ├── LessonView.jsx # Theory + Code + Slides tabs +│ │ ├── CodeBlock.jsx # Copyable code block with syntax highlighting +│ │ ├── SlideViewer.jsx # Fullscreen presentation mode +│ │ ├── ProgressBar.jsx # Visual progress indicator +│ │ └── Markdown.jsx # Simple markdown renderer +│ ├── data/ +│ │ ├── courses.js # Main course data index (imports all modules) +│ │ ├── i18n.js # UI labels in ES/EN/JP +│ │ └── modules/ +│ │ ├── m01-introduction.js +│ │ ├── m02-dev-environment.js +│ │ ├── m03-first-hook.js +│ │ ├── m04-deployment.js +│ │ └── _template.js # Template for creating new modules +│ └── styles/ +│ └── index.css # Global styles + Tailwind imports +└── docs/ + └── ADDING_MODULES.md # Guide for contributors adding content +``` + +## Key Conventions + +### Adding a New Module +1. Copy `src/data/modules/_template.js` +2. Rename to `mXX-slug-name.js` +3. Fill in the content following the template structure +4. Import and add to the array in `src/data/courses.js` +5. Every text field must have `{ es: "", en: "", jp: "" }` + +### Content Structure (per module) +```js +{ + id: "m5", + icon: "🔮", + title: { es: "...", en: "...", jp: "..." }, + lessons: [ + { + id: "m5l1", + title: { es, en, jp }, + theory: { es, en, jp }, // Markdown-ish text + codeBlocks: [ // Array of code examples + { title: { es, en, jp }, language: "c|javascript|bash|python", code: "..." } + ], + slides: [ // Array of presentation slides + { title: { es, en, jp }, content: { es, en, jp }, visual: "emoji" } + ] + } + ] +} +``` + +### Multilingual +- All user-facing strings must exist in ES, EN, and JP +- UI labels are in `src/data/i18n.js` +- Course content translations are inline in each module file + +### Styling +- Dark theme with accent color `#c8ff00` (Xahau green-yellow) +- Background: `#080818` → `#0e0e24` gradients +- Font: Outfit (headings), Fira Code (code/monospace) +- Use Tailwind utilities; avoid inline styles when possible + +## Common Tasks + +### Run dev server +```bash +npm install +npm run dev +``` + +### Build for production +```bash +npm run build +``` + +### Add a new module +Follow the guide in `docs/ADDING_MODULES.md` or copy `src/data/modules/_template.js`. + +### Add a new language +1. Add language key to all module content objects +2. Add UI labels in `src/data/i18n.js` +3. Add language button in `Header.jsx` + +## Code Quality +- Keep components small and focused +- All content in `src/data/`, never hardcode text in components +- Test multilingual: switch through ES/EN/JP to verify all strings render +- Code blocks should be real, working examples tested on Xahau testnet diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..32ec6e5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Xahau Academy Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e7f327 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# ⬡ Xahau Academy + +Open-source, multilingual training portal for learning Xahau blockchain development. + +![License](https://img.shields.io/badge/license-MIT-green) +![Languages](https://img.shields.io/badge/languages-ES%20%7C%20EN%20%7C%20JP-blue) + +## Features + +- 📖 **Theory** — Formatted content with markdown support +- 💻 **Code Blocks** — Copyable code examples with syntax highlighting (C, JavaScript, Bash) +- 📊 **Presentation Mode** — Fullscreen slides with keyboard navigation +- 🌐 **Multilingual** — Spanish, English, and Japanese +- 📈 **Progress Tracking** — Mark lessons as completed +- 🔌 **Modular** — Easy to add new modules and lessons + +## Quick Start + +```bash +git clone https://github.com/YOUR_USERNAME/xahau-academy.git +cd xahau-academy +npm install +npm run dev +``` + +Open [http://localhost:3000](http://localhost:3000). + +## Adding Content + +See [docs/ADDING_MODULES.md](docs/ADDING_MODULES.md) for a step-by-step guide. + +Quick version: +1. Copy `src/data/modules/_template.js` → `src/data/modules/mXX-your-topic.js` +2. Fill in content for all 3 languages (es, en, jp) +3. Import in `src/data/courses.js` + +## Tech Stack + +- React 18 + Vite +- Tailwind CSS +- No backend required — all content is static JS modules + +## Project Structure + +``` +src/ +├── components/ # React UI components +├── data/ +│ ├── i18n.js # UI translations +│ ├── courses.js # Module index +│ └── modules/ # Individual course modules +└── styles/ # Global CSS +``` + +## Contributing + +Contributions welcome! Whether it's new modules, translations, or UI improvements. + +1. Fork the repo +2. Create a branch (`git checkout -b feature/new-module`) +3. Commit your changes +4. Push and open a PR + +## License + +MIT — Use freely for education and community building. + +## Credits + +Built for the Xahau developer community. Learn more about Xahau at [xahau.network](https://xahau.network). diff --git a/docs/ADDING_MODULES.md b/docs/ADDING_MODULES.md new file mode 100644 index 0000000..df8576e --- /dev/null +++ b/docs/ADDING_MODULES.md @@ -0,0 +1,86 @@ +# Adding Modules to Xahau Academy + +## Step-by-Step Guide + +### 1. Create the Module File + +Copy the template: +```bash +cp src/data/modules/_template.js src/data/modules/m05-your-topic.js +``` + +### 2. Fill in the Content + +Edit the new file. Every text field must have translations in all 3 languages: + +```js +title: { + es: "Título en español", + en: "Title in English", + jp: "日本語のタイトル", +} +``` + +### 3. Content Types + +#### Theory (Markdown-ish) +Supports: `**bold**`, `` `inline code` ``, `### headings`, `- bullet lists`, `1. numbered lists`, `[links](url)` + +#### Code Blocks +```js +codeBlocks: [ + { + title: { es: "...", en: "...", jp: "..." }, + language: "c", // "c" | "javascript" | "bash" | "python" + code: `your code here` + } +] +``` + +#### Slides +```js +slides: [ + { + title: { es: "...", en: "...", jp: "..." }, + content: { es: "Line 1\nLine 2", en: "...", jp: "..." }, + visual: "🔮" // Single emoji + } +] +``` + +### 4. Register the Module + +Edit `src/data/courses.js`: + +```js +import m05 from './modules/m05-your-topic.js' + +export const COURSE_DATA = [ + m01, + m02, + m03, + m04, + m05, // ← Add here +] +``` + +### 5. Test + +```bash +npm run dev +``` + +Switch through all 3 languages (ES/EN/JP) to verify all strings render correctly. + +## Naming Convention + +- File: `mXX-slug-name.js` (e.g., `m05-state-management.js`) +- Module id: `"m5"` +- Lesson id: `"m5l1"`, `"m5l2"`, etc. + +## Tips + +- Keep slide content short — it's for live presentation, not reading +- Code examples should be tested on Xahau testnet +- Theory supports basic formatting, not full markdown +- One emoji per slide visual diff --git a/index.html b/index.html new file mode 100644 index 0000000..704d033 --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + Xahau Academy — Learn Xahau Development + + + + + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..59c5c88 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2712 @@ +{ + "name": "xahau-academy", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "xahau-academy", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "vite": "^6.0.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.24", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", + "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001766", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6dc7027 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "xahau-academy", + "version": "0.1.0", + "private": false, + "description": "Open-source multilingual training portal for Xahau blockchain development", + "license": "MIT", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "vite": "^6.0.0" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..3dcad7b --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,4 @@ + + + X + diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..ecda592 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,130 @@ +import React, { useState, useEffect } from 'react' +import { UI_LABELS } from './data/i18n' +import { COURSE_DATA } from './data/courses' +import Header from './components/Header' +import Overview from './components/Overview' +import LessonView from './components/LessonView' +import SlideViewer from './components/SlideViewer' + +export default function App() { + const [lang, setLang] = useState('es') + const [view, setView] = useState('overview') + const [activeModuleIdx, setActiveModuleIdx] = useState(0) + const [activeLessonIdx, setActiveLessonIdx] = useState(0) + const [showSlides, setShowSlides] = useState(false) + const [completedLessons, setCompletedLessons] = useState({}) + const [theme, setTheme] = useState(() => { + return localStorage.getItem('xahau-theme') || 'dark' + }) + + useEffect(() => { + document.documentElement.setAttribute('data-theme', theme) + localStorage.setItem('xahau-theme', theme) + }, [theme]) + + const toggleTheme = () => setTheme(t => t === 'dark' ? 'light' : 'dark') + + const t = UI_LABELS[lang] + const totalLessons = COURSE_DATA.reduce((acc, m) => acc + m.lessons.length, 0) + const completedCount = Object.values(completedLessons).filter(Boolean).length + + const currentModule = COURSE_DATA[activeModuleIdx] + const currentLesson = currentModule?.lessons[activeLessonIdx] + + const openLesson = (mIdx, lIdx) => { + setActiveModuleIdx(mIdx) + setActiveLessonIdx(lIdx) + setView('lesson') + } + + const toggleComplete = (lessonId) => { + setCompletedLessons((prev) => ({ ...prev, [lessonId]: !prev[lessonId] })) + } + + // Navigate to next lesson, crossing module boundaries + const goNext = () => { + const mod = COURSE_DATA[activeModuleIdx] + if (activeLessonIdx < mod.lessons.length - 1) { + setActiveLessonIdx(activeLessonIdx + 1) + } else if (activeModuleIdx < COURSE_DATA.length - 1) { + setActiveModuleIdx(activeModuleIdx + 1) + setActiveLessonIdx(0) + } + } + + // Navigate to previous lesson, crossing module boundaries + const goPrev = () => { + if (activeLessonIdx > 0) { + setActiveLessonIdx(activeLessonIdx - 1) + } else if (activeModuleIdx > 0) { + const prevMod = COURSE_DATA[activeModuleIdx - 1] + setActiveModuleIdx(activeModuleIdx - 1) + setActiveLessonIdx(prevMod.lessons.length - 1) + } + } + + const isFirst = activeModuleIdx === 0 && activeLessonIdx === 0 + const isLast = activeModuleIdx === COURSE_DATA.length - 1 && + activeLessonIdx === currentModule.lessons.length - 1 + + // Slides mode + if (showSlides && currentLesson?.slides) { + return ( + setShowSlides(false)} + theme={theme} + /> + ) + } + + // Overview + if (view === 'overview') { + return ( +
+
+ +
+ ) + } + + // Lesson + return ( + toggleComplete(currentLesson.id)} + onShowSlides={() => setShowSlides(true)} + onBack={() => setView('overview')} + onPrev={goPrev} + onNext={goNext} + onGoToLesson={(lIdx) => setActiveLessonIdx(lIdx)} + hasPrev={!isFirst} + hasNext={!isLast} + theme={theme} + onToggleTheme={toggleTheme} + totalModules={COURSE_DATA.length} + /> + ) +} diff --git a/src/components/CodeBlock.jsx b/src/components/CodeBlock.jsx new file mode 100644 index 0000000..a88be49 --- /dev/null +++ b/src/components/CodeBlock.jsx @@ -0,0 +1,55 @@ +import React, { useState } from 'react' + +const LANG_COLORS = { + javascript: '#f7df1e', + bash: '#4eaa25', + c: '#00599c', + python: '#3776ab', +} + +export default function CodeBlock({ block, lang, labels }) { + const [copied, setCopied] = useState(false) + + const handleCopy = () => { + navigator.clipboard.writeText(block.code).then(() => { + setCopied(true) + setTimeout(() => setCopied(false), 2000) + }) + } + + return ( +
+
+
+ + {block.language} + + {block.title[lang]} +
+ +
+
+        {block.code}
+      
+
+ ) +} diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 0000000..a302b6e --- /dev/null +++ b/src/components/Header.jsx @@ -0,0 +1,65 @@ +import React from 'react' +import ProgressBar from './ProgressBar' + +export default function Header({ lang, setLang, labels, completedCount, totalLessons, theme, onToggleTheme }) { + const pct = totalLessons === 0 ? 0 : Math.round((completedCount / totalLessons) * 100) + + return ( +
+
+
+
+

+ {labels.title} +

+

{labels.subtitle}

+
+
+ {/* Theme Toggle */} + + {/* Language Switcher */} +
+ {['es', 'en', 'jp'].map((l) => ( + + ))} +
+
+
+ {/* Progress */} +
+ + {labels.progress}: {completedCount}/{totalLessons} + + {pct}% +
+ +
+
+ ) +} diff --git a/src/components/LessonView.jsx b/src/components/LessonView.jsx new file mode 100644 index 0000000..23ac538 --- /dev/null +++ b/src/components/LessonView.jsx @@ -0,0 +1,194 @@ +import React, { useState } from 'react' +import Markdown from './Markdown' +import CodeBlock from './CodeBlock' + +export default function LessonView({ + module: mod, + moduleIdx, + lesson, + lessonIdx, + lang, + labels, + isCompleted, + onToggleComplete, + onShowSlides, + onBack, + onPrev, + onNext, + onGoToLesson, + hasPrev, + hasNext, + theme, + onToggleTheme, + totalModules, +}) { + const [activeTab, setActiveTab] = useState('theory') + + const tabs = [ + { key: 'theory', icon: '📖', label: labels.theory, disabled: false }, + { key: 'code', icon: '💻', label: labels.code, disabled: !lesson.codeBlocks?.length }, + { key: 'slides', icon: '📊', label: labels.slides, disabled: !lesson.slides?.length }, + ] + + const lessonNumber = lessonIdx + 1 + const totalLessons = mod.lessons.length + + return ( +
+ {/* Lesson header */} +
+
+
+ +
+ {/* Theme toggle */} + + {/* Lesson position indicator */} + + {labels.module} {moduleIdx}/{totalModules} — {labels.theory} {lessonNumber}/{totalLessons} + +
+
+ +
+ {mod.icon} +
+
+ {mod.title[lang]} +
+

{lesson.title[lang]}

+
+
+ + {/* Lesson navigation pills */} +
+ {mod.lessons.map((l, idx) => ( + + ))} +
+ + {/* Tabs */} +
+ {tabs.map((tab) => { + const isActive = activeTab === tab.key && tab.key !== 'slides' + return ( + + ) + })} +
+
+
+ + {/* Content */} +
+ {activeTab === 'theory' && ( +
+ +
+ )} + + {activeTab === 'code' && lesson.codeBlocks && ( +
+ {lesson.codeBlocks.map((block, idx) => ( + + ))} +
+ )} + + {/* Bottom actions */} +
+ + + + + +
+
+
+ ) +} diff --git a/src/components/Markdown.jsx b/src/components/Markdown.jsx new file mode 100644 index 0000000..552c18c --- /dev/null +++ b/src/components/Markdown.jsx @@ -0,0 +1,168 @@ +import React from 'react' + +function renderInline(text) { + const parts = text.split(/(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]+\]\([^)]+\))/g) + return parts.map((part, i) => { + if (part.startsWith('**') && part.endsWith('**')) { + return {part.slice(2, -2)} + } + if (part.startsWith('`') && part.endsWith('`')) { + return ( + + {part.slice(1, -1)} + + ) + } + const linkMatch = part.match(/\[([^\]]+)\]\(([^)]+)\)/) + if (linkMatch) { + return ( + + {linkMatch[1]} + + ) + } + return part + }) +} + +export default function Markdown({ text }) { + if (!text) return null + const lines = text.split('\n') + const elements = [] + let i = 0 + + while (i < lines.length) { + const line = lines[i] + + // Fenced code blocks (``` ... ```) + if (line.trimStart().startsWith('```')) { + const codeLines = [] + i++ // skip opening ``` + while (i < lines.length && !lines[i].trimStart().startsWith('```')) { + codeLines.push(lines[i]) + i++ + } + i++ // skip closing ``` + elements.push( +
+          {codeLines.join('\n')}
+        
+ ) + continue + } + + // Table rows (|...|...|) + if (line.trimStart().startsWith('|')) { + const tableLines = [] + while (i < lines.length && lines[i].trimStart().startsWith('|')) { + tableLines.push(lines[i]) + i++ + } + // Parse table + const rows = tableLines + .filter((row) => !/^\|\s*-+/.test(row)) // skip separator rows like |---|---| + .map((row) => + row + .split('|') + .slice(1, -1) // remove empty first/last from split + .map((cell) => cell.trim()) + ) + + if (rows.length > 0) { + const headerRow = rows[0] + const bodyRows = rows.slice(1) + elements.push( +
+ + + + {headerRow.map((cell, ci) => ( + + ))} + + + + {bodyRows.map((row, ri) => ( + + {row.map((cell, ci) => ( + + ))} + + ))} + +
+ {renderInline(cell)} +
+ {renderInline(cell)} +
+
+ ) + } + continue + } + + if (line.startsWith('### ')) { + elements.push( +

+ {line.slice(4)} +

+ ) + } else if (line.startsWith('## ')) { + elements.push( +

+ {line.slice(3)} +

+ ) + } else if (line.startsWith('- ')) { + elements.push( +
+ + {renderInline(line.slice(2))} +
+ ) + } else if (/^\d+\.\s/.test(line)) { + const match = line.match(/^(\d+)\.\s(.*)/) + elements.push( +
+ {match[1]}. + {renderInline(match[2])} +
+ ) + } else if (line.trim() === '') { + elements.push(
) + } else { + elements.push( +

+ {renderInline(line)} +

+ ) + } + + i++ + } + + return
{elements}
+} diff --git a/src/components/Overview.jsx b/src/components/Overview.jsx new file mode 100644 index 0000000..64ac240 --- /dev/null +++ b/src/components/Overview.jsx @@ -0,0 +1,61 @@ +import React from 'react' + +export default function Overview({ courseData, lang, labels, completedLessons, onOpenLesson }) { + return ( +
+
+ {courseData.map((mod, mIdx) => ( +
+
+
+
+ {mod.icon} +
+
+
+ {labels.module} {mIdx} +
+

{mod.title[lang]}

+
+
+
+ {mod.lessons.map((lesson, lIdx) => { + const done = completedLessons[lesson.id] + return ( + + ) + })} +
+
+
+ ))} +
+
+ ) +} diff --git a/src/components/ProgressBar.jsx b/src/components/ProgressBar.jsx new file mode 100644 index 0000000..5b48270 --- /dev/null +++ b/src/components/ProgressBar.jsx @@ -0,0 +1,16 @@ +import React from 'react' + +export default function ProgressBar({ completed, total }) { + const pct = total === 0 ? 0 : Math.round((completed / total) * 100) + return ( +
+
+
+ ) +} diff --git a/src/components/SlideViewer.jsx b/src/components/SlideViewer.jsx new file mode 100644 index 0000000..1bcf6b4 --- /dev/null +++ b/src/components/SlideViewer.jsx @@ -0,0 +1,109 @@ +import React, { useState, useEffect } from 'react' + +export default function SlideViewer({ slides, lang, labels, onExit, theme }) { + const [current, setCurrent] = useState(0) + + useEffect(() => { + const handleKey = (e) => { + if (e.key === 'ArrowRight' || e.key === ' ') { + e.preventDefault() + setCurrent((c) => Math.min(c + 1, slides.length - 1)) + } + if (e.key === 'ArrowLeft') setCurrent((c) => Math.max(c - 1, 0)) + if (e.key === 'Escape') onExit() + } + window.addEventListener('keydown', handleKey) + return () => window.removeEventListener('keydown', handleKey) + }, [slides.length, onExit]) + + const slide = slides[current] + const isLight = theme === 'light' + + return ( +
+ {/* Top bar */} +
+ + {current + 1} {labels.slideOf} {slides.length} + + +
+ + {/* Content */} +
+
+
{slide.visual}
+

+ {slide.title[lang]} +

+
+ {slide.content[lang]} +
+
+
+ + {/* Navigation */} +
+ +
+ {slides.map((_, idx) => ( +
+ +
+
+ ) +} diff --git a/src/data/courses.js b/src/data/courses.js new file mode 100644 index 0000000..c594401 --- /dev/null +++ b/src/data/courses.js @@ -0,0 +1,39 @@ +/** + * Course Data Index + * + * Import all module files and export them as an ordered array. + * To add a new module: + * 1. Create the module file in ./modules/ + * 2. Import it below + * 3. Add it to the COURSE_DATA array in the desired order + */ + +import m00 from './modules/m00-setup.js' +import m01 from './modules/m01-blockchain-no-evm.js' +import m02 from './modules/m02-consenso.js' +import m03 from './modules/m03-primera-wallet.js' +import m04 from './modules/m04-consulta-datos.js' +import m05 from './modules/m05-pagos.js' +import m05b from './modules/m05b-anatomia-transacciones.js' +import m06 from './modules/m06-tokens.js' +import m07 from './modules/m07-nfts.js' +import m08 from './modules/m08-smart-contracts.js' +import m09 from './modules/m09-dex.js' +import m10 from './modules/m10-herramientas.js' +import m11 from './modules/m11-proyecto-final.js' + +export const COURSE_DATA = [ + m00, + m01, + m02, + m03, + m04, + m05b, + m05, + m06, + m07, + m08, + m09, + m10, + m11, +] diff --git a/src/data/i18n.js b/src/data/i18n.js new file mode 100644 index 0000000..f055cd7 --- /dev/null +++ b/src/data/i18n.js @@ -0,0 +1,92 @@ +export const UI_LABELS = { + es: { + title: "Xahau Academy", + subtitle: "Curso de Iniciación a la Programación", + progress: "Progreso", + theory: "Teoría", + code: "Código", + slides: "Slides", + copy: "Copiar", + copied: "¡Copiado!", + next: "Siguiente", + prev: "Anterior", + completed: "Completado", + markComplete: "Marcar como completado", + slideOf: "de", + exitSlides: "Salir", + allModules: "Módulos", + module: "Módulo", + overview: "Vista general", + language: "Idioma", + startCourse: "Empezar curso", + continueLesson: "Continuar", + lessonCompleted: "✓ Completada", + resetProgress: "Reiniciar progreso", + nextModule: "Siguiente módulo", + prevModule: "Módulo anterior", + nextLesson: "Siguiente lección", + prevLesson: "Lección anterior", + lessonOf: "de", + slideMode: "Modo Presentación", + }, + en: { + title: "Xahau Academy", + subtitle: "Introduction to Programming Course", + progress: "Progress", + theory: "Theory", + code: "Code", + slides: "Slides", + copy: "Copy", + copied: "Copied!", + next: "Next", + prev: "Previous", + completed: "Completed", + markComplete: "Mark as completed", + slideOf: "of", + exitSlides: "Exit", + allModules: "Modules", + module: "Module", + overview: "Overview", + language: "Language", + startCourse: "Start course", + continueLesson: "Continue", + lessonCompleted: "✓ Completed", + resetProgress: "Reset progress", + nextModule: "Next module", + prevModule: "Previous module", + nextLesson: "Next lesson", + prevLesson: "Previous lesson", + lessonOf: "of", + slideMode: "Presentation Mode", + }, + jp: { + title: "Xahau Academy", + subtitle: "プログラミング入門コース", + progress: "進捗", + theory: "理論", + code: "コード", + slides: "スライド", + copy: "コピー", + copied: "コピー済み!", + next: "次へ", + prev: "前へ", + completed: "完了", + markComplete: "完了にする", + slideOf: "/", + exitSlides: "終了", + allModules: "モジュール", + module: "モジュール", + overview: "概要", + language: "言語", + startCourse: "コース開始", + continueLesson: "続ける", + lessonCompleted: "✓ 完了", + resetProgress: "リセット", + nextModule: "次のモジュール", + prevModule: "前のモジュール", + nextLesson: "次のレッスン", + prevLesson: "前のレッスン", + lessonOf: "/", + slideMode: "プレゼンテーションモード", + }, +} diff --git a/src/data/modules/_template.js b/src/data/modules/_template.js new file mode 100644 index 0000000..d233393 --- /dev/null +++ b/src/data/modules/_template.js @@ -0,0 +1,84 @@ +/** + * Module Template — Copy this file to create a new module + * + * Instructions: + * 1. Copy this file and rename to mXX-your-module-slug.js + * 2. Fill in all { es, en, jp } fields + * 3. Import in src/data/courses.js and add to the COURSE_DATA array + * + * Tips: + * - Theory supports basic markdown: **bold**, `code`, ###headings, - lists, [links](url) + * - Code blocks: use language "c", "javascript", "bash", or "python" + * - Slides: keep content short, use \n for line breaks, visual is a single emoji + * - Each lesson needs a unique id (e.g., "m5l1", "m5l2") + */ + +export default { + id: "mXX", // Unique module id + icon: "🔮", // Emoji icon for the module card + title: { + es: "Título del Módulo", + en: "Module Title", + jp: "モジュールタイトル", + }, + lessons: [ + { + id: "mXXl1", // Unique lesson id + title: { + es: "Título de la lección", + en: "Lesson Title", + jp: "レッスンタイトル", + }, + theory: { + es: `Contenido teórico en español. + +### Subtítulo +- Punto 1 +- Punto 2 + +Texto con **negrita** y \`código inline\`.`, + en: `Theory content in English. + +### Subtitle +- Point 1 +- Point 2 + +Text with **bold** and \`inline code\`.`, + jp: `日本語の理論コンテンツ。 + +### サブタイトル +- ポイント1 +- ポイント2 + +**太字**と\`インラインコード\`のテキスト。`, + }, + codeBlocks: [ + { + title: { + es: "Ejemplo de código", + en: "Code example", + jp: "コード例", + }, + language: "javascript", // "c" | "javascript" | "bash" | "python" + code: `// Your code here +console.log("Hello Xahau!");`, + }, + ], + slides: [ + { + title: { + es: "Título del slide", + en: "Slide Title", + jp: "スライドタイトル", + }, + content: { + es: "Contenido del slide\n\n• Punto clave 1\n• Punto clave 2", + en: "Slide content\n\n• Key point 1\n• Key point 2", + jp: "スライド内容\n\n• キーポイント1\n• キーポイント2", + }, + visual: "🔮", // Single emoji as visual element + }, + ], + }, + ], +} diff --git a/src/data/modules/m00-setup.js b/src/data/modules/m00-setup.js new file mode 100644 index 0000000..4bf9145 --- /dev/null +++ b/src/data/modules/m00-setup.js @@ -0,0 +1,1728 @@ +export default { + id: "m0", + icon: "⚙️", + title: { + es: "Preparación del entorno de trabajo", + en: "Setting Up the Development Environment", + jp: "", + }, + lessons: [ + { + id: "m0l1", + title: { + es: "Instalación de Visual Studio Code", + en: "Installing Visual Studio Code", + jp: "", + }, + theory: { + es: `**Visual Studio Code (VS Code)** es el editor de código que usaremos durante todo el curso. Es gratuito, ligero y tiene un ecosistema enorme de extensiones que nos facilitarán el desarrollo. + +### ¿Por qué VS Code? + +- **Gratuito y open source** (mantenido por Microsoft) +- **Multiplataforma**: funciona en Windows, macOS y Linux +- **Terminal integrada**: puedes ejecutar comandos sin salir del editor +- **Extensiones**: soporte para JavaScript, formateo automático, autocompletado inteligente y mucho más +- **Git integrado**: gestión de versiones sin salir del editor + +### Instalación en Windows + +1. Ve a [code.visualstudio.com](https://code.visualstudio.com) +2. Haz clic en **"Download for Windows"** +3. Ejecuta el instalador \`.exe\` descargado +4. Durante la instalación, marca estas opciones recomendadas: + - ✅ Agregar "Abrir con Code" al menú contextual de archivos + - ✅ Agregar "Abrir con Code" al menú contextual de directorios + - ✅ Agregar a PATH (para poder abrir desde terminal con \`code .\`) +5. Haz clic en **Instalar** y espera a que termine + +### Instalación en macOS + +1. Ve a [code.visualstudio.com](https://code.visualstudio.com) +2. Haz clic en **"Download for Mac"** +3. Abre el archivo \`.zip\` descargado +4. Arrastra **Visual Studio Code.app** a la carpeta **Aplicaciones** +5. Para usar el comando \`code\` desde la terminal: + - Abre VS Code + - Pulsa \`Cmd + Shift + P\` para abrir la paleta de comandos + - Escribe **"Shell Command: Install 'code' command in PATH"** + - Selecciona la opción y confirma + +### Instalación en Linux (Ubuntu/Debian) + +1. Abre una terminal y ejecuta los siguientes comandos: + +\`\`\` +sudo apt update +sudo apt install software-properties-common apt-transport-https wget +wget -q https://packages.microsoft.com/keys/microsoft.asc -O- | sudo apt-key add - +sudo add-apt-repository "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" +sudo apt update +sudo apt install code +\`\`\` + +2. Alternativamente, descarga el paquete \`.deb\` desde [code.visualstudio.com](https://code.visualstudio.com) y haz doble clic para instalarlo + +### Instalación en Linux (Fedora/RHEL) + +1. Abre una terminal y ejecuta: + +\`\`\` +sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc +sudo sh -c 'echo -e "[code]\\nname=Visual Studio Code\\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\\nenabled=1\\ngpgcheck=1\\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/vscode.repo' +sudo dnf install code +\`\`\` + +### Verificar la instalación + +Una vez instalado, abre una terminal (o el Símbolo del sistema en Windows) y ejecuta: + +\`\`\` +code --version +\`\`\` + +Debería mostrar el número de versión instalada.`, + en: `**Visual Studio Code (VS Code)** is the code editor we will use throughout the entire course. It is free, lightweight, and has a huge ecosystem of extensions that will make development easier for us. + +### Why VS Code? + +- **Free and open source** (maintained by Microsoft) +- **Cross-platform**: works on Windows, macOS, and Linux +- **Integrated terminal**: you can run commands without leaving the editor +- **Extensions**: support for JavaScript, automatic formatting, smart autocomplete, and much more +- **Built-in Git**: version control without leaving the editor + +### Installation on Windows + +1. Go to [code.visualstudio.com](https://code.visualstudio.com) +2. Click on **"Download for Windows"** +3. Run the downloaded \`.exe\` installer +4. During installation, check these recommended options: + - ✅ Add "Open with Code" to the file context menu + - ✅ Add "Open with Code" to the directory context menu + - ✅ Add to PATH (to be able to open from terminal with \`code .\`) +5. Click **Install** and wait for it to finish + +### Installation on macOS + +1. Go to [code.visualstudio.com](https://code.visualstudio.com) +2. Click on **"Download for Mac"** +3. Open the downloaded \`.zip\` file +4. Drag **Visual Studio Code.app** to the **Applications** folder +5. To use the \`code\` command from the terminal: + - Open VS Code + - Press \`Cmd + Shift + P\` to open the command palette + - Type **"Shell Command: Install 'code' command in PATH"** + - Select the option and confirm + +### Installation on Linux (Ubuntu/Debian) + +1. Open a terminal and run the following commands: + +\`\`\` +sudo apt update +sudo apt install software-properties-common apt-transport-https wget +wget -q https://packages.microsoft.com/keys/microsoft.asc -O- | sudo apt-key add - +sudo add-apt-repository "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" +sudo apt update +sudo apt install code +\`\`\` + +2. Alternatively, download the \`.deb\` package from [code.visualstudio.com](https://code.visualstudio.com) and double-click to install it + +### Installation on Linux (Fedora/RHEL) + +1. Open a terminal and run: + +\`\`\` +sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc +sudo sh -c 'echo -e "[code]\\nname=Visual Studio Code\\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\\nenabled=1\\ngpgcheck=1\\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/vscode.repo' +sudo dnf install code +\`\`\` + +### Verify the installation + +Once installed, open a terminal (or Command Prompt on Windows) and run: + +\`\`\` +code --version +\`\`\` + +It should display the installed version number.`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Verificar instalación de VS Code desde terminal", + en: "Verify VS Code installation from the terminal", + jp: "", + }, + language: "bash", + code: `# Verify that VS Code is installed +code --version + +# Open VS Code in the current directory +code . + +# Open a specific file +code mi-archivo.js`, + }, + { + title: { + es: "Extensiones recomendadas para el curso", + en: "Recommended extensions for the course", + jp: "", + }, + language: "bash", + code: `# Install extensions from the terminal +# (you can also search for them in the VS Code Extensions tab) + +# Enhanced JavaScript/TypeScript support +code --install-extension dbaeumer.vscode-eslint + +# Automatic code formatting +code --install-extension esbenp.prettier-vscode + +# Bracket pair colorization +code --install-extension CoenraadS.bracket-pair-colorizer-2 + +# Icons for the file explorer +code --install-extension vscode-icons-team.vscode-icons`, + }, + ], + slides: [ + { + title: { es: "Visual Studio Code", en: "Visual Studio Code", jp: "" }, + content: { + es: "Editor de código gratuito y multiplataforma\n\n• Windows, macOS y Linux\n• Terminal integrada\n• Miles de extensiones\n• Git integrado\n• Descarga: code.visualstudio.com", + en: "Free and cross-platform code editor\n\n• Windows, macOS, and Linux\n• Integrated terminal\n• Thousands of extensions\n• Built-in Git\n• Download: code.visualstudio.com", + jp: "", + }, + visual: "💻", + }, + { + title: { es: "Instalación rápida", en: "Quick Installation", jp: "" }, + content: { + es: "🪟 Windows → Descargar .exe e instalar\n🍎 macOS → Descargar .zip, arrastrar a Aplicaciones\n🐧 Linux → apt install code / dnf install code\n\n✅ Verificar: code --version", + en: "🪟 Windows → Download .exe and install\n🍎 macOS → Download .zip, drag to Applications\n🐧 Linux → apt install code / dnf install code\n\n✅ Verify: code --version", + jp: "", + }, + visual: "📦", + }, + ], + }, + { + id: "m0l2", + title: { + es: "Instalación de Node.js", + en: "Installing Node.js", + jp: "", + }, + theory: { + es: `**Node.js** es el entorno de ejecución de JavaScript que necesitamos para ejecutar los scripts del curso. Todos los ejemplos de código que interactúan con la blockchain Xahau se ejecutan con Node.js. + +### ¿Qué es Node.js? + +Node.js permite ejecutar código JavaScript **fuera del navegador**, directamente en tu ordenador. Incluye: +- **node**: El intérprete de JavaScript (ejecuta tus scripts) +- **npm**: El gestor de paquetes (instala librerías como \`xahau\`) +- **npx**: Ejecutor de paquetes (ejecuta herramientas sin instalar globalmente) + +### Versión recomendada + +Para este curso necesitas **Node.js v18 o superior** (recomendamos la versión LTS más reciente). La librería \`xahau\` requiere al menos v18. + +### Instalación en Windows + +1. Ve a [nodejs.org](https://nodejs.org) +2. Descarga la versión **LTS** (Long Term Support) +3. Ejecuta el instalador \`.msi\` +4. Sigue el asistente con las opciones por defecto +5. **Importante**: marca la casilla "Automatically install the necessary tools" si aparece +6. Reinicia la terminal después de instalar + +### Instalación en macOS + +**Opción A — Instalador oficial:** +1. Ve a [nodejs.org](https://nodejs.org) +2. Descarga la versión **LTS** para macOS +3. Abre el archivo \`.pkg\` y sigue el asistente + +**Opción B — Con Homebrew (recomendado):** +1. Si no tienes Homebrew, instálalo primero desde [brew.sh](https://brew.sh) +2. Ejecuta en la terminal: + +\`\`\` +brew install node@22 +\`\`\` + +### Instalación en Linux (Ubuntu/Debian) + +Usa el repositorio oficial de NodeSource para obtener la versión más reciente: + +\`\`\` +curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - +sudo apt install -y nodejs +\`\`\` + +### Instalación en Linux (Fedora) + +\`\`\` +curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash - +sudo dnf install -y nodejs +\`\`\` + +### Verificar la instalación + +Abre una **nueva terminal** (esto es importante, sobre todo en Windows) y ejecuta: + +\`\`\` +node --version +npm --version +\`\`\` + +Deberías ver algo como \`v22.x.x\` y \`10.x.x\` respectivamente. + +### Instalar la librería xahau + +Con Node.js instalado, ya puedes instalar la librería que usaremos en todo el curso: + +\`\`\` +mkdir xahau-curso +cd xahau-curso +npm init -y +npm install xahau +\`\`\` + +Esto creará tu proyecto y descargará la librería \`xahau\` para que puedas ejecutar todos los ejemplos del curso.`, + en: `**Node.js** is the JavaScript runtime environment we need to run the course scripts. All code examples that interact with the Xahau blockchain are executed with Node.js. + +### What is Node.js? + +Node.js allows you to run JavaScript code **outside the browser**, directly on your computer. It includes: +- **node**: The JavaScript interpreter (runs your scripts) +- **npm**: The package manager (installs libraries like \`xahau\`) +- **npx**: Package runner (runs tools without installing them globally) + +### Recommended version + +For this course you need **Node.js v18 or higher** (we recommend the latest LTS version). The \`xahau\` library requires at least v18. + +### Installation on Windows + +1. Go to [nodejs.org](https://nodejs.org) +2. Download the **LTS** (Long Term Support) version +3. Run the \`.msi\` installer +4. Follow the wizard with the default options +5. **Important**: check the "Automatically install the necessary tools" box if it appears +6. Restart the terminal after installation + +### Installation on macOS + +**Option A — Official installer:** +1. Go to [nodejs.org](https://nodejs.org) +2. Download the **LTS** version for macOS +3. Open the \`.pkg\` file and follow the wizard + +**Option B — With Homebrew (recommended):** +1. If you don't have Homebrew, install it first from [brew.sh](https://brew.sh) +2. Run in the terminal: + +\`\`\` +brew install node@22 +\`\`\` + +### Installation on Linux (Ubuntu/Debian) + +Use the official NodeSource repository to get the latest version: + +\`\`\` +curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - +sudo apt install -y nodejs +\`\`\` + +### Installation on Linux (Fedora) + +\`\`\` +curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash - +sudo dnf install -y nodejs +\`\`\` + +### Verify the installation + +Open a **new terminal** (this is important, especially on Windows) and run: + +\`\`\` +node --version +npm --version +\`\`\` + +You should see something like \`v22.x.x\` and \`10.x.x\` respectively. + +### Install the xahau library + +With Node.js installed, you can now install the library we will use throughout the course: + +\`\`\` +mkdir xahau-curso +cd xahau-curso +npm init -y +npm install xahau +\`\`\` + +This will create your project and download the \`xahau\` library so you can run all the course examples.`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Verificar instalación y crear el proyecto del curso", + en: "Verify installation and create the course project", + jp: "", + }, + language: "bash", + code: `# 1. Verify that Node.js is installed +node --version +# Expected: v22.x.x (or v18+) + +npm --version +# Expected: 10.x.x + +# 2. Create the course project directory +mkdir xahau-curso +cd xahau-curso + +# 3. Initialize a Node.js project +npm init -y + +# 4. Install the xahau library +npm install xahau`, + }, + { + title: { + es: "Tu primer script: Hola Xahau", + en: "Your first script: Hello Xahau", + jp: "", + }, + language: "javascript", + code: `// File: hola-xahau.js +// Run with: node hola-xahau.js + +const { Client } = require("xahau"); + +async function main() { + console.log("Connecting to Xahau..."); + + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const response = await client.request({ + command: "server_info" + }); + + const info = response.result.info; + console.log("Successfully connected!"); + console.log("Network:", info.network_id); + console.log("Version:", info.build_version); + console.log("Ledger:", info.validated_ledger.seq); + + await client.disconnect(); + console.log("Disconnected. Your environment is ready!"); +} + +main();`, + }, + ], + slides: [ + { + title: { es: "¿Qué es Node.js?", en: "What is Node.js?", jp: "" }, + content: { + es: "JavaScript fuera del navegador\n\n• node → Ejecuta tus scripts\n• npm → Instala librerías\n• npx → Ejecuta herramientas\n\nVersiones: v18+ (recomendado v22 LTS)", + en: "JavaScript outside the browser\n\n• node → Runs your scripts\n• npm → Installs libraries\n• npx → Runs tools\n\nVersions: v18+ (recommended v22 LTS)", + jp: "", + }, + visual: "🟢", + }, + { + title: { es: "Instalación rápida", en: "Quick Installation", jp: "" }, + content: { + es: "🪟 Windows → nodejs.org → .msi\n🍎 macOS → brew install node@22\n🐧 Linux → NodeSource + apt/dnf\n\n✅ Verificar:\nnode --version\nnpm --version", + en: "🪟 Windows → nodejs.org → .msi\n🍎 macOS → brew install node@22\n🐧 Linux → NodeSource + apt/dnf\n\n✅ Verify:\nnode --version\nnpm --version", + jp: "", + }, + visual: "📦", + }, + { + title: { es: "Preparar el proyecto", en: "Set Up the Project", jp: "" }, + content: { + es: "mkdir xahau-curso\ncd xahau-curso\nnpm init -y\nnpm install xahau\n\n¡Listo para ejecutar los scripts del curso!", + en: "mkdir xahau-curso\ncd xahau-curso\nnpm init -y\nnpm install xahau\n\nReady to run the course scripts!", + jp: "", + }, + visual: "🚀", + }, + ], + }, + { + id: "m0l3", + title: { + es: "Alternativa online: CodeSandbox", + en: "Online Alternative: CodeSandbox", + jp: "", + }, + theory: { + es: `Si no quieres o no puedes instalar software en tu ordenador, puedes usar **CodeSandbox**, un entorno de desarrollo online gratuito que funciona directamente en tu navegador. + +### ¿Qué es CodeSandbox? + +[CodeSandbox](https://codesandbox.io) es un IDE en la nube que te permite escribir, ejecutar y compartir código sin instalar nada. Su plan gratuito incluye todo lo que necesitas para este curso. + +### Ventajas de CodeSandbox + +- **Sin instalación**: todo funciona en el navegador +- **Acceso desde cualquier dispositivo**: solo necesitas internet +- **Terminal integrada**: puedes ejecutar comandos npm y node +- **Compartir código**: cada sandbox tiene una URL única +- **Gratis**: el plan gratuito es suficiente para el curso + +### Crear tu cuenta + +1. Ve a [codesandbox.io](https://codesandbox.io) +2. Haz clic en **"Sign In"** (arriba a la derecha) +3. Puedes registrarte con tu cuenta de **GitHub**, **Google** o **email** +4. Una vez dentro, llegarás a tu dashboard + +### Crear un sandbox para el curso + +1. En tu dashboard, haz clic en **"Create"** (arriba a la derecha) +2. Selecciona **"Import from GitHub"** o busca la plantilla **"Node.js"** +3. Si no encuentras la plantilla de Node.js: + - Haz clic en **"Create"** → **"Devbox"** + - Selecciona **"Node.js"** como plantilla +4. Esto creará un entorno con Node.js preinstalado + +### Configurar el sandbox para Xahau + +Una vez dentro del sandbox: + +1. **Abrir la terminal**: haz clic en el icono de terminal en el panel inferior, o usa el menú **Terminal → New Terminal** +2. **Instalar la librería xahau**: ejecuta en la terminal: + +\`\`\` +npm install xahau +\`\`\` + +3. **Crear tu primer archivo**: haz clic derecho en el explorador de archivos (panel izquierdo) → **New File** → nombra el archivo \`hola-xahau.js\` +4. **Escribir el código**: copia cualquier ejemplo del curso en el archivo +5. **Ejecutar el script**: en la terminal, ejecuta: + +\`\`\` +node hola-xahau.js +\`\`\` + +### Estructura recomendada del sandbox + +Organiza tus archivos así para seguir el curso: + +\`\`\` +xahau-curso/ +├── package.json ← Se crea automáticamente +├── node_modules/ ← Se crea con npm install +├── m01-arquitectura.js ← Scripts del módulo 1 +├── m02-consenso.js ← Scripts del módulo 2 +├── m03-wallet.js ← Scripts del módulo 3 +├── m04-consultas.js ← Scripts del módulo 4 +├── m05-pagos.js ← Scripts del módulo 5 +├── m06-tokens.js ← Scripts del módulo 6 +├── m07-nfts.js ← Scripts del módulo 7 +└── m08-hooks.js ← Scripts del módulo 8 +\`\`\` + +### Limitaciones del plan gratuito + +- **Sandboxes públicos**: tu código es visible para otros (no pongas claves privadas de mainnet) +- **Tiempo de inactividad**: el sandbox se pausa tras un rato sin uso (se reactiva al volver) +- **Recursos limitados**: suficiente para los scripts del curso, pero no para compilar Hooks en C + +### Recomendación de seguridad + +Como los sandboxes gratuitos son públicos, **nunca pongas seeds o claves privadas de mainnet** en CodeSandbox. Usa únicamente claves de **testnet** (tokens sin valor real). Para trabajar con mainnet, usa un entorno local con VS Code.`, + en: `If you don't want to or can't install software on your computer, you can use **CodeSandbox**, a free online development environment that works directly in your browser. + +### What is CodeSandbox? + +[CodeSandbox](https://codesandbox.io) is a cloud IDE that allows you to write, run, and share code without installing anything. Its free plan includes everything you need for this course. + +### Advantages of CodeSandbox + +- **No installation**: everything works in the browser +- **Access from any device**: you only need internet +- **Integrated terminal**: you can run npm and node commands +- **Share code**: each sandbox has a unique URL +- **Free**: the free plan is sufficient for the course + +### Create your account + +1. Go to [codesandbox.io](https://codesandbox.io) +2. Click on **"Sign In"** (top right) +3. You can sign up with your **GitHub**, **Google**, or **email** account +4. Once inside, you will reach your dashboard + +### Create a sandbox for the course + +1. In your dashboard, click on **"Create"** (top right) +2. Select **"Import from GitHub"** or search for the **"Node.js"** template +3. If you can't find the Node.js template: + - Click on **"Create"** → **"Devbox"** + - Select **"Node.js"** as the template +4. This will create an environment with Node.js preinstalled + +### Configure the sandbox for Xahau + +Once inside the sandbox: + +1. **Open the terminal**: click on the terminal icon in the bottom panel, or use the menu **Terminal → New Terminal** +2. **Install the xahau library**: run in the terminal: + +\`\`\` +npm install xahau +\`\`\` + +3. **Create your first file**: right-click in the file explorer (left panel) → **New File** → name the file \`hola-xahau.js\` +4. **Write the code**: copy any example from the course into the file +5. **Run the script**: in the terminal, run: + +\`\`\` +node hola-xahau.js +\`\`\` + +### Recommended sandbox structure + +Organize your files like this to follow the course: + +\`\`\` +xahau-curso/ +├── package.json ← Created automatically +├── node_modules/ ← Created with npm install +├── m01-arquitectura.js ← Module 1 scripts +├── m02-consenso.js ← Module 2 scripts +├── m03-wallet.js ← Module 3 scripts +├── m04-consultas.js ← Module 4 scripts +├── m05-pagos.js ← Module 5 scripts +├── m06-tokens.js ← Module 6 scripts +├── m07-nfts.js ← Module 7 scripts +└── m08-hooks.js ← Module 8 scripts +\`\`\` + +### Free plan limitations + +- **Public sandboxes**: your code is visible to others (don't put mainnet private keys) +- **Inactivity timeout**: the sandbox pauses after a while of inactivity (reactivates when you return) +- **Limited resources**: sufficient for course scripts, but not for compiling Hooks in C + +### Security recommendation + +Since free sandboxes are public, **never put mainnet seeds or private keys** in CodeSandbox. Only use **testnet** keys (tokens with no real value). To work with mainnet, use a local environment with VS Code.`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Instalar xahau en CodeSandbox (terminal)", + en: "Install xahau in CodeSandbox (terminal)", + jp: "", + }, + language: "bash", + code: `# In the CodeSandbox terminal: + +# 1. Install the xahau library +npm install xahau + +# 2. Create a test file +touch hola-xahau.js + +# 3. Run the script (after writing the code) +node hola-xahau.js`, + }, + { + title: { + es: "Script de prueba para CodeSandbox", + en: "Test script for CodeSandbox", + jp: "", + }, + language: "javascript", + code: `// File: hola-xahau.js +// Copy this code into your sandbox and run: node hola-xahau.js + +const { Client } = require("xahau"); + +async function main() { + console.log("=== Xahau Academy - Connection Test ===\\n"); + + // Connect to the Xahau testnet + const client = new Client("wss://xahau-test.net"); + await client.connect(); + console.log("✅ Connected to Xahau Testnet"); + + // Get server info + const response = await client.request({ + command: "server_info" + }); + + const info = response.result.info; + console.log("📡 Network ID:", info.network_id); + console.log("🔢 Ledger:", info.validated_ledger.seq); + console.log("🏗️ Version:", info.build_version); + + await client.disconnect(); + console.log("\\n🎉 Your CodeSandbox environment is ready!"); + console.log(" You can now follow the Xahau Academy course."); +} + +main().catch(console.error);`, + }, + ], + slides: [ + { + title: { es: "CodeSandbox", en: "CodeSandbox", jp: "" }, + content: { + es: "IDE online gratuito en tu navegador\n\n• Sin instalar nada\n• Terminal integrada\n• Node.js preinstalado\n• codesandbox.io", + en: "Free online IDE in your browser\n\n• No installation needed\n• Integrated terminal\n• Node.js preinstalled\n• codesandbox.io", + jp: "", + }, + visual: "☁️", + }, + { + title: { es: "Configurar para Xahau", en: "Configure for Xahau", jp: "" }, + content: { + es: "1️⃣ Crear cuenta en codesandbox.io\n2️⃣ Crear Devbox con plantilla Node.js\n3️⃣ npm install xahau\n4️⃣ Crear archivo .js y escribir código\n5️⃣ node mi-archivo.js", + en: "1️⃣ Create account at codesandbox.io\n2️⃣ Create Devbox with Node.js template\n3️⃣ npm install xahau\n4️⃣ Create .js file and write code\n5️⃣ node mi-archivo.js", + jp: "", + }, + visual: "🛠️", + }, + { + title: { es: "Seguridad", en: "Security", jp: "" }, + content: { + es: "⚠️ Los sandboxes gratuitos son PÚBLICOS\n\n• NUNCA pongas seeds de mainnet\n• Usa SOLO claves de testnet\n• Para mainnet → entorno local con VS Code", + en: "⚠️ Free sandboxes are PUBLIC\n\n• NEVER put mainnet seeds\n• Use ONLY testnet keys\n• For mainnet → local environment with VS Code", + jp: "", + }, + visual: "🔒", + }, + ], + }, + { + id: "m0l4", + title: { + es: "Estructura de un proyecto Node.js", + en: "Structure of a Node.js Project", + jp: "", + }, + theory: { + es: `Ahora que tienes Node.js instalado y la librería \`xahau\` descargada, es importante entender **cómo se organiza un proyecto Node.js** antes de empezar a escribir código que interactúe con la blockchain. + +### ¿Qué es package.json? + +El archivo \`package.json\` es la **ficha técnica de tu proyecto**. Se crea automáticamente cuando ejecutas \`npm init -y\` y contiene: + +- **name**: El nombre de tu proyecto +- **version**: La versión actual +- **description**: Una descripción breve +- **main**: El archivo principal (por defecto \`index.js\`) +- **scripts**: Comandos personalizados que puedes ejecutar con \`npm run\` +- **dependencies**: Las librerías que tu proyecto necesita para funcionar (como \`xahau\`) + +Cuando ejecutas \`npm install xahau\`, npm descarga la librería y la registra automáticamente en el campo \`dependencies\` del \`package.json\`. + +### ¿Qué es node_modules/? + +La carpeta \`node_modules/\` es donde npm descarga todas las librerías que tu proyecto necesita. Contiene: + +- La librería \`xahau\` que instalaste +- Todas las **dependencias internas** de esa librería (otras librerías que necesita para funcionar) +- Puede contener cientos o miles de archivos + +**Regla importante**: **Nunca compartas ni subas \`node_modules/\` a repositorios ni a otros ordenadores.** Esta carpeta se puede recrear en cualquier momento ejecutando \`npm install\` (npm lee el \`package.json\` y descarga todo de nuevo). Si usas Git, añade \`node_modules/\` al archivo \`.gitignore\`. + +### ¿Qué es require() y cómo importar librerías? + +En Node.js, usamos \`require()\` para **importar librerías** y usarlas en nuestro código: + +\`\`\` +const { Client, Wallet } = require("xahau"); +\`\`\` + +Esta línea hace lo siguiente: +1. Busca la librería \`xahau\` dentro de \`node_modules/\` +2. Importa los objetos \`Client\` y \`Wallet\` de esa librería +3. Los almacena en constantes que puedes usar en tu código + +También puedes importar archivos propios: + +\`\`\` +const misFunciones = require("./utils.js"); +\`\`\` + +El \`./\` al inicio indica que el archivo está en el directorio actual. + +### Crear y organizar archivos .js + +Cada script del curso será un archivo \`.js\` independiente. Recomendamos esta organización: + +\`\`\` +xahau-curso/ +├── package.json +├── node_modules/ +├── 01-conexion.js +├── 02-wallet.js +├── 03-balance.js +├── 04-pago.js +└── utils.js ← Funciones compartidas (opcional) +\`\`\` + +Cada archivo se ejecuta de forma independiente con \`node nombre-archivo.js\`. + +### async/await: operaciones asíncronas + +Cuando tu código se comunica con la blockchain, las operaciones **tardan un tiempo** (conectarse al nodo, enviar transacciones, esperar respuestas). JavaScript usa **async/await** para manejar estas operaciones sin bloquear el programa: + +- **async**: Marca una función como asíncrona (puede contener operaciones que tardan) +- **await**: Pausa la ejecución hasta que la operación termine y devuelva un resultado + +\`\`\` +async function consultar() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); // Espera a que se conecte + const datos = await client.request({ command: "server_info" }); // Espera la respuesta + await client.disconnect(); // Espera a que se desconecte +} +\`\`\` + +Sin \`await\`, el código intentaría usar la respuesta antes de recibirla, causando errores. + +### Manejo de errores con try/catch + +Las operaciones con la blockchain pueden fallar: el nodo puede estar caído, la red lenta, o el código puede tener un error. Usamos **try/catch** para capturar estos errores de forma controlada: + +\`\`\` +try { + // Código que puede fallar + await client.connect(); +} catch (error) { + // Se ejecuta si algo falla + console.error("Error:", error.message); +} +\`\`\` + +**try** intenta ejecutar el código. Si algo falla, el flujo salta directamente al bloque **catch**, donde puedes mostrar el error o tomar una acción alternativa. Sin \`try/catch\`, un error detendría todo el programa abruptamente.`, + en: `Now that you have Node.js installed and the \`xahau\` library downloaded, it's important to understand **how a Node.js project is organized** before you start writing code that interacts with the blockchain. + +### What is package.json? + +The \`package.json\` file is your **project's technical spec sheet**. It is created automatically when you run \`npm init -y\` and contains: + +- **name**: Your project's name +- **version**: The current version +- **description**: A brief description +- **main**: The main file (by default \`index.js\`) +- **scripts**: Custom commands you can run with \`npm run\` +- **dependencies**: The libraries your project needs to work (like \`xahau\`) + +When you run \`npm install xahau\`, npm downloads the library and automatically registers it in the \`dependencies\` field of \`package.json\`. + +### What is node_modules/? + +The \`node_modules/\` folder is where npm downloads all the libraries your project needs. It contains: + +- The \`xahau\` library you installed +- All the **internal dependencies** of that library (other libraries it needs to work) +- It can contain hundreds or thousands of files + +**Important rule**: **Never share or upload \`node_modules/\` to repositories or other computers.** This folder can be recreated at any time by running \`npm install\` (npm reads the \`package.json\` and downloads everything again). If you use Git, add \`node_modules/\` to the \`.gitignore\` file. + +### What is require() and how to import libraries? + +In Node.js, we use \`require()\` to **import libraries** and use them in our code: + +\`\`\` +const { Client, Wallet } = require("xahau"); +\`\`\` + +This line does the following: +1. Looks for the \`xahau\` library inside \`node_modules/\` +2. Imports the \`Client\` and \`Wallet\` objects from that library +3. Stores them in constants you can use in your code + +You can also import your own files: + +\`\`\` +const misFunciones = require("./utils.js"); +\`\`\` + +The \`./\` at the beginning indicates the file is in the current directory. + +### Creating and organizing .js files + +Each course script will be an independent \`.js\` file. We recommend this organization: + +\`\`\` +xahau-curso/ +├── package.json +├── node_modules/ +├── 01-conexion.js +├── 02-wallet.js +├── 03-balance.js +├── 04-pago.js +└── utils.js ← Shared functions (optional) +\`\`\` + +Each file is executed independently with \`node filename.js\`. + +### async/await: asynchronous operations + +When your code communicates with the blockchain, operations **take time** (connecting to the node, sending transactions, waiting for responses). JavaScript uses **async/await** to handle these operations without blocking the program: + +- **async**: Marks a function as asynchronous (it can contain operations that take time) +- **await**: Pauses execution until the operation finishes and returns a result + +\`\`\` +async function consultar() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); // Wait for it to connect + const datos = await client.request({ command: "server_info" }); // Wait for the response + await client.disconnect(); // Wait for it to disconnect +} +\`\`\` + +Without \`await\`, the code would try to use the response before receiving it, causing errors. + +### Error handling with try/catch + +Blockchain operations can fail: the node might be down, the network slow, or the code might have a bug. We use **try/catch** to capture these errors in a controlled way: + +\`\`\` +try { + // Code that might fail + await client.connect(); +} catch (error) { + // Runs if something fails + console.error("Error:", error.message); +} +\`\`\` + +**try** attempts to execute the code. If something fails, the flow jumps directly to the **catch** block, where you can display the error or take an alternative action. Without \`try/catch\`, an error would stop the entire program abruptly.`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Ejemplo de package.json explicado", + en: "package.json example explained", + jp: "", + }, + language: "javascript", + code: `// File: package.json (created with npm init -y) +// You do NOT need to edit this file manually. +// npm updates it when you install libraries. + +{ + "name": "xahau-curso", // Project name + "version": "1.0.0", // Project version + "description": "", // Description (you can fill it in) + "main": "index.js", // Main file (we won't use it) + "scripts": { + "test": "echo \\"Error: no test specified\\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "xahau": "^1.0.0" // <-- npm install xahau added this + } +} + +// NOTE: node_modules/ is created automatically with npm install. +// Never share it. It is regenerated with: npm install`, + }, + { + title: { + es: "Script básico con async/await y try/catch", + en: "Basic script with async/await and try/catch", + jp: "", + }, + language: "javascript", + code: `// File: estructura-basica.js +// Run with: node estructura-basica.js + +// 1. Import the xahau library from node_modules/ +const { Client, Wallet } = require("xahau"); + +// 2. Create an asynchronous (async) function +async function main() { + console.log("=== Basic structure of a Xahau script ===\\n"); + + // 3. Use try/catch to handle errors + try { + // 4. await waits for each operation to finish + const client = new Client("wss://xahau-test.net"); + console.log("Connecting to the node..."); + await client.connect(); + console.log("Connected successfully.\\n"); + + // 5. Query the blockchain + const response = await client.request({ + command: "server_info" + }); + + const info = response.result.info; + console.log("Server information:"); + console.log(" Network:", info.network_id); + console.log(" Version:", info.build_version); + console.log(" Ledger:", info.validated_ledger.seq); + + // 6. Disconnect cleanly + await client.disconnect(); + console.log("\\nDisconnected correctly."); + + } catch (error) { + // 7. If something fails, we show the error without crashing the program + console.error("\\nError found!"); + console.error("Type:", error.name); + console.error("Message:", error.message); + } +} + +// 8. Execute the main function +main();`, + }, + ], + slides: [ + { + title: { es: "Anatomía de un proyecto Node.js", en: "Anatomy of a Node.js Project", jp: "" }, + content: { + es: "package.json → Ficha técnica del proyecto\n\nnode_modules/ → Librerías descargadas\n (nunca compartir, se regenera con npm install)\n\narchivo.js → Tu código\n (se ejecuta con: node archivo.js)", + en: "package.json → Project's technical spec sheet\n\nnode_modules/ → Downloaded libraries\n (never share, regenerated with npm install)\n\nfile.js → Your code\n (run with: node file.js)", + jp: "", + }, + visual: "📁", + }, + { + title: { es: "require() e importaciones", en: "require() and imports", jp: "" }, + content: { + es: "Importar librerías instaladas:\nconst { Client, Wallet } = require(\"xahau\");\n\nImportar archivos propios:\nconst utils = require(\"./utils.js\");\n\nrequire() busca en node_modules/ o en la ruta indicada", + en: "Import installed libraries:\nconst { Client, Wallet } = require(\"xahau\");\n\nImport your own files:\nconst utils = require(\"./utils.js\");\n\nrequire() searches in node_modules/ or in the specified path", + jp: "", + }, + visual: "📦", + }, + { + title: { es: "async/await y try/catch", en: "async/await and try/catch", jp: "" }, + content: { + es: "async → Marca funciones que hacen operaciones lentas\nawait → Espera a que la operación termine\n\ntry { } → Intenta ejecutar el código\ncatch (error) { } → Captura errores sin romper el programa\n\nIndispensables para trabajar con blockchain", + en: "async → Marks functions that perform slow operations\nawait → Waits for the operation to finish\n\ntry { } → Attempts to execute the code\ncatch (error) { } → Catches errors without crashing the program\n\nEssential for working with blockchain", + jp: "", + }, + visual: "⏳", + }, + ], + }, + { + id: "m0l5", + title: { + es: "Ejecutar y depurar scripts", + en: "Running and Debugging Scripts", + jp: "", + }, + theory: { + es: `Ya sabes cómo se estructura un proyecto Node.js. Ahora vamos a aprender a **ejecutar scripts** y, lo más importante, a **entender y solucionar los errores** que inevitablemente aparecerán. + +### Ejecutar scripts con Node.js + +Para ejecutar cualquier archivo JavaScript, usa el comando: + +\`\`\` +node nombre-del-archivo.js +\`\`\` + +Por ejemplo: +\`\`\` +node hola-xahau.js +node 01-conexion.js +node mi-script.js +\`\`\` + +**Importante**: Debes estar en el directorio donde está el archivo, o usar la ruta completa. Si el archivo no se encuentra, verás un error. + +### Leer mensajes de error (stack traces) + +Cuando algo falla, Node.js muestra un **stack trace** — un mensaje con información sobre el error. Aprende a leerlo: + +\`\`\` +/Users/tu-nombre/xahau-curso/mi-script.js:5 + const response = await client.request({ + ^^^^^ +SyntaxError: await is only valid in async functions + at Object.compileFunction (node:vm:360:18) + at wrapSafe (node:internal/modules/cjs/loader:1124:15) + at /Users/tu-nombre/xahau-curso/mi-script.js:5:20 +\`\`\` + +Cómo leerlo: +1. **Primera línea**: El archivo y la línea donde ocurrió el error (\`mi-script.js:5\`) +2. **Tipo de error**: \`SyntaxError\`, \`TypeError\`, \`ReferenceError\`, etc. +3. **Mensaje**: Explicación del problema (\`await is only valid in async functions\`) +4. **Stack trace**: Ruta de ejecución que llevó al error (de más reciente a más antiguo) + +### Usar console.log para depurar + +\`console.log()\` es tu mejor herramienta de depuración. Úsala para ver el valor de variables en cualquier punto del código: + +\`\`\` +console.log("Paso 1: Conectando..."); +console.log("Valor de response:", response); +console.log("Tipo de dato:", typeof variable); +console.log("Objeto completo:", JSON.stringify(objeto, null, 2)); +\`\`\` + +**Tip**: Usa \`JSON.stringify(objeto, null, 2)\` para imprimir objetos grandes de forma legible (con indentación de 2 espacios). + +### Errores comunes y cómo solucionarlos + +**Error: Cannot find module 'xahau'** +\`\`\` +Error: Cannot find module 'xahau' +\`\`\` +Causa: No has instalado la librería o no estás en el directorio correcto. +Solución: Ejecuta \`npm install xahau\` en la carpeta de tu proyecto. + +**Error: await is only valid in async functions** +\`\`\` +SyntaxError: await is only valid in async functions +\`\`\` +Causa: Estás usando \`await\` fuera de una función marcada con \`async\`. +Solución: Envuelve tu código en una función \`async\`: +\`\`\` +async function main() { ... } +main(); +\`\`\` + +**Error: Unexpected token** +\`\`\` +SyntaxError: Unexpected token ')' +\`\`\` +Causa: Error de sintaxis — falta una coma, un paréntesis, una llave, etc. +Solución: Revisa la línea indicada y las líneas anteriores. Busca paréntesis o llaves sin cerrar. + +**Error: connect ETIMEDOUT / ECONNREFUSED** +\`\`\` +Error: connect ETIMEDOUT wss://xahau-test.net +\`\`\` +Causa: No se puede conectar al nodo de Xahau (red caída, firewall, sin internet). +Solución: Verifica tu conexión a internet. Si persiste, prueba otro nodo o espera unos minutos. + +**Error: Account not found** +\`\`\` +Error: Account not found. +\`\`\` +Causa: La cuenta que estás consultando no existe en el ledger o no ha sido activada. +Solución: Verifica que la dirección sea correcta. En testnet, usa el faucet para activar cuentas. + +### Tips para depurar conexiones blockchain + +1. **Prueba la conexión primero**: Antes de hacer operaciones complejas, verifica que puedes conectarte al nodo +2. **Usa try/catch siempre**: Cualquier operación de red puede fallar +3. **Revisa la URL del nodo**: \`wss://xahau-test.net\` para testnet, \`wss://xahau.network\` para mainnet +4. **Desconecta siempre al terminar**: Usa \`await client.disconnect()\` para liberar recursos +5. **Añade timeouts**: Si una operación tarda demasiado, puede que el nodo esté saturado`, + en: `You already know how a Node.js project is structured. Now we are going to learn how to **run scripts** and, most importantly, how to **understand and fix the errors** that will inevitably appear. + +### Running scripts with Node.js + +To run any JavaScript file, use the command: + +\`\`\` +node filename.js +\`\`\` + +For example: +\`\`\` +node hola-xahau.js +node 01-conexion.js +node mi-script.js +\`\`\` + +**Important**: You must be in the directory where the file is located, or use the full path. If the file is not found, you will see an error. + +### Reading error messages (stack traces) + +When something fails, Node.js displays a **stack trace** — a message with information about the error. Learn to read it: + +\`\`\` +/Users/your-name/xahau-curso/mi-script.js:5 + const response = await client.request({ + ^^^^^ +SyntaxError: await is only valid in async functions + at Object.compileFunction (node:vm:360:18) + at wrapSafe (node:internal/modules/cjs/loader:1124:15) + at /Users/your-name/xahau-curso/mi-script.js:5:20 +\`\`\` + +How to read it: +1. **First line**: The file and line where the error occurred (\`mi-script.js:5\`) +2. **Error type**: \`SyntaxError\`, \`TypeError\`, \`ReferenceError\`, etc. +3. **Message**: Explanation of the problem (\`await is only valid in async functions\`) +4. **Stack trace**: Execution path that led to the error (from most recent to oldest) + +### Using console.log for debugging + +\`console.log()\` is your best debugging tool. Use it to see the value of variables at any point in the code: + +\`\`\` +console.log("Step 1: Connecting..."); +console.log("Value of response:", response); +console.log("Data type:", typeof variable); +console.log("Full object:", JSON.stringify(object, null, 2)); +\`\`\` + +**Tip**: Use \`JSON.stringify(object, null, 2)\` to print large objects in a readable format (with 2-space indentation). + +### Common errors and how to fix them + +**Error: Cannot find module 'xahau'** +\`\`\` +Error: Cannot find module 'xahau' +\`\`\` +Cause: You haven't installed the library or you're not in the correct directory. +Solution: Run \`npm install xahau\` in your project folder. + +**Error: await is only valid in async functions** +\`\`\` +SyntaxError: await is only valid in async functions +\`\`\` +Cause: You are using \`await\` outside of a function marked with \`async\`. +Solution: Wrap your code in an \`async\` function: +\`\`\` +async function main() { ... } +main(); +\`\`\` + +**Error: Unexpected token** +\`\`\` +SyntaxError: Unexpected token ')' +\`\`\` +Cause: Syntax error — a comma, parenthesis, brace, etc. is missing. +Solution: Check the indicated line and the lines before it. Look for unclosed parentheses or braces. + +**Error: connect ETIMEDOUT / ECONNREFUSED** +\`\`\` +Error: connect ETIMEDOUT wss://xahau-test.net +\`\`\` +Cause: Cannot connect to the Xahau node (network down, firewall, no internet). +Solution: Check your internet connection. If it persists, try another node or wait a few minutes. + +**Error: Account not found** +\`\`\` +Error: Account not found. +\`\`\` +Cause: The account you are querying does not exist in the ledger or has not been activated. +Solution: Verify that the address is correct. On testnet, use the faucet to activate accounts. + +### Tips for debugging blockchain connections + +1. **Test the connection first**: Before performing complex operations, verify that you can connect to the node +2. **Always use try/catch**: Any network operation can fail +3. **Check the node URL**: \`wss://xahau-test.net\` for testnet, \`wss://xahau.network\` for mainnet +4. **Always disconnect when done**: Use \`await client.disconnect()\` to free resources +5. **Add timeouts**: If an operation takes too long, the node might be overloaded`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Script con manejo de errores y depuración", + en: "Script with error handling and debugging", + jp: "", + }, + language: "javascript", + code: `// File: depurar-errores.js +// Run with: node depurar-errores.js +// This script shows how to handle errors step by step. + +const { Client } = require("xahau"); + +async function main() { + console.log("=== Error Debugging in Xahau ===\\n"); + + // Step 1: Verify that the library was imported correctly + console.log("1. xahau library imported correctly"); + console.log(" Type of Client:", typeof Client); + + // Step 2: Create the client + const client = new Client("wss://xahau-test.net"); + console.log("2. Client created for:", "wss://xahau-test.net"); + + // Step 3: Try to connect with error handling + try { + console.log("3. Attempting to connect..."); + await client.connect(); + console.log(" Connected successfully"); + } catch (error) { + console.error(" ERROR connecting:", error.message); + console.error(" Possible causes:"); + console.error(" - No internet connection"); + console.error(" - The node is down"); + console.error(" - Firewall blocking WebSocket"); + return; // Exit the function if we can't connect + } + + // Step 4: Make a query + try { + console.log("4. Querying server_info..."); + const response = await client.request({ + command: "server_info" + }); + + // Step 5: Inspect the response + console.log("5. Response received:"); + console.log(" Type:", typeof response); + console.log(" Keys:", Object.keys(response.result)); + + const info = response.result.info; + console.log(" Network:", info.network_id); + console.log(" Ledger:", info.validated_ledger.seq); + } catch (error) { + console.error(" ERROR in query:", error.message); + } + + // Step 6: Disconnect + try { + await client.disconnect(); + console.log("6. Disconnected correctly"); + } catch (error) { + console.error(" ERROR disconnecting:", error.message); + } + + console.log("\\n=== End of debugging ==="); +} + +main();`, + }, + { + title: { + es: "Test de conectividad y errores comunes", + en: "Connectivity test and common errors", + jp: "", + }, + language: "javascript", + code: `// File: test-conectividad.js +// Run with: node test-conectividad.js +// Tests the connection and shows common errors. + +const { Client } = require("xahau"); + +// Helper function to test a connection +async function testConexion(url, nombre) { + console.log("Testing:", nombre, "(" + url + ")"); + + const client = new Client(url); + + try { + await client.connect(); + const response = await client.request({ command: "server_info" }); + const ledger = response.result.info.validated_ledger.seq; + console.log(" ✅ Connected - Ledger:", ledger); + await client.disconnect(); + return true; + } catch (error) { + console.log(" ❌ Error:", error.message); + return false; + } +} + +async function main() { + console.log("=== Xahau Connectivity Test ===\\n"); + + // Test 1: Connection to testnet (should work) + await testConexion("wss://xahau-test.net", "Xahau Testnet"); + + console.log(""); + + // Test 2: Connection to mainnet (should work) + await testConexion("wss://xahau.network", "Xahau Mainnet"); + + console.log(""); + + // Test 3: Incorrect URL (should fail - error example) + await testConexion("wss://nodo-que-no-existe.example.com", "Incorrect URL"); + + console.log("\\n=== Summary ==="); + console.log("If testnet and mainnet connect: your environment is ready."); + console.log("If any fails: check your internet connection."); + console.log("The incorrect URL MUST fail (it's an error test)."); +} + +main();`, + }, + ], + slides: [ + { + title: { es: "Ejecutar scripts", en: "Running Scripts", jp: "" }, + content: { + es: "Comando básico:\nnode nombre-archivo.js\n\nDebes estar en la carpeta del proyecto\n(donde está package.json y node_modules/)\n\nEjemplo:\ncd xahau-curso\nnode hola-xahau.js", + en: "Basic command:\nnode filename.js\n\nYou must be in the project folder\n(where package.json and node_modules/ are)\n\nExample:\ncd xahau-curso\nnode hola-xahau.js", + jp: "", + }, + visual: "▶️", + }, + { + title: { es: "Leer errores (stack trace)", en: "Reading Errors (Stack Trace)", jp: "" }, + content: { + es: "1. Archivo y línea del error → mi-script.js:5\n2. Tipo de error → SyntaxError, TypeError...\n3. Mensaje → Qué salió mal\n4. Stack trace → Ruta de ejecución\n\nSiempre empieza leyendo el TIPO y el MENSAJE", + en: "1. File and line of the error → mi-script.js:5\n2. Error type → SyntaxError, TypeError...\n3. Message → What went wrong\n4. Stack trace → Execution path\n\nAlways start by reading the TYPE and MESSAGE", + jp: "", + }, + visual: "🔍", + }, + { + title: { es: "Errores más comunes", en: "Most Common Errors", jp: "" }, + content: { + es: "Cannot find module 'xahau'\n → npm install xahau\n\nawait is only valid in async functions\n → Envolver en async function\n\nconnect ETIMEDOUT\n → Verificar internet / nodo\n\nUnexpected token\n → Revisar sintaxis (comas, llaves)", + en: "Cannot find module 'xahau'\n → npm install xahau\n\nawait is only valid in async functions\n → Wrap in async function\n\nconnect ETIMEDOUT\n → Check internet / node\n\nUnexpected token\n → Check syntax (commas, braces)", + jp: "", + }, + visual: "⚠️", + }, + ], + }, + { + id: "m0l6", + title: { + es: "Guardar claves de forma segura con .env", + en: "Storing Keys Securely with .env", + jp: "", + }, + theory: { + es: `A lo largo del curso vamos a trabajar con **seeds** (claves privadas) de cuentas de Xahau. Es fundamental que aprendas desde el principio a guardarlas de forma segura, incluso en testnet, para crear buenos hábitos que te protejan en mainnet. + +### ¿Por qué NO poner claves directamente en el código? + +Imagina que tienes esto en tu script: + +\`\`\` +const wallet = Wallet.fromSeed("sEdV9mHTYLPKPPPfBGB9xpGnFxsQo4r", {algorithm: 'secp256k1'}); +\`\`\` + +Esto es **muy peligroso** por varias razones: + +- Si subes tu código a **GitHub** (u otro repositorio), cualquiera puede ver tu clave privada y robar tus fondos +- Si compartes el archivo con alguien (por email, chat, etc.), estás compartiendo tu clave +- Los bots de GitHub **escanean repositorios públicos** buscando claves privadas expuestas y roban fondos automáticamente en segundos +- Incluso si borras la clave después, el historial de Git **la conserva** y sigue siendo accesible + +### ¿Qué es un archivo .env? + +Un archivo \`.env\` (de "environment", entorno) es un archivo de texto plano que almacena **variables de entorno** — configuraciones sensibles que tu código necesita pero que no deben estar en el código fuente: + +\`\`\` +WALLET_A_SEED=sEdVxxxTuSeedDeTestnet +WALLET_B_SEED=sEdYyyOtraSeedDeTestnet +XAHAU_NODE=wss://xahau-test.net +\`\`\` + +### Reglas del archivo .env + +- **Nunca subas .env a Git**: Añádelo siempre a \`.gitignore\` +- **Un .env por entorno**: Puedes tener uno para testnet y otro para mainnet +- **Sin comillas** (a menos que el valor tenga espacios): \`CLAVE=valor\` +- **Sin espacios** alrededor del \`=\`: \`CLAVE=valor\` (correcto) vs \`CLAVE = valor\` (incorrecto) +- **Cada variable en una línea** + +### Instalar dotenv + +La librería \`dotenv\` lee el archivo \`.env\` y carga las variables en \`process.env\`: + +\`\`\` +npm install dotenv +\`\`\` + +### Cómo usar dotenv en tu código + +Al inicio de tu script, añade una sola línea: + +\`\`\` +require("dotenv").config(); +\`\`\` + +Esto carga todas las variables del archivo \`.env\` en el objeto \`process.env\`. Después puedes acceder a ellas así: + +\`\`\` +const seed = process.env.WALLET_A_SEED; +const nodo = process.env.XAHAU_NODE; +\`\`\` + +### Crear el archivo .gitignore + +El archivo \`.gitignore\` le dice a Git qué archivos **no debe rastrear ni subir** al repositorio. Crea un archivo llamado \`.gitignore\` en la raíz de tu proyecto con este contenido: + +\`\`\` +.env +node_modules/ +\`\`\` + +Esto protege tanto tus claves (\`.env\`) como las librerías descargadas (\`node_modules/\`). + +### Flujo de trabajo recomendado + +1. Crea tu archivo \`.env\` con las claves +2. Crea o actualiza tu \`.gitignore\` para excluir \`.env\` +3. En cada script, carga dotenv al inicio: \`require("dotenv").config()\` +4. Accede a las claves con \`process.env.NOMBRE_VARIABLE\` +5. Si compartes tu código, crea un archivo \`.env.example\` (sin valores reales) para que otros sepan qué variables necesitan + +### ¿Y en producción / mainnet? + +En un servidor o entorno de producción, las variables de entorno se configuran directamente en el sistema operativo o en el panel de tu proveedor de hosting (Vercel, Railway, AWS, etc.), **sin necesidad del archivo .env**. La librería \`dotenv\` solo se usa en desarrollo local. + +### Implicaciones de seguridad + +- **Testnet**: Si se filtra un seed de testnet, no pierdes dinero real, pero alguien podría interferir con tus pruebas +- **Mainnet**: Si se filtra un seed de mainnet, **puedes perder todos tus fondos de forma irreversible**. No hay forma de recuperar fondos robados en una blockchain +- **Repositorios públicos**: Una vez que un seed se sube a un repo público, considéralo **comprometido**. Mueve tus fondos a una nueva cuenta inmediatamente +- **Historial de Git**: Incluso si borras el archivo, el seed sigue en el historial. Necesitarías reescribir la historia de Git, lo cual es complicado`, + en: `Throughout the course we will work with **seeds** (private keys) of Xahau accounts. It is essential that you learn from the beginning how to store them securely, even on testnet, to build good habits that will protect you on mainnet. + +### Why NOT put keys directly in the code? + +Imagine you have this in your script: + +\`\`\` +const wallet = Wallet.fromSeed("sEdV9mHTYLPKPPPfBGB9xpGnFxsQo4r", {algorithm: 'secp256k1'}); +\`\`\` + +This is **very dangerous** for several reasons: + +- If you upload your code to **GitHub** (or another repository), anyone can see your private key and steal your funds +- If you share the file with someone (via email, chat, etc.), you are sharing your key +- GitHub bots **scan public repositories** looking for exposed private keys and steal funds automatically within seconds +- Even if you delete the key afterwards, the Git history **preserves it** and it remains accessible + +### What is a .env file? + +A \`.env\` file (short for "environment") is a plain text file that stores **environment variables** — sensitive configurations your code needs but that should not be in the source code: + +\`\`\` +WALLET_A_SEED=sEdVxxxYourTestnetSeed +WALLET_B_SEED=sEdYyyAnotherTestnetSeed +XAHAU_NODE=wss://xahau-test.net +\`\`\` + +### .env file rules + +- **Never upload .env to Git**: Always add it to \`.gitignore\` +- **One .env per environment**: You can have one for testnet and another for mainnet +- **No quotes** (unless the value has spaces): \`KEY=value\` +- **No spaces** around \`=\`: \`KEY=value\` (correct) vs \`KEY = value\` (incorrect) +- **Each variable on its own line** + +### Install dotenv + +The \`dotenv\` library reads the \`.env\` file and loads the variables into \`process.env\`: + +\`\`\` +npm install dotenv +\`\`\` + +### How to use dotenv in your code + +At the beginning of your script, add a single line: + +\`\`\` +require("dotenv").config(); +\`\`\` + +This loads all variables from the \`.env\` file into the \`process.env\` object. Then you can access them like this: + +\`\`\` +const seed = process.env.WALLET_A_SEED; +const node = process.env.XAHAU_NODE; +\`\`\` + +### Create the .gitignore file + +The \`.gitignore\` file tells Git which files **it should not track or upload** to the repository. Create a file named \`.gitignore\` in the root of your project with this content: + +\`\`\` +.env +node_modules/ +\`\`\` + +This protects both your keys (\`.env\`) and the downloaded libraries (\`node_modules/\`). + +### Recommended workflow + +1. Create your \`.env\` file with the keys +2. Create or update your \`.gitignore\` to exclude \`.env\` +3. In each script, load dotenv at the beginning: \`require("dotenv").config()\` +4. Access keys with \`process.env.VARIABLE_NAME\` +5. If you share your code, create a \`.env.example\` file (without real values) so others know which variables they need + +### What about production / mainnet? + +On a server or production environment, environment variables are configured directly in the operating system or in your hosting provider's panel (Vercel, Railway, AWS, etc.), **without needing the .env file**. The \`dotenv\` library is only used in local development. + +### Security implications + +- **Testnet**: If a testnet seed is leaked, you don't lose real money, but someone could interfere with your tests +- **Mainnet**: If a mainnet seed is leaked, **you can lose all your funds irreversibly**. There is no way to recover stolen funds on a blockchain +- **Public repositories**: Once a seed is uploaded to a public repo, consider it **compromised**. Move your funds to a new account immediately +- **Git history**: Even if you delete the file, the seed remains in the history. You would need to rewrite Git history, which is complicated`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Crear el archivo .env", + en: "Create the .env file", + jp: "", + }, + language: "bash", + code: `# 1. Install the dotenv library +npm install dotenv + +# 2. Create the .env file (in the root of your project) +# IMPORTANT: This file is NOT uploaded to Git + +# Contents of the .env file: +# WALLET_A_SEED=sEdVxxxYourTestnetSeed +# WALLET_B_SEED=sEdYyyAnotherTestnetSeed +# XAHAU_NODE=wss://xahau-test.net + +# 3. Create the .gitignore file +# Contents of the .gitignore file: +# .env +# node_modules/ + +# 4. (Optional) Create .env.example to document the variables +# Contents of the .env.example file: +# WALLET_A_SEED=your_seed_here +# WALLET_B_SEED=your_seed_here +# XAHAU_NODE=wss://xahau-test.net`, + }, + { + title: { + es: "Script que usa variables de entorno con dotenv", + en: "Script that uses environment variables with dotenv", + jp: "", + }, + language: "javascript", + code: `// File: pago-seguro.js +// Run with: node pago-seguro.js +// Requires: .env file with WALLET_A_SEED, WALLET_B_SEED, and XAHAU_NODE + +// 1. Load environment variables from .env +require("dotenv").config(); + +const { Client, Wallet } = require("xahau"); + +async function main() { + // 2. Read keys from process.env (NOT from the code) + const seedA = process.env.WALLET_A_SEED; + const seedB = process.env.WALLET_B_SEED; + const node = process.env.XAHAU_NODE; + + // 3. Verify that the variables exist + if (!seedA || !seedB) { + console.error("Error: Missing variables in the .env file"); + console.error("Make sure WALLET_A_SEED and WALLET_B_SEED are defined."); + console.error("Copy .env.example to .env and fill in the values."); + return; + } + + if (!node) { + console.error("Error: XAHAU_NODE missing in .env"); + return; + } + + console.log("Variables loaded correctly from .env"); + console.log("Node:", node); + // NEVER console.log the seed — not even on testnet + + const client = new Client(node); + await client.connect(); + + // 4. Create wallets from the .env seeds + const walletA = Wallet.fromSeed(seedA, {algorithm: 'secp256k1'}); + const walletB = Wallet.fromSeed(seedB, {algorithm: 'secp256k1'}); + + console.log("Wallet A:", walletA.address); + console.log("Wallet B:", walletB.address); + + // 5. Send a payment from A to B + const payment = { + TransactionType: "Payment", + Account: walletA.address, + Destination: walletB.address, + Amount: "10000000", // 10 XAH + }; + + const result = await client.submitAndWait(payment, { wallet: walletA }); + console.log("Result:", result.result.meta.TransactionResult); + + await client.disconnect(); +} + +main().catch(console.error);`, + }, + { + title: { + es: "Ejemplo de .env.example (para compartir sin claves reales)", + en: ".env.example example (for sharing without real keys)", + jp: "", + }, + language: "bash", + code: `# File: .env.example +# Copy this file as .env and fill in with your real values: +# cp .env.example .env +# +# NEVER upload the .env file to Git. +# This .env.example file CAN be uploaded because it has no real keys. + +WALLET_A_SEED=your_testnet_seed_here +WALLET_B_SEED=your_testnet_seed_here +XAHAU_NODE=wss://xahau-test.net`, + }, + ], + slides: [ + { + title: { es: "¿Por qué usar .env?", en: "Why use .env?", jp: "" }, + content: { + es: "NUNCA pongas claves privadas en el código\n\n• Los bots escanean GitHub y roban fondos\n• El historial de Git conserva las claves\n• Compartir código = compartir claves\n\nSolución: archivo .env + .gitignore", + en: "NEVER put private keys in the code\n\n• Bots scan GitHub and steal funds\n• Git history preserves the keys\n• Sharing code = sharing keys\n\nSolution: .env file + .gitignore", + jp: "", + }, + visual: "🔐", + }, + { + title: { es: "Cómo usar dotenv", en: "How to use dotenv", jp: "" }, + content: { + es: "1. npm install dotenv\n2. Crear .env con tus claves\n3. Añadir .env a .gitignore\n4. En tu script: require(\"dotenv\").config()\n5. Leer: process.env.NOMBRE_VARIABLE", + en: "1. npm install dotenv\n2. Create .env with your keys\n3. Add .env to .gitignore\n4. In your script: require(\"dotenv\").config()\n5. Read: process.env.VARIABLE_NAME", + jp: "", + }, + visual: "📋", + }, + { + title: { es: "Buenas prácticas", en: "Best Practices", jp: "" }, + content: { + es: "• .env → Claves reales (NO subir a Git)\n• .env.example → Plantilla sin claves (SÍ subir)\n• .gitignore → Excluir .env y node_modules/\n• Nunca hacer console.log de un seed\n• En mainnet: un seed filtrado = fondos perdidos", + en: "• .env → Real keys (DO NOT upload to Git)\n• .env.example → Template without keys (DO upload)\n• .gitignore → Exclude .env and node_modules/\n• Never console.log a seed\n• On mainnet: a leaked seed = lost funds", + jp: "", + }, + visual: "✅", + }, + ], + }, + ], +} diff --git a/src/data/modules/m01-blockchain-no-evm.js b/src/data/modules/m01-blockchain-no-evm.js new file mode 100644 index 0000000..fb99919 --- /dev/null +++ b/src/data/modules/m01-blockchain-no-evm.js @@ -0,0 +1,923 @@ +export default { + id: "m1", + icon: "🧱", + title: { + es: "Arquitectura básica de una blockchain No-EVM", + en: "Basic Architecture of a Non-EVM Blockchain", + jp: "", + }, + lessons: [ + { + id: "m1l0", + title: { + es: "¿Qué es una blockchain?", + en: "What is a Blockchain?", + jp: "", + }, + theory: { + es: `Antes de hablar de blockchains No-EVM, necesitamos entender **qué es una blockchain** y por qué esta tecnología es revolucionaria. + +### Definición simple + +Una **blockchain** (cadena de bloques) es un **libro de registros digital, distribuido e inmutable**. Imagina un cuaderno contable que: +- Está **copiado en miles de ordenadores** por todo el mundo (distribuido) +- **Nadie puede borrar ni alterar** lo que ya se ha escrito (inmutable) +- **Cualquiera puede verificar** que los datos son correctos (transparente) +- **No necesita un intermediario** como un banco o una empresa (descentralizado) + +### ¿Cómo funciona? + +Los datos se agrupan en **bloques**. Cada bloque contiene: +1. Un conjunto de **transacciones** (por ejemplo: "Alice envía 10 tokens a Bob") +2. Un **hash** (huella digital única) del bloque +3. El **hash del bloque anterior**, creando así una cadena + +Esta estructura hace que modificar un bloque antiguo sea prácticamente imposible, porque cambiaría su hash y rompería toda la cadena posterior. + +### Conceptos clave + +**Descentralización** +No hay un servidor central. La red está formada por **nodos** (ordenadores) que mantienen una copia del libro de registros. No hay un punto único de fallo. + +**Inmutabilidad** +Una vez que una transacción se incluye en un bloque y se valida, **no se puede modificar ni eliminar**. Esto garantiza un historial fiable. + +**Consenso** +Los nodos necesitan un mecanismo para ponerse de acuerdo sobre qué transacciones son válidas. Esto se llama **protocolo de consenso** (lo veremos en detalle en el módulo 2). + +**Criptografía** +La blockchain usa funciones criptográficas para: +- **Hashes**: Identificar bloques y verificar integridad de datos +- **Firmas digitales**: Demostrar que una transacción fue autorizada por el propietario +- **Claves público/privada**: Cada usuario tiene un par de claves que actúa como su identidad + +**Transacciones** +Son las operaciones que modifican el estado de la blockchain: enviar tokens, crear un contrato, registrar un dato, etc. Cada transacción está **firmada digitalmente** por su emisor. + +### Blockchain vs Base de datos tradicional + +| Característica | Base de datos tradicional | Blockchain | +|---|---|---| +| Control | Una empresa (centralizada) | Red de nodos (descentralizada) | +| Modificación | Cualquiera con acceso puede editar | Inmutable una vez validado | +| Confianza | Confías en la empresa | Confías en la criptografía y el consenso | +| Transparencia | Privada por defecto | Pública y verificable | +| Intermediario | Necesario (banco, servidor) | No necesario (peer-to-peer) | + +### ¿Para qué sirve? + +Las blockchains se usan para: +- **Criptomonedas**: Enviar dinero sin bancos (Bitcoin, XAH) +- **Tokens**: Crear activos digitales propios +- **NFTs**: Certificar la propiedad de objetos digitales únicos +- **Smart contracts**: Ejecutar lógica programable de forma automática y confiable +- **Trazabilidad**: Registrar cadenas de suministro, certificados, votaciones, etc. + +### Tipos de blockchain + +- **Públicas**: Cualquiera puede participar (Bitcoin, Ethereum, Xahau) +- **Privadas/Permisionadas**: Solo miembros autorizados participan (Hyperledger) +- **Híbridas**: Combinan elementos de ambas + +En este curso nos centraremos en **Xahau**, una blockchain **pública** diseñada para pagos rápidos, tokens y smart contracts eficientes.`, + en: `Before talking about Non-EVM blockchains, we need to understand **what a blockchain is** and why this technology is revolutionary. + +### Simple Definition + +A **blockchain** is a **digital, distributed, and immutable ledger**. Imagine an accounting book that: +- Is **copied across thousands of computers** around the world (distributed) +- **Nobody can erase or alter** what has already been written (immutable) +- **Anyone can verify** that the data is correct (transparent) +- **Does not need an intermediary** like a bank or a company (decentralized) + +### How Does It Work? + +Data is grouped into **blocks**. Each block contains: +1. A set of **transactions** (for example: "Alice sends 10 tokens to Bob") +2. A **hash** (unique digital fingerprint) of the block +3. The **hash of the previous block**, thus creating a chain + +This structure makes modifying an old block practically impossible, because it would change its hash and break the entire subsequent chain. + +### Key Concepts + +**Decentralization** +There is no central server. The network is made up of **nodes** (computers) that maintain a copy of the ledger. There is no single point of failure. + +**Immutability** +Once a transaction is included in a block and validated, **it cannot be modified or deleted**. This guarantees a reliable history. + +**Consensus** +Nodes need a mechanism to agree on which transactions are valid. This is called a **consensus protocol** (we will cover this in detail in module 2). + +**Cryptography** +The blockchain uses cryptographic functions for: +- **Hashes**: Identifying blocks and verifying data integrity +- **Digital signatures**: Proving that a transaction was authorized by its owner +- **Public/private keys**: Each user has a key pair that acts as their identity + +**Transactions** +These are the operations that modify the state of the blockchain: sending tokens, creating a contract, registering data, etc. Each transaction is **digitally signed** by its sender. + +### Blockchain vs Traditional Database + +| Feature | Traditional Database | Blockchain | +|---|---|---| +| Control | A company (centralized) | Network of nodes (decentralized) | +| Modification | Anyone with access can edit | Immutable once validated | +| Trust | You trust the company | You trust cryptography and consensus | +| Transparency | Private by default | Public and verifiable | +| Intermediary | Required (bank, server) | Not required (peer-to-peer) | + +### What Is It Used For? + +Blockchains are used for: +- **Cryptocurrencies**: Sending money without banks (Bitcoin, XAH) +- **Tokens**: Creating your own digital assets +- **NFTs**: Certifying ownership of unique digital objects +- **Smart contracts**: Executing programmable logic automatically and reliably +- **Traceability**: Recording supply chains, certificates, votes, etc. + +### Types of Blockchain + +- **Public**: Anyone can participate (Bitcoin, Ethereum, Xahau) +- **Private/Permissioned**: Only authorized members participate (Hyperledger) +- **Hybrid**: Combine elements of both + +In this course we will focus on **Xahau**, a **public** blockchain designed for fast payments, tokens, and efficient smart contracts.`, + jp: "", + }, + codeBlocks: [], + slides: [ + { + title: { es: "¿Qué es una blockchain?", en: "What is a Blockchain?", jp: "" }, + content: { + es: "Un libro de registros digital:\n\n• Distribuido → Copiado en miles de nodos\n• Inmutable → No se puede alterar\n• Transparente → Cualquiera puede verificar\n• Descentralizado → Sin intermediarios", + en: "A digital ledger:\n\n• Distributed → Copied across thousands of nodes\n• Immutable → Cannot be altered\n• Transparent → Anyone can verify\n• Decentralized → No intermediaries", + jp: "", + }, + visual: "📒", + }, + { + title: { es: "Cadena de bloques", en: "Chain of Blocks", jp: "" }, + content: { + es: "Bloque 1 → Bloque 2 → Bloque 3 → ...\n\nCada bloque contiene:\n• Transacciones\n• Hash propio (huella digital)\n• Hash del bloque anterior\n\nCambiar un bloque rompe toda la cadena", + en: "Block 1 → Block 2 → Block 3 → ...\n\nEach block contains:\n• Transactions\n• Its own hash (digital fingerprint)\n• Hash of the previous block\n\nChanging a block breaks the entire chain", + jp: "", + }, + visual: "🔗", + }, + { + title: { es: "Conceptos clave", en: "Key Concepts", jp: "" }, + content: { + es: "🔐 Criptografía → Hashes y firmas digitales\n🤝 Consenso → Nodos se ponen de acuerdo\n🔑 Claves → Tu identidad en la red\n📝 Transacciones → Operaciones firmadas", + en: "🔐 Cryptography → Hashes and digital signatures\n🤝 Consensus → Nodes agree with each other\n🔑 Keys → Your identity on the network\n📝 Transactions → Signed operations", + jp: "", + }, + visual: "🧩", + }, + { + title: { es: "¿Para qué sirve?", en: "What Is It Used For?", jp: "" }, + content: { + es: "• 💰 Criptomonedas (pagos sin bancos)\n• 🪙 Tokens (activos digitales)\n• 🎨 NFTs (objetos únicos)\n• 🪝 Smart contracts (lógica programable)\n• 📦 Trazabilidad (registros verificables)", + en: "• 💰 Cryptocurrencies (payments without banks)\n• 🪙 Tokens (digital assets)\n• 🎨 NFTs (unique objects)\n• 🪝 Smart contracts (programmable logic)\n• 📦 Traceability (verifiable records)", + jp: "", + }, + visual: "🌐", + }, + ], + }, + { + id: "m1l1", + title: { + es: "¿Qué es una blockchain No-EVM?", + en: "What is a Non-EVM Blockchain?", + jp: "", + }, + theory: { + es: `Cuando hablamos de blockchains, la mayoría de desarrolladores piensan en **Ethereum** y su máquina virtual (**EVM**). Sin embargo, existen blockchains que funcionan de manera completamente diferente, sin usar la EVM ni Solidity. + +### EVM vs No-EVM + +| Característica | Blockchain EVM | Blockchain No-EVM (Xahau) | +|---|---|---| +| Lenguaje de contratos | Solidity / Vyper | C (compilado a WebAssembly) | +| Máquina virtual | EVM (Ethereum Virtual Machine) | No usa VM, ejecución nativa WASM | +| Modelo de estado | Cuentas con storage arbitrario | Objetos del ledger tipados | +| Gas / Fees | Gas variable y costoso | Fees fijos y predecibles | +| Modelo de datos | Key-value en storage | Objetos nativos (AccountRoot, TrustLine, etc.) | + +### ¿Por qué No-EVM? + +Las blockchains No-EVM como **Xahau** fueron diseñadas desde cero para casos de uso específicos: pagos rápidos, tokenización y lógica programable eficiente. No intentan ser "computadoras de propósito general" como Ethereum, sino que optimizan para **rendimiento, bajo coste y finalidad rápida**. + +### Xahau: una blockchain No-EVM + +**Xahau** es una blockchain de capa 1 que hereda la arquitectura del **XRP Ledger (XRPL)** y le añade la capacidad de ejecutar **Hooks**, smart contracts ligeros escritos en C y compilados a WebAssembly. + +A diferencia de las redes EVM, en Xahau: +- Las transacciones son **nativas y tipadas** (Payment, TrustSet, OfferCreate, etc.) +- El ledger mantiene **objetos estructurados**, no estados arbitrarios +- Los smart contracts (Hooks) se ejecutan como **filtros reactivos** sobre las transacciones +- El token nativo es **XAH**`, + en: `When we talk about blockchains, most developers think of **Ethereum** and its virtual machine (**EVM**). However, there are blockchains that work in a completely different way, without using the EVM or Solidity. + +### EVM vs Non-EVM + +| Feature | EVM Blockchain | Non-EVM Blockchain (Xahau) | +|---|---|---| +| Contract language | Solidity / Vyper | C (compiled to WebAssembly) | +| Virtual machine | EVM (Ethereum Virtual Machine) | No VM, native WASM execution | +| State model | Accounts with arbitrary storage | Typed ledger objects | +| Gas / Fees | Variable and expensive gas | Fixed and predictable fees | +| Data model | Key-value in storage | Native objects (AccountRoot, TrustLine, etc.) | + +### Why Non-EVM? + +Non-EVM blockchains like **Xahau** were designed from scratch for specific use cases: fast payments, tokenization, and efficient programmable logic. They do not try to be "general-purpose computers" like Ethereum, but instead optimize for **performance, low cost, and fast finality**. + +### Xahau: a Non-EVM Blockchain + +**Xahau** is a layer 1 blockchain that inherits the architecture of the **XRP Ledger (XRPL)** and adds the ability to execute **Hooks**, lightweight smart contracts written in C and compiled to WebAssembly. + +Unlike EVM networks, in Xahau: +- Transactions are **native and typed** (Payment, TrustSet, OfferCreate, etc.) +- The ledger maintains **structured objects**, not arbitrary states +- Smart contracts (Hooks) execute as **reactive filters** on transactions +- The native token is **XAH**`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Conectar a un nodo Xahau y ver info del servidor", + en: "Connect to a Xahau node and view server info", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function serverInfo() { + const client = new Client("wss://xahau.network"); + await client.connect(); + + const response = await client.request({ + command: "server_info" + }); + + const info = response.result.info; + console.log("Network:", info.network_id); + console.log("Version:", info.build_version); + console.log("Current ledger:", info.validated_ledger.seq); + console.log("Network type: Non-EVM (Xahau blockchain)"); + + await client.disconnect(); +} + +serverInfo();`, + }, + ], + slides: [ + { + title: { es: "EVM vs No-EVM", en: "EVM vs Non-EVM", jp: "" }, + content: { + es: "EVM (Ethereum)\n• Solidity → Bytecode EVM\n• Gas variable\n• Estado arbitrario\n\nNo-EVM (Xahau)\n• C → WebAssembly\n• Fees fijos\n• Objetos tipados del ledger", + en: "EVM (Ethereum)\n• Solidity → EVM Bytecode\n• Variable gas\n• Arbitrary state\n\nNon-EVM (Xahau)\n• C → WebAssembly\n• Fixed fees\n• Typed ledger objects", + jp: "", + }, + visual: "⚖️", + }, + { + title: { es: "¿Qué es Xahau?", en: "What is Xahau?", jp: "" }, + content: { + es: "Blockchain de capa 1 basada en XRPL\n\n• Smart Contracts nativos (Hooks)\n• Token nativo: XAH\n• Transacciones tipadas\n• Fees bajos y predecibles\n• Finalidad en 3-5 segundos", + en: "Layer 1 blockchain based on XRPL\n\n• Native Smart Contracts (Hooks)\n• Native token: XAH\n• Typed transactions\n• Low and predictable fees\n• Finality in 3-5 seconds", + jp: "", + }, + visual: "🧱", + }, + { + title: { es: "Arquitectura del Ledger", en: "Ledger Architecture", jp: "" }, + content: { + es: "El ledger de Xahau contiene objetos nativos:\n\n• AccountRoot → Cuentas\n• TrustLine → Líneas de confianza\n• Offer → Órdenes de intercambio\n• URIToken → NFTs\n• Hook → Smart contracts\n• HookState → Estado de los Hooks", + en: "The Xahau ledger contains native objects:\n\n• AccountRoot → Accounts\n• TrustLine → Trust lines\n• Offer → Trade orders\n• URIToken → NFTs\n• Hook → Smart contracts\n• HookState → Hook state data", + jp: "", + }, + visual: "📦", + }, + ], + }, + { + id: "m1l2", + title: { + es: "Estructura del ledger en Xahau", + en: "Ledger Structure in Xahau", + jp: "", + }, + theory: { + es: `El **ledger** (libro mayor) de Xahau es una base de datos distribuida que almacena el estado completo de la red en un momento dado. Cada ledger tiene un **número de secuencia** único y contiene todos los objetos del estado actual. + +### Componentes del Ledger + +Cada versión del ledger incluye: +- **Ledger Header**: Metadatos (hash, secuencia, timestamp, fees) +- **State Tree**: Todos los objetos del ledger (cuentas, tokens, hooks, etc.) +- **Transaction Set**: Transacciones que produjeron este ledger + +### Tipos de objetos del Ledger + +Los objetos están **tipados** — cada tipo tiene campos específicos y predefinidos: + +- **AccountRoot**: Representa una cuenta con su balance, secuencia, flags y hooks instalados +- **RippleState (TrustLine)**: Línea de confianza entre dos cuentas para un token +- **Offer**: Orden de compra/venta en el DEX nativo +- **URIToken**: Token no fungible con URI asociado +- **HookDefinition**: Código WASM de un Hook desplegado +- **HookState**: Datos persistentes almacenados por un Hook + +### Diferencia clave con EVM + +En Ethereum, el estado es un **árbol de cuentas** donde cada cuenta tiene su propio **storage** (key-value arbitrario). En Xahau, el estado son **objetos tipados** con campos predefinidos. Esto es más restrictivo pero mucho más eficiente y fácil de consultar.`, + en: `The Xahau **ledger** is a distributed database that stores the complete state of the network at a given point in time. Each ledger has a unique **sequence number** and contains all objects of the current state. + +### Ledger Components + +Each ledger version includes: +- **Ledger Header**: Metadata (hash, sequence, timestamp, fees) +- **State Tree**: All ledger objects (accounts, tokens, hooks, etc.) +- **Transaction Set**: Transactions that produced this ledger + +### Ledger Object Types + +Objects are **typed** — each type has specific, predefined fields: + +- **AccountRoot**: Represents an account with its balance, sequence, flags, and installed hooks +- **RippleState (TrustLine)**: Trust line between two accounts for a token +- **Offer**: Buy/sell order on the native DEX +- **URIToken**: Non-fungible token with an associated URI +- **HookDefinition**: WASM code of a deployed Hook +- **HookState**: Persistent data stored by a Hook + +### Key Difference from EVM + +In Ethereum, the state is an **account tree** where each account has its own **storage** (arbitrary key-value). In Xahau, the state consists of **typed objects** with predefined fields. This is more restrictive but much more efficient and easier to query.`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Consultar información del ledger actual", + en: "Query current ledger information", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function getLedgerInfo() { + const client = new Client("wss://xahau.network"); + await client.connect(); + + const response = await client.request({ + command: "ledger", + ledger_index: "validated", + }); + + const ledger = response.result.ledger; + console.log("Ledger Seq:", ledger.ledger_index); + console.log("Hash:", ledger.ledger_hash); + console.log("Closed:", ledger.close_time_human); + + await client.disconnect(); +} + +getLedgerInfo();`, + }, + ], + slides: [ + { + title: { es: "El Ledger de Xahau", en: "The Xahau Ledger", jp: "" }, + content: { + es: "Base de datos distribuida con el estado completo\n\n• Cada ledger tiene un número de secuencia\n• Se cierra cada 3-5 segundos\n• Contiene todos los objetos del estado\n• Inmutable una vez validado", + en: "Distributed database with the complete state\n\n• Each ledger has a sequence number\n• Closes every 3-5 seconds\n• Contains all state objects\n• Immutable once validated", + jp: "", + }, + visual: "📖", + }, + { + title: { es: "Objetos del Ledger", en: "Ledger Objects", jp: "" }, + content: { + es: "Objetos tipados y estructurados:\n\n• AccountRoot → Cuentas\n• RippleState → TrustLines\n• Offer → Órdenes DEX\n• URIToken → NFTs\n• HookDefinition → Código de Hooks\n• HookState → Estado de Hooks", + en: "Typed and structured objects:\n\n• AccountRoot → Accounts\n• RippleState → TrustLines\n• Offer → DEX orders\n• URIToken → NFTs\n• HookDefinition → Hook code\n• HookState → Hook state data", + jp: "", + }, + visual: "🗂️", + }, + { + title: { es: "Detalle de objetos del Ledger", en: "Ledger Object Details", jp: "" }, + content: { + es: "Cada objeto tiene campos predefinidos:\n\n• AccountRoot → Balance, Sequence, Flags, Hooks\n• RippleState → Saldo entre dos cuentas para un token\n• Offer → Precio, cantidad, par de intercambio\n• DirectoryNode → Índice que conecta objetos\n\nDiferencia con EVM:\n• Sin storage arbitrario (key-value)\n• Campos fijos → consultas más eficientes", + en: "Each object has predefined fields:\n\n• AccountRoot → Balance, Sequence, Flags, Hooks\n• RippleState → Balance between two accounts for a token\n• Offer → Price, amount, trading pair\n• DirectoryNode → Index connecting objects\n\nDifference from EVM:\n• No arbitrary storage (key-value)\n• Fixed fields → more efficient queries", + jp: "", + }, + visual: "🔍", + }, + ], + }, + { + id: "m1l3", + title: { + es: "Historia de las blockchains: de Bitcoin a Xahau", + en: "History of Blockchains: from Bitcoin to Xahau", + jp: "", + }, + theory: { + es: `Para entender por qué Xahau existe y qué la hace diferente, necesitamos recorrer la **historia de las blockchains** y cómo cada generación resolvió problemas que la anterior no podía. + +### 2008 — Bitcoin: el nacimiento + +Todo empezó con un documento de 9 páginas publicado por **Satoshi Nakamoto** bajo el título *"Bitcoin: A Peer-to-Peer Electronic Cash System"*. La idea era simple y revolucionaria: **dinero digital sin intermediarios**. + +Bitcoin introdujo: +- **Proof of Work (PoW)**: Los mineros resuelven problemas matemáticos para validar transacciones +- **Descentralización total**: Sin bancos, sin servidores centrales +- **Inmutabilidad**: Las transacciones confirmadas no se pueden revertir +- **Escasez digital**: Solo existirán 21 millones de BTC + +Limitación: Bitcoin es lento (~7 transacciones por segundo) y su lenguaje de scripting es muy limitado. No fue diseñado para ejecutar lógica compleja. + +### 2012 — XRP Ledger: velocidad sin minería + +Más adelante se creó el **XRP Ledger (o XRPL)**, la primera blockchain importante que **no usa Proof of Work**. En su lugar, usa un protocolo de consenso basado en **validadores de confianza (UNL)**. + +XRPL introdujo: +- **Consenso sin minería**: Transacciones confirmadas en 3-5 segundos +- **DEX nativo**: Intercambio descentralizado integrado en el protocolo +- **Tokens nativos**: Crear tokens sin necesidad de smart contracts +- **Fees mínimos**: Fracciones de centavo por transacción + +Limitación: XRPL no tenía capacidad para ejecutar smart contracts (lógica programable personalizada). + +### 2015 — Ethereum: la computadora mundial + +**Vitalik Buterin** publicó el whitepaper de Ethereum con una idea ambiciosa: una blockchain que pudiera ejecutar **cualquier programa**. Así nació la **Ethereum Virtual Machine (EVM)**. + +Ethereum introdujo: +- **Smart contracts**: Programas que viven en la blockchain y se ejecutan automáticamente +- **Solidity**: Lenguaje de programación para escribir contratos +- **EVM**: Máquina virtual que ejecuta el código de los contratos +- **ERC-20 / ERC-721**: Estándares para tokens fungibles y NFTs +- **DeFi**: Finanzas descentralizadas (préstamos, exchanges, stablecoins) + +Limitación: Gas caro y variable, baja velocidad (~15 TPS), escalabilidad limitada. + +### 2020+ — Explosión de L1s y L2s + +Los problemas de Ethereum impulsaron una oleada de nuevas blockchains: + +- **Solana** (2020): Alta velocidad (~65,000 TPS teóricos) con Proof of History +- **Avalanche** (2020): Subredes personalizables con consenso rápido +- **Polygon** (2020): Solución Layer 2 para escalar Ethereum +- **Arbitrum / Optimism** (2021): Rollups que procesan transacciones fuera de Ethereum +- **Cosmos / Polkadot**: Ecosistemas de blockchains interconectadas + +La mayoría de estas redes son **compatibles con EVM** — usan Solidity y herramientas de Ethereum. + +### 2023 — Xahau: XRPL + Smart Contracts + +**Xahau** nace como un **fork del XRP Ledger** que añade la capacidad que XRPL siempre necesitó: **smart contracts**, llamados **Hooks**. Inicialmente Xahau no iba a existir y los Hooks iban a ser parte de XRP Ledger pero Ripple no quiso aceptar esta mejora de la comunidad. Por no desaprovechar el trabajo realizado durante años, Xahau nació. + +Xahau introdujo: +- **Hooks**: Smart contracts escritos en C y compilados a WebAssembly +- **XAH**: Token nativo con sistema de emisiones/recompensas +- **Herencia de XRPL**: Conserva la velocidad, el DEX nativo y los fees bajos +- **Sin EVM**: Arquitectura propia, no compatible con Solidity + +### ¿Por qué Xahau es un fork de XRPL? + +Xahau al ser un fork de XRPL, aprovecha todas las ventajas de una blockchain probada y optimizada para pagos y tokens, y le añade la pieza que faltaba: la capacidad de ejecutar lógica programable directamente en el protocolo. + +1. **Base probada**: XRPL lleva funcionando desde 2012 sin interrupciones graves +2. **Velocidad nativa**: El consenso de XRPL ya ofrece 3-5 segundos de finalidad +3. **DEX integrado**: No hay que construir un exchange descentralizado desde cero +4. **Tokens nativos**: El sistema de TrustLines y tokens ya existe y funciona +5. **Comunidad existente**: Desarrolladores y herramientas de XRPL pueden adaptarse + +### Línea temporal resumida + +| Año | Hito | Innovación clave | +|---|---|---| +| 2008 | Bitcoin | Dinero digital descentralizado | +| 2012 | XRP Ledger | Consenso sin minería, DEX nativo | +| 2015 | Ethereum | Smart contracts (EVM + Solidity) | +| 2017 | ICO boom | Tokens ERC-20, financiación descentralizada | +| 2020 | DeFi Summer | Finanzas descentralizadas en Ethereum | +| 2020+ | L1s/L2s | Solana, Avalanche, Polygon, Rollups | +| 2023 | Xahau | XRPL + Hooks (smart contracts en C/WASM) |`, + en: `To understand why Xahau exists and what makes it different, we need to go through the **history of blockchains** and how each generation solved problems that the previous one could not. + +### 2008 — Bitcoin: The Birth + +It all started with a 9-page document published by **Satoshi Nakamoto** titled *"Bitcoin: A Peer-to-Peer Electronic Cash System"*. The idea was simple and revolutionary: **digital money without intermediaries**. + +Bitcoin introduced: +- **Proof of Work (PoW)**: Miners solve mathematical problems to validate transactions +- **Total decentralization**: No banks, no central servers +- **Immutability**: Confirmed transactions cannot be reversed +- **Digital scarcity**: Only 21 million BTC will ever exist + +Limitation: Bitcoin is slow (~7 transactions per second) and its scripting language is very limited. It was not designed to execute complex logic. + +### 2012 — XRP Ledger: Speed Without Mining + +Later, the **XRP Ledger (or XRPL)** was created, the first major blockchain that **does not use Proof of Work**. Instead, it uses a consensus protocol based on **trusted validators (UNL)**. + +XRPL introduced: +- **Consensus without mining**: Transactions confirmed in 3-5 seconds +- **Native DEX**: Decentralized exchange integrated into the protocol +- **Native tokens**: Create tokens without needing smart contracts +- **Minimal fees**: Fractions of a cent per transaction + +Limitation: XRPL did not have the ability to execute smart contracts (custom programmable logic). + +### 2015 — Ethereum: The World Computer + +**Vitalik Buterin** published the Ethereum whitepaper with an ambitious idea: a blockchain that could execute **any program**. Thus the **Ethereum Virtual Machine (EVM)** was born. + +Ethereum introduced: +- **Smart contracts**: Programs that live on the blockchain and execute automatically +- **Solidity**: Programming language for writing contracts +- **EVM**: Virtual machine that executes contract code +- **ERC-20 / ERC-721**: Standards for fungible tokens and NFTs +- **DeFi**: Decentralized finance (lending, exchanges, stablecoins) + +Limitation: Expensive and variable gas, low speed (~15 TPS), limited scalability. + +### 2020+ — The L1 and L2 Explosion + +Ethereum's problems drove a wave of new blockchains: + +- **Solana** (2020): High speed (~65,000 theoretical TPS) with Proof of History +- **Avalanche** (2020): Customizable subnets with fast consensus +- **Polygon** (2020): Layer 2 solution for scaling Ethereum +- **Arbitrum / Optimism** (2021): Rollups that process transactions off Ethereum +- **Cosmos / Polkadot**: Ecosystems of interconnected blockchains + +Most of these networks are **EVM-compatible** — they use Solidity and Ethereum tools. + +### 2023 — Xahau: XRPL + Smart Contracts + +**Xahau** was born as a **fork of the XRP Ledger** that adds the capability XRPL always needed: **smart contracts**, called **Hooks**. Initially Xahau was not going to exist and Hooks were going to be part of the XRP Ledger, but Ripple did not want to accept this community improvement. In order not to waste the work done over years, Xahau was born. + +Xahau introduced: +- **Hooks**: Smart contracts written in C and compiled to WebAssembly +- **XAH**: Native token with an emission/reward system +- **XRPL inheritance**: Retains the speed, native DEX, and low fees +- **No EVM**: Its own architecture, not compatible with Solidity + +### Why Is Xahau a Fork of XRPL? + +As a fork of XRPL, Xahau leverages all the advantages of a proven blockchain optimized for payments and tokens, and adds the missing piece: the ability to execute programmable logic directly in the protocol. + +1. **Proven foundation**: XRPL has been running since 2012 without major disruptions +2. **Native speed**: XRPL's consensus already offers 3-5 second finality +3. **Integrated DEX**: No need to build a decentralized exchange from scratch +4. **Native tokens**: The TrustLines and token system already exists and works +5. **Existing community**: XRPL developers and tools can adapt + +### Timeline Summary + +| Year | Milestone | Key Innovation | +|---|---|---| +| 2008 | Bitcoin | Decentralized digital money | +| 2012 | XRP Ledger | Consensus without mining, native DEX | +| 2015 | Ethereum | Smart contracts (EVM + Solidity) | +| 2017 | ICO boom | ERC-20 tokens, decentralized funding | +| 2020 | DeFi Summer | Decentralized finance on Ethereum | +| 2020+ | L1s/L2s | Solana, Avalanche, Polygon, Rollups | +| 2023 | Xahau | XRPL + Hooks (smart contracts in C/WASM) |`, + jp: "", + }, + codeBlocks: [], + slides: [ + { + title: { es: "2008-2015: Los orígenes", en: "2008-2015: The Origins", jp: "" }, + content: { + es: "2008 — Bitcoin\n• Primer dinero digital descentralizado\n• Proof of Work, lento pero revolucionario\n\n2012 — XRP Ledger\n• Sin minería, consenso en 3-5 segundos\n• DEX nativo y tokens integrados\n\n2015 — Ethereum\n• Smart contracts con Solidity\n• La EVM como computadora mundial", + en: "2008 — Bitcoin\n• First decentralized digital money\n• Proof of Work, slow but revolutionary\n\n2012 — XRP Ledger\n• No mining, consensus in 3-5 seconds\n• Native DEX and integrated tokens\n\n2015 — Ethereum\n• Smart contracts with Solidity\n• The EVM as a world computer", + jp: "", + }, + visual: "📜", + }, + { + title: { es: "2020+: La explosión", en: "2020+: The Explosion", jp: "" }, + content: { + es: "Los problemas de Ethereum impulsan nuevas redes:\n\n• Solana → Alta velocidad\n• Avalanche → Subredes personalizables\n• Polygon → Layer 2 para Ethereum\n• Arbitrum/Optimism → Rollups\n\nLa mayoría son compatibles con EVM (Solidity)", + en: "Ethereum's problems drive new networks:\n\n• Solana → High speed\n• Avalanche → Customizable subnets\n• Polygon → Layer 2 for Ethereum\n• Arbitrum/Optimism → Rollups\n\nMost are EVM-compatible (Solidity)", + jp: "", + }, + visual: "🚀", + }, + { + title: { es: "2023: Nace Xahau", en: "2023: Xahau Is Born", jp: "" }, + content: { + es: "Fork de XRPL + Smart Contracts (Hooks)\n\n¿Por qué un fork de XRPL?\n• Base probada desde 2012\n• Velocidad nativa (3-5 seg)\n• DEX y tokens integrados\n• Solo faltaban smart contracts\n\nHooks = C compilado a WebAssembly\nSin EVM, sin Solidity", + en: "Fork of XRPL + Smart Contracts (Hooks)\n\nWhy a fork of XRPL?\n• Proven foundation since 2012\n• Native speed (3-5 sec)\n• Integrated DEX and tokens\n• Only smart contracts were missing\n\nHooks = C compiled to WebAssembly\nNo EVM, no Solidity", + jp: "", + }, + visual: "🧱", + }, + { + title: { es: "Línea temporal completa", en: "Complete Timeline", jp: "" }, + content: { + es: "2008 → Bitcoin (PoW, dinero digital)\n2012 → XRPL (sin minería, DEX)\n2015 → Ethereum (EVM, Solidity)\n2017 → Boom de ICOs y tokens\n2020 → DeFi + nuevas L1s/L2s\n2023 → Xahau (XRPL + Hooks)\n\nCada generación resolvió limitaciones de la anterior", + en: "2008 → Bitcoin (PoW, digital money)\n2012 → XRPL (no mining, DEX)\n2015 → Ethereum (EVM, Solidity)\n2017 → ICO and token boom\n2020 → DeFi + new L1s/L2s\n2023 → Xahau (XRPL + Hooks)\n\nEach generation solved limitations of the previous one", + jp: "", + }, + visual: "⏳", + }, + ], + }, + { + id: "m1l4", + title: { + es: "El ecosistema Xahau", + en: "The Xahau Ecosystem", + jp: "", + }, + theory: { + es: `Xahau no es solo una blockchain, es un **ecosistema completo** con herramientas, wallets, exploradores y una comunidad activa. En esta lección conocerás las piezas fundamentales del ecosistema para saber dónde buscar información y cómo interactuar con la red. + +### XAH: el token nativo + +**XAH** es la criptomoneda nativa de Xahau. A diferencia de XRP en el XRPL, XAH tiene un sistema de **emisión inflaccionario**: los titulares de cuentas activas pueden solicitar recompensas periódicas en XAH. Esto incentiva la participación en la red y el uso de ésta. + +Características de XAH: +- Se usa para pagar **fees** (comisiones de transacción) +- Se necesita una **reserva mínima** para mantener una cuenta activa +- El sistema de **emisiones** distribuye XAH a cuentas activas que lo soliciten +- Se puede enviar, intercambiar y usar en Hooks + +### Xaman (antes XUMM): la wallet principal + +**Xaman** (anteriormente conocida como XUMM) es la wallet más utilizada en el ecosistema XRPL/Xahau. Es una aplicación móvil que te permite: + +- Crear y gestionar cuentas en Xahau y XRPL +- Enviar y recibir XAH y tokens +- Firmar transacciones de forma segura +- Interactuar con aplicaciones descentralizadas (xApps) +- Disponible para **iOS** y **Android** + +Descarga: [xaman.app](https://xaman.app) + +### Hooks Builder: IDE online para smart contracts + +**Hooks Builder** es un entorno de desarrollo integrado (IDE) que funciona en el navegador y te permite escribir, compilar y desplegar Hooks sin instalar nada en tu ordenador en Xahau Testnet. + +Características: +- Editor de código con resaltado de sintaxis para C +- Compilador de C a WebAssembly integrado +- Despliegue directo a la testnet de Xahau +- Ejemplos y plantillas para empezar rápido + +URL: [builder.xahau.network/](https://builder.xahau.network/) + +### Exploradores de bloques + +Los **exploradores** te permiten ver todo lo que ocurre en la blockchain de forma visual: + +- Buscar transacciones por hash +- Ver el estado de cualquier cuenta (balance, tokens, hooks) +- Explorar ledgers y sus contenidos +- Verificar el estado de la red + +Para **Xahau Mainnet**: + +URL: [xahauexplorer.com](https://xahauexplorer.com) +URL: [xahau.xrplwin.com](https://xahau.xrplwin.com) +URL: [explorer.xahau.network](https://explorer.xahau.network) +URL: [xahscan.com](https://xahscan.com) + +Para **Xahau Testnet**: + +URL: [test.xahauexplorer.com](https://test.xahauexplorer.com) +URL: [xahau-testnet.xrplwin.com](https://xahau-testnet.xrplwin.com) +URL: [explorer.xahau-test.net](https://explorer.xahau-test.net) + +### Recursos para desarrolladores + +- **Documentación oficial**: [xahau.network/docs/](https://xahau.network/docs/) Guías, referencia de API y tutoriales +- **GitHub**: [https://github.com/xahau](https://github.com/xahau) Código fuente del nodo, librerías y herramientas +- **Discord**: [https://discord.gg/ds7nb93mYj](https://discord.gg/ds7nb93mYj) Comunidad activa donde hacer preguntas y compartir proyectos +- **X**: [https://x.com/XahauNetwork](https://x.com/XahauNetwork) Cuenta oficial de la blockchain Xahau para noticias y actualizaciones +- **Librería xahau js**: [https://www.npmjs.com/package/xahau](https://www.npmjs.com/package/xahau) La librería JavaScript que usamos en este curso para interactuar con la red + +### Testnet vs Mainnet + +Xahau tiene dos redes principales: + +| Característica | Testnet | Mainnet | +|---|---|---| +| URL WebSocket | wss://xahau-test.net | wss://xahau.network | +| Token | XAH (sin valor real) | XAH (con valor real) | +| Propósito | Desarrollo y pruebas | Producción | +| Faucet | Sí (XAH gratis para probar) | No | +| Datos | Se pueden reiniciar periódicamente | Permanentes | + +**Para este curso usaremos siempre la testnet.** Los tokens de testnet no tienen valor real, así que puedes experimentar libremente sin riesgo de perder dinero. + +Para obtener XAH de testnet, usa el **faucet** (grifo): una herramienta que te envía tokens gratuitos a tu cuenta de prueba. Lo veremos en detalle en módulos posteriores.`, + en: `Xahau is not just a blockchain, it is a **complete ecosystem** with tools, wallets, explorers, and an active community. In this lesson you will learn about the fundamental pieces of the ecosystem so you know where to find information and how to interact with the network. + +### XAH: The Native Token + +**XAH** is the native cryptocurrency of Xahau. Unlike XRP on XRPL, XAH has an **inflationary emission system**: holders of active accounts can request periodic rewards in XAH. This incentivizes participation in the network and its usage. + +XAH characteristics: +- Used to pay **fees** (transaction fees) +- A **minimum reserve** is needed to maintain an active account +- The **emission system** distributes XAH to active accounts that request it +- It can be sent, exchanged, and used in Hooks + +### Xaman (formerly XUMM): The Main Wallet + +**Xaman** (formerly known as XUMM) is the most widely used wallet in the XRPL/Xahau ecosystem. It is a mobile application that allows you to: + +- Create and manage accounts on Xahau and XRPL +- Send and receive XAH and tokens +- Sign transactions securely +- Interact with decentralized applications (xApps) +- Available for **iOS** and **Android** + +Download: [xaman.app](https://xaman.app) + +### Hooks Builder: Online IDE for Smart Contracts + +**Hooks Builder** is an integrated development environment (IDE) that runs in the browser and allows you to write, compile, and deploy Hooks without installing anything on your computer on Xahau Testnet. + +Features: +- Code editor with syntax highlighting for C +- Built-in C to WebAssembly compiler +- Direct deployment to the Xahau testnet +- Examples and templates to get started quickly + +URL: [builder.xahau.network/](https://builder.xahau.network/) + +### Block Explorers + +**Explorers** allow you to visually see everything happening on the blockchain: + +- Search transactions by hash +- View the state of any account (balance, tokens, hooks) +- Explore ledgers and their contents +- Verify the network status + +For **Xahau Mainnet**: + +URL: [xahauexplorer.com](https://xahauexplorer.com) +URL: [xahau.xrplwin.com](https://xahau.xrplwin.com) +URL: [explorer.xahau.network](https://explorer.xahau.network) +URL: [xahscan.com](https://xahscan.com) + +For **Xahau Testnet**: + +URL: [test.xahauexplorer.com](https://test.xahauexplorer.com) +URL: [xahau-testnet.xrplwin.com](https://xahau-testnet.xrplwin.com) +URL: [explorer.xahau-test.net](https://explorer.xahau-test.net) + +### Developer Resources + +- **Official documentation**: [xahau.network/docs/](https://xahau.network/docs/) Guides, API reference, and tutorials +- **GitHub**: [https://github.com/xahau](https://github.com/xahau) Node source code, libraries, and tools +- **Discord**: [https://discord.gg/ds7nb93mYj](https://discord.gg/ds7nb93mYj) Active community for asking questions and sharing projects +- **X**: [https://x.com/XahauNetwork](https://x.com/XahauNetwork) Official Xahau blockchain account for news and updates +- **xahau js library**: [https://www.npmjs.com/package/xahau](https://www.npmjs.com/package/xahau) The JavaScript library we use in this course to interact with the network + +### Testnet vs Mainnet + +Xahau has two main networks: + +| Feature | Testnet | Mainnet | +|---|---|---| +| WebSocket URL | wss://xahau-test.net | wss://xahau.network | +| Token | XAH (no real value) | XAH (real value) | +| Purpose | Development and testing | Production | +| Faucet | Yes (free XAH for testing) | No | +| Data | Can be reset periodically | Permanent | + +**For this course we will always use the testnet.** Testnet tokens have no real value, so you can experiment freely without the risk of losing money. + +To obtain testnet XAH, use the **faucet**: a tool that sends free tokens to your test account. We will cover this in detail in later modules.`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Verificar conectividad con Xahau Mainnet y Testnet", + en: "Verify connectivity with Xahau Mainnet and Testnet", + jp: "", + }, + language: "javascript", + code: `// File: test-ecosystem.js +// Run with: node test-ecosystem.js +// Verifies that you can connect to both Mainnet and Testnet. + +const { Client } = require("xahau"); + +async function verifyNetwork(url, name) { + const client = new Client(url); + + try { + await client.connect(); + + const response = await client.request({ + command: "server_info" + }); + + const info = response.result.info; + console.log("✅", name); + console.log(" URL:", url); + console.log(" Network ID:", info.network_id); + console.log(" Version:", info.build_version); + console.log(" Ledger:", info.validated_ledger.seq); + console.log(" Status: Operational"); + + await client.disconnect(); + return true; + } catch (error) { + console.log("❌", name); + console.log(" URL:", url); + console.log(" Error:", error.message); + return false; + } +} + +async function main() { + console.log("=== Xahau Ecosystem Verification ===\\n"); + + // Verify Mainnet + const mainnetOk = await verifyNetwork( + "wss://xahau.network", + "Xahau Mainnet" + ); + + console.log(""); + + // Verify Testnet + const testnetOk = await verifyNetwork( + "wss://xahau-test.net", + "Xahau Testnet" + ); + + // Summary + console.log("\\n=== Summary ==="); + console.log("Mainnet:", mainnetOk ? "Accessible" : "Not accessible"); + console.log("Testnet:", testnetOk ? "Accessible" : "Not accessible"); + + if (mainnetOk && testnetOk) { + console.log("\\nBoth networks are accessible. All set!"); + } else { + console.log("\\nSome network is not responding. Check your internet connection."); + } + + console.log("\\n--- Ecosystem Resources ---"); + console.log("Wallet: https://xaman.app"); + console.log("Explorer: https://explorer.xahau.network"); + console.log("Hooks IDE: https://builder.xahau.network"); + console.log("Docs: https://xahau.network/docs"); +} + +main();`, + }, + ], + slides: [ + { + title: { es: "XAH y el sistema de emisiones", en: "XAH and the Emission System", jp: "" }, + content: { + es: "XAH = Token nativo de Xahau\n\n• Pagar fees (comisiones)\n• Reserva mínima para cuentas\n• Sistema de emisión inflaccionario\n → Los usuarios que lo soliciten, reciben XAH periódicamente", + en: "XAH = Native token of Xahau\n\n• Pay fees (transaction fees)\n• Minimum reserve for accounts\n• Inflationary emission system\n → Users who request it receive XAH periodically", + jp: "", + }, + visual: "💰", + }, + { + title: { es: "Herramientas del ecosistema", en: "Ecosystem Tools", jp: "" }, + content: { + es: "Xaman → Wallet móvil (iOS/Android)\n xaman.app\n\nHooks Builder → IDE online para smart contracts\n builder.xahau.network\n\nExplorer → Exploradores de bloques\n xahauexplorer.com xahau.xrplwin.com xahscan.com\n\nDocs → Documentación oficial\n xahau.network/docs", + en: "Xaman → Mobile wallet (iOS/Android)\n xaman.app\n\nHooks Builder → Online IDE for smart contracts\n builder.xahau.network\n\nExplorer → Block explorers\n xahauexplorer.com xahau.xrplwin.com xahscan.com\n\nDocs → Official documentation\n xahau.network/docs", + jp: "", + }, + visual: "🛠️", + }, + { + title: { es: "Testnet vs Mainnet", en: "Testnet vs Mainnet", jp: "" }, + content: { + es: "Testnet (desarrollo)\n• wss://xahau-test.net\n• XAH sin valor real\n• Faucet para obtener tokens gratis\n\nMainnet (producción)\n• wss://xahau.network\n• XAH con valor real\n• Sin faucet\n\nEn este curso usamos SIEMPRE testnet", + en: "Testnet (development)\n• wss://xahau-test.net\n• XAH with no real value\n• Faucet to get free tokens\n\nMainnet (production)\n• wss://xahau.network\n• XAH with real value\n• No faucet\n\nIn this course we ALWAYS use testnet", + jp: "", + }, + visual: "🌐", + }, + ], + }, + ], +} diff --git a/src/data/modules/m02-consenso.js b/src/data/modules/m02-consenso.js new file mode 100644 index 0000000..c33fa7a --- /dev/null +++ b/src/data/modules/m02-consenso.js @@ -0,0 +1,658 @@ +export default { + id: "m2", + icon: "🤝", + title: { + es: "Cómo funciona el consenso en una blockchain", + en: "How consensus works in a blockchain", + jp: "", + }, + lessons: [ + { + id: "m2l1", + title: { + es: "Mecanismos de consenso", + en: "Consensus mechanisms", + jp: "", + }, + theory: { + es: `El **consenso** es el mecanismo por el cual todos los nodos de una red blockchain se ponen de acuerdo sobre cuál es el estado válido del ledger. Sin consenso, no hay blockchain. + +### ¿Por qué es necesario el consenso? + +En una red descentralizada, no hay una autoridad central que decida qué transacciones son válidas. El consenso resuelve el problema de cómo múltiples nodos independientes pueden acordar un estado único sin confiar los unos en los otros. + +### El problema del doble gasto + +El **doble gasto** es el problema fundamental que todo sistema de dinero digital debe resolver: ¿cómo evitar que alguien gaste el mismo dinero dos veces? + +Con dinero físico esto no es posible, si le das un billete a alguien, ya no lo tienes. Pero los datos digitales se pueden copiar. Sin un mecanismo de consenso, Alice podría enviar sus 10 XAH a Bob y simultáneamente enviar esos mismos 10 XAH a Carol. Ambas transacciones parecerían válidas por separado. + +El consenso resuelve esto: todos los nodos de la red acuerdan **un único orden** de transacciones. Si la transacción a Bob se procesa primero, la transacción a Carol se rechaza porque Alice ya no tiene esos fondos. + +### El Problema de los Generales Bizantinos + +El doble gasto es un caso particular de un problema más general de la informática distribuida: el **Problema de los Generales Bizantinos** (1982, Lamport, Shostak y Pease). + +Imagina varios generales de un ejército rodeando una ciudad enemiga. Deben coordinar si atacar o retirarse, si solo algunos atacan, perderán. El problema es que se comunican por mensajeros y **algunos generales pueden ser traidores** que envían órdenes contradictorias para provocar el caos. + +Trasladado a una blockchain: +- Los **generales** son los **nodos/validadores** de la red +- Los **mensajes** son las **transacciones y propuestas** +- Los **traidores** son **nodos maliciosos** que intentan hacer trampas (por ejemplo, aprobar un doble gasto) + +Un protocolo de consenso debe funcionar correctamente **incluso si una parte de los participantes miente o falla**. Esto se llama **Tolerancia a Fallos Bizantinos (BFT)**. Cada mecanismo de consenso lo resuelve de forma diferente: +- **PoW**: Hace que mentir sea extremadamente caro (requiere gastar energía) +- **PoS**: Hace que mentir tenga consecuencias económicas (pierdes tu stake) +- **Consenso federado (Xahau)**: Requiere que al menos el 80% de los validadores de confianza estén de acuerdo + +### Tipos principales de consenso + +**Proof of Work (PoW)** — Bitcoin +- Los mineros compiten resolviendo problemas matemáticos +- Alto consumo energético +- Finalidad probabilística (hay que esperar varias confirmaciones) + +**Proof of Stake (PoS)** — Ethereum +- Los validadores ponen en juego (stake) sus tokens +- Más eficiente que PoW +- Finalidad más rápida pero con posibles reorganizaciones + +**Consenso federado / UNL** — Xahau +- Los validadores votan sobre las transacciones válidas +- No requiere minería ni staking +- Finalidad determinística en segundos +- Bajo consumo energético + +### ¿Qué hace diferente al consenso de Xahau? + +Xahau no se basa en competencia (como PoW) ni en capital bloqueado (como PoS), sino en **confianza entre validadores** a través de listas UNL.`, + en: `**Consensus** is the mechanism by which all nodes in a blockchain network agree on the valid state of the ledger. Without consensus, there is no blockchain. + +### Why is consensus necessary? + +In a decentralized network, there is no central authority to decide which transactions are valid. Consensus solves the problem of how multiple independent nodes can agree on a single state without trusting each other. + +### The double spending problem + +**Double spending** is the fundamental problem that every digital money system must solve: how do you prevent someone from spending the same money twice? + +With physical money this is not possible — if you give a bill to someone, you no longer have it. But digital data can be copied. Without a consensus mechanism, Alice could send her 10 XAH to Bob and simultaneously send those same 10 XAH to Carol. Both transactions would appear valid separately. + +Consensus solves this: all nodes in the network agree on **a single order** of transactions. If the transaction to Bob is processed first, the transaction to Carol is rejected because Alice no longer has those funds. + +### The Byzantine Generals Problem + +Double spending is a specific case of a more general problem in distributed computing: the **Byzantine Generals Problem** (1982, Lamport, Shostak, and Pease). + +Imagine several army generals surrounding an enemy city. They must coordinate whether to attack or retreat — if only some attack, they will lose. The problem is that they communicate via messengers and **some generals may be traitors** who send contradictory orders to cause chaos. + +Applied to a blockchain: +- The **generals** are the network's **nodes/validators** +- The **messages** are the **transactions and proposals** +- The **traitors** are **malicious nodes** that try to cheat (for example, approving a double spend) + +A consensus protocol must work correctly **even if some participants lie or fail**. This is called **Byzantine Fault Tolerance (BFT)**. Each consensus mechanism solves it differently: +- **PoW**: Makes lying extremely expensive (requires spending energy) +- **PoS**: Makes lying have economic consequences (you lose your stake) +- **Federated consensus (Xahau)**: Requires at least 80% of trusted validators to agree + +### Main types of consensus + +**Proof of Work (PoW)** — Bitcoin +- Miners compete by solving mathematical problems +- High energy consumption +- Probabilistic finality (you must wait for several confirmations) + +**Proof of Stake (PoS)** — Ethereum +- Validators put their tokens at stake +- More efficient than PoW +- Faster finality but with possible reorganizations + +**Federated consensus / UNL** — Xahau +- Validators vote on valid transactions +- No mining or staking required +- Deterministic finality in seconds +- Low energy consumption + +### What makes Xahau's consensus different? + +Xahau is not based on competition (like PoW) or locked capital (like PoS), but on **trust between validators** through UNL lists.`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Consultar el estado de los validadores", + en: "Query the validator status", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function getValidators() { + const client = new Client("wss://xahau.network"); + await client.connect(); + + // Query server information including validators + const response = await client.request({ + command: "server_info" + }); + + const info = response.result.info; + console.log("Consensus status:"); + console.log(" Validated ledger:", info.validated_ledger.seq); + console.log(" Hash:", info.validated_ledger.hash); + console.log(" Quorum:", info.validation_quorum); + + await client.disconnect(); +} + +getValidators();`, + }, + ], + slides: [ + { + title: { es: "¿Qué es el consenso?", en: "What is consensus?", jp: "" }, + content: { + es: "El mecanismo por el cual los nodos\nacuerdan el estado válido del ledger\n\n• Sin consenso, no hay blockchain\n• Resuelve el problema de la confianza\n• Cada red usa un mecanismo diferente", + en: "The mechanism by which nodes\nagree on the valid state of the ledger\n\n• Without consensus, there is no blockchain\n• Solves the trust problem\n• Each network uses a different mechanism", + jp: "", + }, + visual: "🤝", + }, + { + title: { es: "PoW vs PoS vs Federated", en: "PoW vs PoS vs Federated", jp: "" }, + content: { + es: "⛏️ PoW → Minería (Bitcoin)\n💰 PoS → Staking (Ethereum)\n🗳️ Federado → Votación (Xahau)\n\nXahau: sin minería, sin staking\nFinalidad determinística en segundos", + en: "⛏️ PoW → Mining (Bitcoin)\n💰 PoS → Staking (Ethereum)\n🗳️ Federated → Voting (Xahau)\n\nXahau: no mining, no staking\nDeterministic finality in seconds", + jp: "", + }, + visual: "⚡", + }, + { + title: { es: "¿Por qué consenso federado?", en: "Why federated consensus?", jp: "" }, + content: { + es: "Xahau eligió consenso federado por:\n\n• Velocidad → Finalidad en 3-5 segundos\n• Eficiencia energética → Sin minería costosa\n• Finalidad determinística → Sin reorgs ni forks\n• Sin barreras económicas → No requiere staking\n• Confianza distribuida → Validadores diversos\n\nIdeal para pagos y aplicaciones financieras", + en: "Xahau chose federated consensus for:\n\n• Speed → Finality in 3-5 seconds\n• Energy efficiency → No costly mining\n• Deterministic finality → No reorgs or forks\n• No economic barriers → No staking required\n• Distributed trust → Diverse validators\n\nIdeal for payments and financial applications", + jp: "", + }, + visual: "🏆", + }, + ], + }, + { + id: "m2l2", + title: { + es: "El protocolo de consenso de Xahau", + en: "The Xahau consensus protocol", + jp: "", + }, + theory: { + es: `Xahau utiliza el **Mecanísmo de Consenso Federado**. Este protocolo se basa en el concepto de **UNL (Unique Node List)**, una lista de validadores en los que cada nodo confía. + +### ¿Cómo funciona? + +1. **Propuesta**: Los validadores proponen un conjunto de transacciones para incluir en el próximo ledger +2. **Votación**: Los validadores comparan sus propuestas con las de otros validadores de su UNL +3. **Convergencia**: A través de varias rondas, los validadores convergen hacia un conjunto común de transacciones +4. **Validación**: Cuando al menos el **80%** de los validadores de la UNL están de acuerdo, el ledger se valida +5. **Cierre**: El nuevo ledger se cierra y se convierte en el estado oficial de la red + +### UNL (Unique Node List) + +Cada nodo mantiene una **UNL**, la lista de validadores cuyas opiniones considera fiables. No todos los nodos necesitan confiar en los mismos validadores, pero debe haber suficiente **solapamiento** entre las UNLs para que la red converja. + +### Propiedades del consenso en Xahau + +- **Finalidad determinística**: Una vez que un ledger se valida, es final. No hay reorganizaciones (a diferencia de Bitcoin/Ethereum) +- **Velocidad**: El ledger se cierra cada **3-5 segundos** +- **Eficiencia energética**: No requiere cálculos intensivos como PoW +- **Sin staking**: Los validadores no necesitan bloquear capital +- **Tolerancia a fallos**: La red funciona mientras al menos el 80% de los validadores de la UNL estén operativos + +### Diferencia con Proof of Stake + +En PoS, la seguridad está respaldada por capital económico (tokens en staking). En el consenso de Xahau, la seguridad está respaldada por la **reputación y diversidad** de los validadores. Los validadores son operados por entidades independientes (universidades, empresas, fundaciones o particulares).`, + en: `Xahau uses the **Federated Consensus Mechanism**. This protocol is based on the concept of **UNL (Unique Node List)**, a list of validators that each node trusts. + +### How does it work? + +1. **Proposal**: Validators propose a set of transactions to include in the next ledger +2. **Voting**: Validators compare their proposals with those of other validators in their UNL +3. **Convergence**: Through several rounds, validators converge toward a common set of transactions +4. **Validation**: When at least **80%** of the UNL validators agree, the ledger is validated +5. **Closing**: The new ledger is closed and becomes the official state of the network + +### UNL (Unique Node List) + +Each node maintains a **UNL**, the list of validators whose opinions it considers reliable. Not all nodes need to trust the same validators, but there must be enough **overlap** between UNLs for the network to converge. + +### Properties of consensus in Xahau + +- **Deterministic finality**: Once a ledger is validated, it is final. There are no reorganizations (unlike Bitcoin/Ethereum) +- **Speed**: The ledger closes every **3-5 seconds** +- **Energy efficiency**: Does not require intensive computations like PoW +- **No staking**: Validators do not need to lock up capital +- **Fault tolerance**: The network works as long as at least 80% of the UNL validators are operational + +### Difference from Proof of Stake + +In PoS, security is backed by economic capital (staked tokens). In Xahau's consensus, security is backed by the **reputation and diversity** of validators. Validators are operated by independent entities (universities, companies, foundations, or individuals).`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Monitorizar el cierre de ledgers en tiempo real", + en: "Monitor ledger closing in real time", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function monitorLedgers() { + const client = new Client("wss://xahau.network"); + await client.connect(); + + console.log("Monitoring ledger closing..."); + console.log("(Each close = a completed consensus round)\\n"); + + // Subscribe to ledger events + await client.request({ + command: "subscribe", + streams: ["ledger"] + }); + + client.on("ledgerClosed", (ledger) => { + console.log(\`Ledger #\${ledger.ledger_index} closed\`); + console.log(\` Hash: \${ledger.ledger_hash}\`); + console.log(\` Transactions: \${ledger.txn_count}\`); + console.log(\` Time: \${new Date().toISOString()}\\n\`); + }); + + // Stop after 30 seconds + setTimeout(async () => { + console.log("Stopping monitoring..."); + await client.disconnect(); + }, 30000); +} + +monitorLedgers();`, + }, + ], + slides: [ + { + title: { es: "UNL: Unique Node List", en: "UNL: Unique Node List", jp: "" }, + content: { + es: "Cada nodo tiene una lista de validadores\nen los que confía (UNL)\n\n• Los validadores proponen transacciones\n• Votan en múltiples rondas\n• 80% de acuerdo → Ledger validado\n• Finalidad en 3-5 segundos", + en: "Each node has a list of validators\nthat it trusts (UNL)\n\n• Validators propose transactions\n• They vote in multiple rounds\n• 80% agreement → Validated ledger\n• Finality in 3-5 seconds", + jp: "", + }, + visual: "🗳️", + }, + { + title: { es: "Propiedades del consenso", en: "Consensus properties", jp: "" }, + content: { + es: "✅ Finalidad determinística (sin reorgs)\n✅ Cierre cada 3-5 segundos\n✅ Sin minería ni staking\n✅ Bajo consumo energético\n✅ Tolerante a fallos (80% quorum)", + en: "✅ Deterministic finality (no reorgs)\n✅ Closes every 3-5 seconds\n✅ No mining or staking\n✅ Low energy consumption\n✅ Fault tolerant (80% quorum)", + jp: "", + }, + visual: "🛡️", + }, + { + title: { es: "Las 5 fases del consenso", en: "The 5 phases of consensus", jp: "" }, + content: { + es: "1️⃣ Propuesta → Validadores proponen transacciones\n2️⃣ Votación → Comparan propuestas con su UNL\n3️⃣ Convergencia → Varias rondas hasta coincidir\n4️⃣ Validación → 80% de acuerdo en la UNL\n5️⃣ Cierre → Nuevo ledger oficial e irreversible\n\nTodo el proceso tarda 3-5 segundos", + en: "1️⃣ Proposal → Validators propose transactions\n2️⃣ Voting → Compare proposals with their UNL\n3️⃣ Convergence → Multiple rounds until agreement\n4️⃣ Validation → 80% agreement in the UNL\n5️⃣ Closing → New official and irreversible ledger\n\nThe entire process takes 3-5 seconds", + jp: "", + }, + visual: "🔄", + }, + ], + }, + { + id: "m2l3", + title: { + es: "Tolerancia a fallos bizantinos", + en: "Byzantine Fault Tolerance", + jp: "", + }, + theory: { + es: `La seguridad de una blockchain depende de su capacidad para funcionar correctamente incluso cuando algunos participantes fallan o actúan de forma maliciosa. Este concepto se conoce como **Tolerancia a Fallos Bizantinos (BFT)**. + +### El Problema de los Generales Bizantinos + +Imagina varios generales de un ejército que rodean una ciudad enemiga. Deben coordinar un ataque simultáneo para ganar: si solo algunos atacan, perderán. El problema es que se comunican por mensajeros, y **algunos generales pueden ser traidores** que envían mensajes contradictorios. + +Este es el **Problema de los Generales Bizantinos**, formulado en 1982 por Lamport, Shostak y Pease. Trasladado a blockchain: +- Los **generales** son los **validadores** +- Los **mensajes** son las **propuestas de transacciones** +- Los **traidores** son **nodos maliciosos o defectuosos** + +### ¿Qué significa BFT? + +Un sistema tiene **Tolerancia a Fallos Bizantinos** cuando puede llegar a un consenso correcto aunque una fracción de sus participantes actúe de forma arbitraria (envíe datos incorrectos, no responda, o intente sabotear la red). + +### ¿Cómo maneja Xahau los fallos bizantinos? + +El protocolo de consenso de Xahau requiere que al menos el **80% de los validadores de la UNL** estén de acuerdo para validar un ledger. Esto significa que la red puede tolerar hasta un **20% de validadores defectuosos o maliciosos** y seguir funcionando correctamente. + +Escenarios que Xahau maneja: +- **Validador caído**: Si un validador deja de responder, los demás continúan sin él +- **Validador malicioso**: Si un validador propone transacciones inválidas, el 80% restante lo ignora +- **Partición de red**: Si un grupo de validadores pierde conectividad, el grupo mayoritario (>80%) sigue validando + +### ¿Qué pasa cuando los validadores no están de acuerdo? + +Cuando no se alcanza el umbral del 80%, el ledger simplemente **no se cierra**. Las transacciones en disputa se posponen hasta la siguiente ronda de consenso. No hay "ganador parcial", o hay consenso completo o no hay cierre. Si no se llega a un acuerdo, la blockchain se para antes que equivocarse `, + en: `The security of a blockchain depends on its ability to function correctly even when some participants fail or act maliciously. This concept is known as **Byzantine Fault Tolerance (BFT)**. + +### The Byzantine Generals Problem + +Imagine several army generals surrounding an enemy city. They must coordinate a simultaneous attack to win: if only some attack, they will lose. The problem is that they communicate via messengers, and **some generals may be traitors** who send contradictory messages. + +This is the **Byzantine Generals Problem**, formulated in 1982 by Lamport, Shostak, and Pease. Applied to blockchain: +- The **generals** are the **validators** +- The **messages** are the **transaction proposals** +- The **traitors** are **malicious or faulty nodes** + +### What does BFT mean? + +A system has **Byzantine Fault Tolerance** when it can reach correct consensus even if a fraction of its participants acts arbitrarily (sends incorrect data, does not respond, or tries to sabotage the network). + +### How does Xahau handle Byzantine faults? + +Xahau's consensus protocol requires at least **80% of the UNL validators** to agree in order to validate a ledger. This means the network can tolerate up to **20% of faulty or malicious validators** and continue functioning correctly. + +Scenarios that Xahau handles: +- **Downed validator**: If a validator stops responding, the others continue without it +- **Malicious validator**: If a validator proposes invalid transactions, the remaining 80% ignores it +- **Network partition**: If a group of validators loses connectivity, the majority group (>80%) continues validating + +### What happens when validators disagree? + +When the 80% threshold is not reached, the ledger simply **does not close**. Disputed transactions are postponed until the next consensus round. There is no "partial winner" — either there is full consensus or there is no closing. If agreement cannot be reached, the blockchain halts rather than making an error.`, + jp: "", + }, + codeBlocks: [], + slides: [ + { + title: { es: "El Problema de los Generales Bizantinos", en: "The Byzantine Generals Problem", jp: "" }, + content: { + es: "Generales deben coordinar un ataque\npero algunos pueden ser traidores\n\nEn blockchain:\n🏛️ Generales = Validadores\n📨 Mensajes = Propuestas de transacciones\n🦹 Traidores = Nodos maliciosos\n\nBFT = funcionar correctamente\nincluso con actores maliciosos", + en: "Generals must coordinate an attack\nbut some may be traitors\n\nIn blockchain:\n🏛️ Generals = Validators\n📨 Messages = Transaction proposals\n🦹 Traitors = Malicious nodes\n\nBFT = functioning correctly\neven with malicious actors", + jp: "", + }, + visual: "🏛️", + }, + { + title: { es: "Xahau y la tolerancia a fallos", en: "Xahau and fault tolerance", jp: "" }, + content: { + es: "Xahau tolera hasta 20% de fallos\n\n• 80% de la UNL debe estar de acuerdo\n• Validador caído → los demás continúan\n• Validador malicioso → es ignorado\n• Sin consenso → el ledger no se cierra\n (nunca se fuerza un resultado parcial)", + en: "Xahau tolerates up to 20% failures\n\n• 80% of the UNL must agree\n• Downed validator → the others continue\n• Malicious validator → it is ignored\n• No consensus → the ledger does not close\n (a partial result is never forced)", + jp: "", + }, + visual: "🛡️", + } + ], + }, + { + id: "m2l4", + title: { + es: "Validadores en la práctica", + en: "Validators in practice", + jp: "", + }, + theory: { + es: `Hasta ahora hemos hablado de validadores de forma teórica. En esta lección veremos cómo funcionan **en la práctica**: quién los opera, qué se necesita para ejecutar uno, y cómo la red evoluciona a través del sistema de enmiendas. + +### ¿Quién opera validadores en Xahau? + +La fortaleza de una red descentralizada depende de la **diversidad de sus validadores**. En Xahau, los validadores son operados por: + +- **Fundaciones y organizaciones** del ecosistema +- **Empresas** que construyen sobre la red +- **Desarrolladores independientes** de la comunidad + +La clave es que los validadores sean operados por entidades **independientes** en distintas jurisdicciones y con diferentes motivaciones, lo que dificulta la colusión. + +### Requisitos para operar un validador + +Para ejecutar un nodo validador en Xahau necesitas: + +- **Hardware**: Servidor con al menos 8 GB de RAM, 4 CPUs, y almacenamiento SSD rápido +- **Red**: Conexión a internet estable con baja latencia y alta disponibilidad +- **Software**: El software \`xahaud\` (daemon de Xahau) configurado en modo validador +- **Disponibilidad**: El validador debe estar online 24/7 con un uptime superior al 99% +- **Mantenimiento**: Actualizaciones regulares del software cuando se publican nuevas versiones + +No se requiere ningún depósito ni staking de tokens para ser validador. + +### UNL por defecto vs UNL personalizada + +**UNL por defecto (Default UNL / dUNL)**: +- Es la lista de validadores recomendada publicada por los operadores principales de la red +- Los nodos nuevos usan esta lista por defecto +- Se actualiza periódicamente para añadir o eliminar validadores + +**UNL personalizada**: +- Cada operador de nodo puede crear su propia UNL +- Permite elegir en qué validadores confiar específicamente +- Debe tener suficiente solapamiento con otras UNLs para mantener la convergencia +- Útil para operadores avanzados que quieren mayor control + +### ¿Qué pasa si un validador se desconecta? + +Cuando un validador de la UNL deja de responder: +1. Los otros validadores simplemente continúan sin él +2. El quorum se calcula sobre los validadores **activos** +3. Si demasiados validadores caen (<80% disponible), la red **deja de validar** nuevos ledgers (no se corrompe, solo se pausa) +4. Cuando suficientes validadores vuelven, la red reanuda automáticamente + +### Enmiendas (Amendments) y votación de protocolo + +Las **enmiendas** son el mecanismo por el cual Xahau actualiza su protocolo de forma descentralizada: + +1. Un desarrollador propone un cambio al protocolo y lo implementa con un ID de enmienda único +2. Los validadores **votan** si apoyan la activación de esa enmienda +3. Si una enmienda recibe apoyo del **80% de los validadores** durante **2 semanas consecutivas**, se activa automáticamente +4. Una vez activada, es permanente e irreversible + +Ejemplos de enmiendas incluyen: nuevos tipos de transacciones, nuevas características de la blockchain. + +### Métricas de descentralización + +¿Cómo medir si una red es realmente descentralizada? Algunas métricas clave: + +- **Coeficiente Nakamoto**: El número mínimo de entidades que tendrían que coludirse para comprometer la red. Cuanto más alto, mejor +- **Distribución geográfica**: Validadores en diferentes países y continentes +- **Diversidad de operadores**: Diferentes tipos de entidades (empresas, universidades, individuos) +- **Diversidad de infraestructura**: Diferentes proveedores de hosting, no todos en AWS o Google Cloud +- **Solapamiento de UNL**: Qué porcentaje de validadores comparten las diferentes UNLs`, + en: `So far we have talked about validators theoretically. In this lesson we will see how they work **in practice**: who operates them, what is needed to run one, and how the network evolves through the amendments system. + +### Who operates validators on Xahau? + +The strength of a decentralized network depends on the **diversity of its validators**. On Xahau, validators are operated by: + +- **Foundations and organizations** in the ecosystem +- **Companies** building on the network +- **Independent developers** from the community + +The key is that validators are operated by **independent** entities in different jurisdictions and with different motivations, making collusion difficult. + +### Requirements to operate a validator + +To run a validator node on Xahau you need: + +- **Hardware**: A server with at least 8 GB of RAM, 4 CPUs, and fast SSD storage +- **Network**: A stable internet connection with low latency and high availability +- **Software**: The \`xahaud\` software (Xahau daemon) configured in validator mode +- **Availability**: The validator must be online 24/7 with uptime above 99% +- **Maintenance**: Regular software updates when new versions are released + +No deposit or token staking is required to be a validator. + +### Default UNL vs custom UNL + +**Default UNL (dUNL)**: +- It is the recommended validator list published by the main network operators +- New nodes use this list by default +- It is updated periodically to add or remove validators + +**Custom UNL**: +- Each node operator can create their own UNL +- Allows choosing which validators to specifically trust +- Must have enough overlap with other UNLs to maintain convergence +- Useful for advanced operators who want more control + +### What happens if a validator disconnects? + +When a UNL validator stops responding: +1. The other validators simply continue without it +2. The quorum is calculated based on **active** validators +3. If too many validators go down (<80% available), the network **stops validating** new ledgers (it does not get corrupted, it just pauses) +4. When enough validators come back, the network resumes automatically + +### Amendments and protocol voting + +**Amendments** are the mechanism by which Xahau updates its protocol in a decentralized way: + +1. A developer proposes a change to the protocol and implements it with a unique amendment ID +2. Validators **vote** on whether they support activating that amendment +3. If an amendment receives support from **80% of validators** for **2 consecutive weeks**, it is automatically activated +4. Once activated, it is permanent and irreversible + +Examples of amendments include: new transaction types, new blockchain features. + +### Decentralization metrics + +How do you measure if a network is truly decentralized? Some key metrics: + +- **Nakamoto Coefficient**: The minimum number of entities that would need to collude to compromise the network. The higher, the better +- **Geographic distribution**: Validators in different countries and continents +- **Operator diversity**: Different types of entities (companies, universities, individuals) +- **Infrastructure diversity**: Different hosting providers, not all on AWS or Google Cloud +- **UNL overlap**: What percentage of validators the different UNLs share`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Consultar server_info y campos de validadores", + en: "Query server_info and validator fields", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function inspectValidatorInfo() { + const client = new Client("wss://xahau.network"); + await client.connect(); + + const response = await client.request({ + command: "server_info" + }); + + const info = response.result.info; + + console.log("=== Server information ==="); + console.log("Server version:", info.build_version); + console.log("Status:", info.server_state); + console.log(""); + + console.log("=== Consensus status ==="); + console.log("Validation quorum:", info.validation_quorum); + console.log("Validated ledger:", info.validated_ledger.seq); + console.log("Ledger hash:", info.validated_ledger.hash); + console.log("Ledger age:", info.validated_ledger.age, "seconds"); + console.log("Base reserve:", info.validated_ledger.reserve_base_xrp, "XAH"); + console.log("Reserve per object:", info.validated_ledger.reserve_inc_xrp, "XAH"); + console.log(""); + + console.log("=== Network metrics ==="); + console.log("Connected peers:", info.peers); + console.log("Uptime:", info.uptime, "seconds"); + console.log("Server load:", info.load_factor); + + await client.disconnect(); +} + +inspectValidatorInfo();`, + }, + { + title: { + es: "Consultar las tarifas actuales de la red", + en: "Query current network fees", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function checkNetworkFees() { + const client = new Client("wss://xahau.network"); + await client.connect(); + + // Fee command: shows current network fees + const feeResponse = await client.request({ + command: "fee" + }); + + const fee = feeResponse.result; + console.log("=== Current network fees ==="); + console.log("Base fee (drops):", fee.drops.base_fee); + console.log("Median fee (drops):", fee.drops.median_fee); + console.log("Minimum fee (drops):", fee.drops.minimum_fee); + console.log("Open ledger fee (drops):", fee.drops.open_ledger_fee); + console.log(""); + + // Convert drops to XAH (1 XAH = 1,000,000 drops) + const baseFeeXAH = Number(fee.drops.base_fee) / 1_000_000; + const medianFeeXAH = Number(fee.drops.median_fee) / 1_000_000; + console.log("=== In XAH ==="); + console.log("Base fee:", baseFeeXAH, "XAH"); + console.log("Median fee:", medianFeeXAH, "XAH"); + console.log(""); + + console.log("=== Ledger status ==="); + console.log("Current ledger:", fee.ledger_current_index); + console.log("Expected load levels:", fee.levels.median_level); + + await client.disconnect(); +} + +checkNetworkFees();`, + }, + ], + slides: [ + { + title: { es: "¿Quién opera los validadores?", en: "Who operates the validators?", jp: "" }, + content: { + es: "La diversidad es clave para la seguridad:\n\n🏛️ Fundaciones del ecosistema\n🏢 Empresas que construyen sobre Xahau\n🎓 Universidades e instituciones\n👩‍💻 Desarrolladores independientes\n\nIndependientes, en distintas jurisdicciones\nSin requisito de staking", + en: "Diversity is key to security:\n\n🏛️ Ecosystem foundations\n🏢 Companies building on Xahau\n🎓 Universities and institutions\n👩‍💻 Independent developers\n\nIndependent, in different jurisdictions\nNo staking requirement", + jp: "", + }, + visual: "🌐", + }, + { + title: { es: "Enmiendas: gobernanza descentralizada", en: "Amendments: decentralized governance", jp: "" }, + content: { + es: "Las actualizaciones del protocolo\nse votan de forma descentralizada:\n\n1. Se propone un cambio (amendment)\n2. Los validadores votan a favor o en contra\n3. 80% de apoyo durante 2 semanas\n4. Se activa automáticamente\n5. Es permanente e irreversible", + en: "Protocol updates\nare voted on in a decentralized way:\n\n1. A change is proposed (amendment)\n2. Validators vote for or against\n3. 80% support for 2 weeks\n4. It is activated automatically\n5. It is permanent and irreversible", + jp: "", + }, + visual: "🗳️", + }, + { + title: { es: "Midiendo la descentralización", en: "Measuring decentralization", jp: "" }, + content: { + es: "Métricas clave:\n\n📊 Coeficiente Nakamoto (mín. entidades para atacar)\n🌍 Distribución geográfica\n🏛️ Diversidad de operadores\n☁️ Diversidad de infraestructura\n🔗 Solapamiento de UNLs\n\nMás diversidad = más seguridad", + en: "Key metrics:\n\n📊 Nakamoto Coefficient (min. entities to attack)\n🌍 Geographic distribution\n🏛️ Operator diversity\n☁️ Infrastructure diversity\n🔗 UNL overlap\n\nMore diversity = more security", + jp: "", + }, + visual: "📊", + }, + ], + }, + ], +} diff --git a/src/data/modules/m03-primera-wallet.js b/src/data/modules/m03-primera-wallet.js new file mode 100644 index 0000000..fc1d312 --- /dev/null +++ b/src/data/modules/m03-primera-wallet.js @@ -0,0 +1,1279 @@ +export default { + id: "m3", + icon: "👛", + title: { + es: "Generación de tu primera wallet", + en: "Generating your first wallet", + jp: "", + }, + lessons: [ + { + id: "m3l1", + title: { + es: "Criptografía y claves en Xahau", + en: "Cryptography and keys in Xahau", + jp: "", + }, + theory: { + es: `Antes de interactuar con Xahau, necesitas una **wallet** (cartera). Una wallet no es más que un par de claves criptográficas que te permiten firmar transacciones y demostrar la propiedad de tu cuenta. + +### Par de claves + +En Xahau (y en muchas otras blockchains), cada cuenta se basa en criptografía de curva elíptica: + +- **Clave privada (Secret/Seed)**: Un valor secreto que NUNCA debes compartir. Se usa para firmar transacciones. Suele representarse como un "family seed" que empieza por \`s\` (ej: \`sEdV....\`) +- **Clave pública**: Se deriva de la clave privada. Se usa para verificar firmas +- **Dirección (Account)**: Se deriva de la clave pública. Empieza por \`r\` (ej: \`rHb9CJ...\`). Es tu identificador público en la red + +### Algoritmos soportados + +Xahau soporta dos algoritmos de firma: +- **secp256k1**: El mismo que usa Bitcoin. Es el algoritmo por defecto +- **ed25519**: Más moderno y eficiente. Recomendado para nuevas cuentas + +**Nota:** La librería \`xahau js\`, deriva por defecto en **ed25519** si no se especifica el algoritmo. El faucet de [xahau-test.net](https://xahau-test.net) genera las wallets con **secp256k1**, por lo que verás que los ejemplos de código de este curso se especifica este algoritmo cuando generamos las wallets. + +### Activación de cuenta + +A diferencia de Ethereum, en Xahau una cuenta **no existe en el ledger hasta que recibe su primer depósito**. Se necesita un mínimo de **1 XAH** (reserve base) para activar una cuenta. Este XAH queda bloqueado como reserva mientras la cuenta exista. + +### Seguridad + +- Nunca compartas tu clave privada (seed/secret) +- Usa la **testnet** para pruebas (tokens sin valor real) +- Guarda tus seeds de mainnet en un lugar seguro y offline`, + en: `Before interacting with Xahau, you need a **wallet**. A wallet is simply a pair of cryptographic keys that allow you to sign transactions and prove ownership of your account. + +### Key pair + +In Xahau (and many other blockchains), each account is based on elliptic curve cryptography: + +- **Private key (Secret/Seed)**: A secret value that you should NEVER share. It is used to sign transactions. It is usually represented as a "family seed" starting with \`s\` (e.g.: \`sEdV....\`) +- **Public key**: Derived from the private key. Used to verify signatures +- **Address (Account)**: Derived from the public key. Starts with \`r\` (e.g.: \`rHb9CJ...\`). It is your public identifier on the network + +### Supported algorithms + +Xahau supports two signing algorithms: +- **secp256k1**: The same one used by Bitcoin. It is the default algorithm +- **ed25519**: More modern and efficient. Recommended for new accounts + +**Note:** The \`xahau js\` library derives by default to **ed25519** if no algorithm is specified. The faucet at [xahau-test.net](https://xahau-test.net) generates wallets with **secp256k1**, so you will see that the code examples in this course specify this algorithm when generating wallets. + +### Account activation + +Unlike Ethereum, in Xahau an account **does not exist on the ledger until it receives its first deposit**. A minimum of **1 XAH** (base reserve) is needed to activate an account. This XAH remains locked as a reserve as long as the account exists. + +### Security + +- Never share your private key (seed/secret) +- Use the **testnet** for testing (tokens with no real value) +- Store your mainnet seeds in a secure, offline location`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Generar una wallet nueva", + en: "Generate a new wallet", + jp: "", + }, + language: "javascript", + code: `const { ECDSA, Wallet } = require("xahau"); + +// Generate wallet with default algorithm (secp256k1) +const wallet1 = Wallet.generate(ECDSA.secp256k1); +console.log("=== Wallet secp256k1 ==="); +console.log("Address:", wallet1.address); +console.log("Public key:", wallet1.publicKey); +console.log("Seed:", wallet1.seed); + +// Generate wallet with ed25519 algorithm +const wallet2 = Wallet.generate(); +console.log("\n=== Wallet ed25519 ==="); +console.log("Address:", wallet2.address); +console.log("Public key:", wallet2.publicKey); +console.log("Seed:", wallet2.seed);`, + }, + { + title: { + es: "Restaurar una wallet desde un seed existente", + en: "Restore a wallet from an existing seed", + jp: "", + }, + language: "javascript", + code: `const { Wallet } = require("xahau"); + +// Restore wallet from an existing seed +// (use your own testnet seed) +const seed = "sEdVHBhkL2next8NH9cMPyPJoXXXXXX"; +// If you prefer to derive it in ed25519, remove {algorithm: 'secp256k1'} since it will use ed25519 by default +const wallet = Wallet.fromSeed(seed, {algorithm: 'secp256k1'}); + +console.log("Address:", wallet.address); +console.log("Public key:", wallet.publicKey); +console.log("Seed:", wallet.seed); + +// The same seed always generates the same address +// Never share your seed!`, + }, + ], + slides: [ + { + title: { es: "¿Qué es una Wallet?", en: "What is a Wallet?", jp: "" }, + content: { + es: "Un par de claves criptográficas:\n\n🔑 Clave privada (seed) → Firmar transacciones\n📢 Clave pública → Verificar firmas\n📍 Dirección (r...) → Tu identidad en la red", + en: "A pair of cryptographic keys:\n\n🔑 Private key (seed) → Sign transactions\n📢 Public key → Verify signatures\n📍 Address (r...) → Your identity on the network", + jp: "", + }, + visual: "👛", + }, + { + title: { es: "Algoritmos de firma", en: "Signing algorithms", jp: "" }, + content: { + es: "Xahau soporta dos algoritmos:\n\n• secp256k1 → Igual que Bitcoin (por defecto)\n• ed25519 → Más moderno y eficiente\n\nAmbos son seguros y válidos", + en: "Xahau supports two algorithms:\n\n• secp256k1 → Same as Bitcoin (default)\n• ed25519 → More modern and efficient\n\nBoth are secure and valid", + jp: "", + }, + visual: "🔐", + }, + { + title: { es: "Activación de cuenta", en: "Account activation", jp: "" }, + content: { + es: "Una cuenta NO existe hasta que recibe\nsu primer depósito\n\n• Mínimo 1 XAH de reserva base\n• Este XAH queda bloqueado\n• En testnet: usa el faucet gratuito", + en: "An account does NOT exist until it receives\nits first deposit\n\n• Minimum 1 XAH base reserve\n• This XAH remains locked\n• On testnet: use the free faucet", + jp: "", + }, + visual: "✨", + }, + ], + }, + { + id: "m3l2", + title: { + es: "Activar tu wallet en testnet", + en: "Activate your wallet on testnet", + jp: "", + }, + theory: { + es: `Ahora que sabes generar una wallet, el siguiente paso es **activarla** en la red. Para desarrollo y pruebas, usaremos la **testnet de Xahau** donde los tokens no tienen valor real. + +### ¿Qué es la testnet? + +La testnet es una copia de la red Xahau diseñada para desarrollo: +- Los tokens (test XAH) **no tienen valor real** +- Puedes obtener tokens gratis desde el **faucet** +- Las transacciones funcionan igual que en mainnet +- Es el lugar perfecto para aprender y experimentar + +### Faucet + +El faucet es un servicio que envía tokens de prueba a tu wallet. Puedes usarlo directamente desde código con la librería \`xahau\`. También puedes conseguir una wallet con test XAH desde la interfaz web del faucet: [xahau-test.net](https://xahau-test.net). Puedes utilizar la seed después en tu código o importarla a Xaman. + +### Verificar tu cuenta + +Una vez activada tu cuenta, puedes verificar su existencia consultando el comando \`account_info\`. Este te mostrará: +- **Balance**: Cantidad de XAH en tu cuenta (en drops: 1 XAH = 1,000,000 drops) +- **Sequence**: Número de secuencia para la próxima transacción +- **Flags**: Configuración de la cuenta +- **OwnerCount**: Número de objetos que posee la cuenta en el ledger`, + en: `Now that you know how to generate a wallet, the next step is to **activate** it on the network. For development and testing, we will use the **Xahau testnet** where tokens have no real value. + +### What is the testnet? + +The testnet is a copy of the Xahau network designed for development: +- Tokens (test XAH) **have no real value** +- You can get free tokens from the **faucet** +- Transactions work the same as on mainnet +- It is the perfect place to learn and experiment + +### Faucet + +The faucet is a service that sends test tokens to your wallet. You can use it directly from code with the \`xahau\` library. You can also get a wallet with test XAH from the faucet web interface: [xahau-test.net](https://xahau-test.net). You can then use the seed in your code or import it into Xaman. + +### Verify your account + +Once your account is activated, you can verify its existence by querying the \`account_info\` command. It will show you: +- **Balance**: Amount of XAH in your account (in drops: 1 XAH = 1,000,000 drops) +- **Sequence**: Sequence number for the next transaction +- **Flags**: Account configuration +- **OwnerCount**: Number of objects the account owns on the ledger`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Crear y activar una wallet en testnet usando el faucet", + en: "Create and activate a wallet on testnet using the faucet", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function createTestnetWallet() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Generate a new wallet + const wallet = Wallet.generate(); + console.log("Wallet generated:"); + console.log(" Address:", wallet.address); + console.log(" Seed:", wallet.seed); + + // Request funds from the testnet faucet + console.log("\\nRequesting funds from faucet..."); + const fundResult = await client.fundWallet(wallet); + + console.log("Wallet funded!"); + console.log(" Balance:", fundResult.balance, "XAH"); + + // Verify the account on the ledger + const response = await client.request({ + command: "account_info", + account: wallet.address, + ledger_index: "validated", + }); + + const account = response.result.account_data; + console.log("\\nAccount data on the ledger:"); + console.log(" Balance:", account.Balance, "drops"); + console.log(" Balance:", Number(account.Balance) / 1_000_000, "XAH"); + console.log(" Sequence:", account.Sequence); + + await client.disconnect(); +} + +createTestnetWallet();`, + }, + { + title: { + es: "Consultar el balance de una cuenta existente", + en: "Check the balance of an existing account", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function checkBalance(address) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + try { + const response = await client.request({ + command: "account_info", + account: address, + ledger_index: "validated", + }); + + const account = response.result.account_data; + console.log("Account:", account.Account); + console.log("Balance:", Number(account.Balance) / 1_000_000, "XAH"); + console.log("Sequence:", account.Sequence); + console.log("Owner Count:", account.OwnerCount); + } catch (error) { + if (error.data?.error === "actNotFound") { + console.log("The account does not exist on the ledger."); + console.log("It needs to receive at least 1 XAH to be activated."); + } else { + console.error("Error:", error.message); + } + } + + await client.disconnect(); +} + +// Replace with your testnet address +checkBalance("rYourXahauAddressHere");`, + }, + ], + slides: [ + { + title: { es: "Testnet de Xahau", en: "Xahau Testnet", jp: "" }, + content: { + es: "Red de pruebas para desarrollo\n\n• Tokens sin valor real\n• Faucet gratuito para obtener test XAH\n• Funciona igual que mainnet\n• Perfecto para aprender", + en: "Test network for development\n\n• Tokens with no real value\n• Free faucet to get test XAH\n• Works the same as mainnet\n• Perfect for learning", + jp: "", + }, + visual: "🧪", + }, + { + title: { es: "Flujo de activación", en: "Activation flow", jp: "" }, + content: { + es: "1️⃣ Generar wallet (par de claves)\n2️⃣ Obtener XAH del faucet\n3️⃣ El faucet envía un pago\n4️⃣ La cuenta se crea en el ledger\n5️⃣ ¡Lista para usar!", + en: "1️⃣ Generate wallet (key pair)\n2️⃣ Get XAH from the faucet\n3️⃣ The faucet sends a payment\n4️⃣ The account is created on the ledger\n5️⃣ Ready to use!", + jp: "", + }, + visual: "🚀", + }, + { + title: { es: "Verificar con account_info", en: "Verify with account_info", jp: "" }, + content: { + es: "Comando account_info para confirmar activación:\n\n• Balance → XAH disponible (en drops)\n• Sequence → Número de próxima transacción\n• Flags → Configuración de la cuenta\n• OwnerCount → Objetos en el ledger\n\nSi la cuenta no existe: error actNotFound\n1 XAH = 1,000,000 drops", + en: "account_info command to confirm activation:\n\n• Balance → Available XAH (in drops)\n• Sequence → Next transaction number\n• Flags → Account configuration\n• OwnerCount → Objects on the ledger\n\nIf the account does not exist: actNotFound error\n1 XAH = 1,000,000 drops", + jp: "", + }, + visual: "🔎", + }, + ], + }, + { + id: "m3l2b", + title: { + es: "Comprobar tu cuenta en exploradores de bloques", + en: "Check your account on block explorers", + jp: "", + }, + theory: { + es: `Una vez que tu cuenta está activada en la testnet (o en mainnet), puedes verificar su estado usando **exploradores de bloques**: aplicaciones web que permiten consultar cualquier cuenta, transacción o ledger de forma visual y sin necesidad de escribir código. + +### ¿Qué es un explorador de bloques? + +Un **explorador de bloques** es una herramienta web que se conecta a los nodos de Xahau y te presenta la información de la blockchain de forma legible. Es como un "buscador" de la blockchain. + +Con un explorer puedes: +- Ver el **balance** y los **tokens** de cualquier cuenta +- Consultar el **historial de transacciones** completo +- Inspeccionar los **detalles** de cualquier transacción (hash, campos, resultado) +- Ver los **objetos del ledger** asociados a una cuenta (trust lines, ofertas, hooks) +- Verificar si una transacción se procesó correctamente + +### Exploradores de Xahau Mainnet + +**Xahau Explorer** — [xahauexplorer.com](https://xahauexplorer.com) +**XRPLWin Xahau** — [xahau.xrplwin.com](https://xahau.xrplwin.com) +**Xahau Network Explorer** — [explorer.xahau.network](https://explorer.xahau.network) +**XahScan** — [xahscan.com](https://xahscan.com) + + +### Exploradores de Xahau Testnet + +Para consultar cuentas de la **testnet** (que es la que usamos en el curso), usa estos exploradores: + +- [test.xahauexplorer.com](https://test.xahauexplorer.com) +- [xahau-testnet.xrplwin.com](https://xahau-testnet.xrplwin.com) +- [explorer.xahau-test.net](https://explorer.xahau-test.net) + +### Cómo consultar tu cuenta + +1. Abre cualquiera de los exploradores de testnet +2. En la barra de búsqueda, pega tu **dirección** (empieza por \`r\`) +3. Pulsa Enter o haz clic en buscar +4. Verás la información de tu cuenta: + - **Balance** en XAH + - **Tokens** que posees (trust lines) + - **Transacciones** recientes + - **Flags** y configuración de la cuenta + - **Objetos** del ledger asociados + +### Consultar una transacción + +Cada transacción tiene un **hash** único (una cadena hexadecimal larga). Puedes buscar ese hash en el explorer para ver: +- **Tipo** de transacción (Payment, TrustSet, AccountSet, etc.) +- **Cuenta origen** y **destino** +- **Cantidad** enviada +- **Resultado** (tesSUCCESS, tecPATH_DRY, etc.) +- **Fee** pagado +- **Ledger** en el que se incluyó +- **Cambios** en el estado del ledger (AffectedNodes) + +### ¿Por qué usar explorers? + +- **Verificación visual**: Confirmar que una transacción se procesó correctamente sin escribir código +- **Depuración**: Cuando algo falla, el explorer muestra todos los detalles del error +- **Transparencia**: Cualquier persona puede verificar cualquier operación en la blockchain +- **Aprendizaje**: Ver transacciones reales te ayuda a entender cómo funciona la red por dentro`, + en: `Once your account is activated on the testnet (or on mainnet), you can verify its status using **block explorers**: web applications that allow you to query any account, transaction, or ledger visually and without writing code. + +### What is a block explorer? + +A **block explorer** is a web tool that connects to Xahau nodes and presents blockchain information in a readable format. It is like a "search engine" for the blockchain. + +With an explorer you can: +- View the **balance** and **tokens** of any account +- Check the complete **transaction history** +- Inspect the **details** of any transaction (hash, fields, result) +- View the **ledger objects** associated with an account (trust lines, offers, hooks) +- Verify if a transaction was processed successfully + +### Xahau Mainnet Explorers + +**Xahau Explorer** — [xahauexplorer.com](https://xahauexplorer.com) +**XRPLWin Xahau** — [xahau.xrplwin.com](https://xahau.xrplwin.com) +**Xahau Network Explorer** — [explorer.xahau.network](https://explorer.xahau.network) +**XahScan** — [xahscan.com](https://xahscan.com) + + +### Xahau Testnet Explorers + +To check accounts on the **testnet** (which is the one we use in this course), use these explorers: + +- [test.xahauexplorer.com](https://test.xahauexplorer.com) +- [xahau-testnet.xrplwin.com](https://xahau-testnet.xrplwin.com) +- [explorer.xahau-test.net](https://explorer.xahau-test.net) + +### How to check your account + +1. Open any of the testnet explorers +2. In the search bar, paste your **address** (starts with \`r\`) +3. Press Enter or click search +4. You will see your account information: + - **Balance** in XAH + - **Tokens** you hold (trust lines) + - Recent **transactions** + - **Flags** and account configuration + - Associated ledger **objects** + +### Check a transaction + +Each transaction has a unique **hash** (a long hexadecimal string). You can search for that hash in the explorer to see: +- Transaction **type** (Payment, TrustSet, AccountSet, etc.) +- **Source** and **destination** account +- **Amount** sent +- **Result** (tesSUCCESS, tecPATH_DRY, etc.) +- **Fee** paid +- **Ledger** it was included in +- **Changes** to the ledger state (AffectedNodes) + +### Why use explorers? + +- **Visual verification**: Confirm that a transaction was processed successfully without writing code +- **Debugging**: When something fails, the explorer shows all error details +- **Transparency**: Anyone can verify any operation on the blockchain +- **Learning**: Viewing real transactions helps you understand how the network works internally`, + jp: "", + }, + codeBlocks: [], + slides: [ + { + title: { es: "¿Qué es un block explorer?", en: "What is a block explorer?", jp: "" }, + content: { + es: "Una herramienta web para consultar la blockchain\nsin escribir código\n\n• Ver balances y tokens de cualquier cuenta\n• Consultar historial de transacciones\n• Inspeccionar detalles de cada operación\n• Verificar resultados y errores", + en: "A web tool to query the blockchain\nwithout writing code\n\n• View balances and tokens of any account\n• Check transaction history\n• Inspect details of each operation\n• Verify results and errors", + jp: "", + }, + visual: "🔍", + }, + { + title: { es: "Exploradores de Xahau", en: "Xahau Explorers", jp: "" }, + content: { + es: "Mainnet:\n• xahauexplorer.com\n• xahau.xrplwin.com\n• explorer.xahau.network\n• xahscan.com\n\nTestnet (para el curso):\n• test.xahauexplorer.com\n• xahau-testnet.xrplwin.com\n• explorer.xahau-test.net", + en: "Mainnet:\n• xahauexplorer.com\n• xahau.xrplwin.com\n• explorer.xahau.network\n• xahscan.com\n\nTestnet (for the course):\n• test.xahauexplorer.com\n• xahau-testnet.xrplwin.com\n• explorer.xahau-test.net", + jp: "", + }, + visual: "🌐", + }, + { + title: { es: "Cómo consultar tu cuenta", en: "How to check your account", jp: "" }, + content: { + es: "1️⃣ Abre un explorer de testnet\n2️⃣ Pega tu dirección (r...)\n3️⃣ Verás:\n • Balance en XAH\n • Tokens y trust lines\n • Historial de transacciones\n • Flags y configuración\n\nTambién puedes buscar por hash de transacción", + en: "1️⃣ Open a testnet explorer\n2️⃣ Paste your address (r...)\n3️⃣ You will see:\n • Balance in XAH\n • Tokens and trust lines\n • Transaction history\n • Flags and configuration\n\nYou can also search by transaction hash", + jp: "", + }, + visual: "📋", + }, + ], + }, + { + id: "m3l3", + title: { + es: "Seguridad de wallets y buenas prácticas", + en: "Wallet security and best practices", + jp: "", + }, + theory: { + es: `La seguridad de tu wallet es lo más importante al trabajar con blockchain. Una wallet comprometida significa la **pérdida total e irreversible** de tus fondos. En esta lección aprenderás las mejores prácticas para proteger tu cuenta. + +### Nunca compartas tu seed/clave secreta + +Tu seed (clave privada) es la **única forma de controlar tu cuenta**. Quien tenga tu seed puede firmar cualquier transacción en tu nombre: enviar todos tus fondos, cambiar configuraciones, etc. No hay forma de revertir esto. + +Reglas fundamentales: +- **Nunca** envíes tu seed por chat, email o ningún medio digital +- **Nunca** la introduzcas en sitios web o aplicaciones que no sean de absoluta confianza +- **Nunca** la guardes en texto plano en tu ordenador +- **Nunca** hagas captura de pantalla o foto de tu seed + +### Hot Wallet vs Cold Wallet + +**Hot Wallet (cartera caliente)**: +- Conectada a internet permanentemente +- Conveniente para transacciones frecuentes +- Mayor riesgo de ser comprometida +- Ejemplo: wallet en una aplicación web, bot de trading + +**Cold Wallet (cartera fría)**: +- Desconectada de internet +- Máxima seguridad para almacenamiento a largo plazo +- Menos conveniente para uso diario +- Ejemplo: wallet generada offline, hardware wallet, paper wallet + +### Buenas prácticas para almacenar seeds + +1. **Offline**: Genera y guarda seeds en un dispositivo que nunca se conecte a internet +2. **Hardware wallet**: Dispositivos especializados (Ledger, Trezor) que almacenan claves de forma segura +3. **Paper wallet**: Escribe la seed en papel y guárdala en un lugar seguro (caja fuerte, caja de seguridad bancaria) +4. **Múltiples copias**: Guarda copias en diferentes ubicaciones físicas por si hay incendio, inundación, etc. +5. **Metal backup**: Graba tu seed en una placa de metal resistente al fuego y al agua + +### Seeds de testnet: la excepción + +Las seeds de **testnet** son seguras de compartir en contextos educativos porque: +- Los tokens de testnet **no tienen valor real** +- La testnet se puede resetear en cualquier momento +- Son útiles para depurar problemas con otros desarrolladores + +Aun así, es buena práctica tratarlas con cuidado para crear buenos hábitos. + +### Estafas comunes y cómo evitarlas + +**Phishing**: +- Sitios web falsos que imitan interfaces legítimas +- Te piden ingresar tu seed para "verificar" tu cuenta +- Siempre verifica la URL y no hagas clic en enlaces sospechosos + +**Fake dApps**: +- Aplicaciones que prometen rendimientos irreales +- Piden permisos excesivos o tu seed directamente +- Investiga siempre el código fuente y la reputación del proyecto + +**Ingeniería social**: +- Personas que se hacen pasar por soporte técnico +- Ofrecen "ayuda" a cambio de tu seed +- Ningún soporte legítimo te pedirá jamás tu clave privada + +**Airdrops falsos**: +- Tokens que aparecen en tu wallet sin pedirlos +- Al intentar interactuar con ellos, te redirigen a sitios maliciosos +- Ignora tokens desconocidos que no esperabas recibir + +### Regular Keys: cambiar la clave de firma + +Xahau ofrece una funcionalidad avanzada llamada **Regular Key**: puedes asignar un **par de claves alternativo** que tenga permiso para firmar transacciones en nombre de tu cuenta. + +Ventajas: +- Si la regular key se compromete, puedes cambiarla por otra nueva sin cambiar tu dirección +- Puedes desactivar la clave maestra y usar solo la regular key para operaciones diarias +- La dirección de tu cuenta permanece igual + +### Master Key Disable: seguridad avanzada + +Para máxima seguridad, puedes **desactivar tu clave maestra** (master key disable): +1. Primero, configuras una regular key +2. Luego, desactivas la master key con un flag de cuenta +3. Ahora solo la regular key puede firmar transacciones +4. Si la regular key se compromete, puedes reactivar la master key para recuperar el control + +Esto añade una capa extra de protección: incluso si alguien obtiene tu master seed, no podrá usarlo mientras esté desactivado. + +### Multi-signing: múltiples firmas + +Para cuentas de alto valor o gobernanza, Xahau soporta **multi-signing**: + +- Se configura una lista de firmantes autorizados con un **quorum** (peso mínimo requerido) +- Cada firmante tiene un peso asignado +- Una transacción solo es válida si recibe suficientes firmas para alcanzar el quorum +- Ejemplo: 3 firmantes con peso 1 cada uno, quorum de 2 → se necesitan al menos 2 de 3 firmas + +Multi-signing es ideal para: +- Tesorerías de organizaciones +- Cuentas compartidas entre socios +- Cualquier situación donde una sola persona no debería tener control total`, + en: `Wallet security is the most important thing when working with blockchain. A compromised wallet means the **total and irreversible loss** of your funds. In this lesson you will learn the best practices to protect your account. + +### Never share your seed/secret key + +Your seed (private key) is the **only way to control your account**. Anyone who has your seed can sign any transaction on your behalf: send all your funds, change configurations, etc. There is no way to reverse this. + +Fundamental rules: +- **Never** send your seed via chat, email, or any digital medium +- **Never** enter it on websites or applications that are not absolutely trustworthy +- **Never** store it in plain text on your computer +- **Never** take a screenshot or photo of your seed + +### Hot Wallet vs Cold Wallet + +**Hot Wallet**: +- Permanently connected to the internet +- Convenient for frequent transactions +- Higher risk of being compromised +- Example: wallet in a web application, trading bot + +**Cold Wallet**: +- Disconnected from the internet +- Maximum security for long-term storage +- Less convenient for daily use +- Example: wallet generated offline, hardware wallet, paper wallet + +### Best practices for storing seeds + +1. **Offline**: Generate and store seeds on a device that never connects to the internet +2. **Hardware wallet**: Specialized devices (Ledger, Trezor) that store keys securely +3. **Paper wallet**: Write the seed on paper and store it in a safe place (safe, bank safety deposit box) +4. **Multiple copies**: Keep copies in different physical locations in case of fire, flood, etc. +5. **Metal backup**: Engrave your seed on a fire-resistant and water-resistant metal plate + +### Testnet seeds: the exception + +**Testnet** seeds are safe to share in educational contexts because: +- Testnet tokens **have no real value** +- The testnet can be reset at any time +- They are useful for debugging issues with other developers + +Even so, it is good practice to treat them carefully to build good habits. + +### Common scams and how to avoid them + +**Phishing**: +- Fake websites that imitate legitimate interfaces +- They ask you to enter your seed to "verify" your account +- Always verify the URL and do not click on suspicious links + +**Fake dApps**: +- Applications that promise unrealistic returns +- They request excessive permissions or your seed directly +- Always investigate the source code and the project's reputation + +**Social engineering**: +- People pretending to be technical support +- They offer "help" in exchange for your seed +- No legitimate support will ever ask for your private key + +**Fake airdrops**: +- Tokens that appear in your wallet without requesting them +- When trying to interact with them, they redirect you to malicious sites +- Ignore unknown tokens that you did not expect to receive + +### Regular Keys: changing the signing key + +Xahau offers an advanced feature called **Regular Key**: you can assign an **alternative key pair** that has permission to sign transactions on behalf of your account. + +Advantages: +- If the regular key is compromised, you can change it to a new one without changing your address +- You can disable the master key and use only the regular key for daily operations +- Your account address remains the same + +### Master Key Disable: advanced security + +For maximum security, you can **disable your master key** (master key disable): +1. First, you set up a regular key +2. Then, you disable the master key with an account flag +3. Now only the regular key can sign transactions +4. If the regular key is compromised, you can reactivate the master key to regain control + +This adds an extra layer of protection: even if someone obtains your master seed, they cannot use it while it is disabled. + +### Multi-signing: multiple signatures + +For high-value or governance accounts, Xahau supports **multi-signing**: + +- A list of authorized signers is configured with a **quorum** (minimum required weight) +- Each signer has an assigned weight +- A transaction is only valid if it receives enough signatures to reach the quorum +- Example: 3 signers with weight 1 each, quorum of 2 → at least 2 of 3 signatures are needed + +Multi-signing is ideal for: +- Organization treasuries +- Shared accounts between partners +- Any situation where a single person should not have total control`, + jp: "", + }, + codeBlocks: [], + slides: [ + { + title: { es: "La regla de oro", en: "The golden rule", jp: "" }, + content: { + es: "NUNCA compartas tu seed/clave privada\n\n❌ No por chat ni email\n❌ No en sitios web dudosos\n❌ No en texto plano en tu PC\n❌ No en capturas de pantalla\n\nQuien tiene tu seed\ntiene TODOS tus fondos", + en: "NEVER share your seed/private key\n\n❌ Not via chat or email\n❌ Not on suspicious websites\n❌ Not in plain text on your PC\n❌ Not in screenshots\n\nWhoever has your seed\nhas ALL your funds", + jp: "", + }, + visual: "🔒", + }, + { + title: { es: "Hot Wallet vs Cold Wallet", en: "Hot Wallet vs Cold Wallet", jp: "" }, + content: { + es: "🔥 Hot Wallet (conectada)\n• Conveniente para uso diario\n• Mayor riesgo\n• Apps, bots de trading\n\n🧊 Cold Wallet (desconectada)\n• Máxima seguridad\n• Almacenamiento largo plazo\n• Hardware wallet, papel, metal", + en: "🔥 Hot Wallet (connected)\n• Convenient for daily use\n• Higher risk\n• Apps, trading bots\n\n🧊 Cold Wallet (disconnected)\n• Maximum security\n• Long-term storage\n• Hardware wallet, paper, metal", + jp: "", + }, + visual: "🔥", + }, + { + title: { es: "Estafas comunes", en: "Common scams", jp: "" }, + content: { + es: "🎣 Phishing → Sitios web falsos\n🤖 Fake dApps → Rendimientos irreales\n🎭 Ingeniería social → Falso soporte\n🪂 Airdrops falsos → Tokens trampa\n\nRegla: NADIE legítimo te pedirá\ntu clave privada. Jamás.", + en: "🎣 Phishing → Fake websites\n🤖 Fake dApps → Unrealistic returns\n🎭 Social engineering → Fake support\n🪂 Fake airdrops → Trap tokens\n\nRule: NOBODY legitimate will ask\nfor your private key. Ever.", + jp: "", + }, + visual: "⚠️", + }, + { + title: { es: "Seguridad avanzada en Xahau", en: "Advanced security in Xahau", jp: "" }, + content: { + es: "🔑 Regular Key\n Clave alternativa para firmar\n (se puede cambiar sin cambiar dirección)\n\n🚫 Master Key Disable\n Desactivar la clave maestra\n (capa extra de protección)\n\n👥 Multi-signing\n Múltiples firmas requeridas\n (ideal para organizaciones)", + en: "🔑 Regular Key\n Alternative key for signing\n (can be changed without changing address)\n\n🚫 Master Key Disable\n Disable the master key\n (extra layer of protection)\n\n👥 Multi-signing\n Multiple signatures required\n (ideal for organizations)", + jp: "", + }, + visual: "🛡️", + }, + ], + }, + { + id: "m3l4", + title: { + es: "Configuración de tu cuenta con AccountSet", + en: "Configuring your account with AccountSet", + jp: "", + }, + theory: { + es: `En Xahau, tu cuenta tiene múltiples opciones de configuración que puedes activar o desactivar usando la transacción **AccountSet**. Estas configuraciones controlan el comportamiento de tu cuenta frente a pagos entrantes, trust lines, y más. + +### La transacción AccountSet + +\`AccountSet\` es el tipo de transacción que te permite modificar las propiedades de tu cuenta. No envía ni recibe fondos, simplemente cambia los **flags** (banderas) y otros campos de configuración de tu cuenta. + +### Flags importantes + +**asfRequireDest (RequireDestTag)** +- Requiere que todos los pagos entrantes incluyan un **Destination Tag** +- Útil para exchanges y servicios que usan un tag para identificar al usuario +- Sin este flag, alguien podría enviarte XAH sin tag y sería imposible saber de quién viene +- Flag ID: \`1\` + +**asfDisallowXRP (DisallowXAH)** +- Señala que tu cuenta **no desea recibir XAH directamente** +- Es solo una señal, técnicamente los pagos aún pueden llegar +- Útil para cuentas que solo trabajan con tokens emitidos (IOUs) +- Flag ID: \`3\` + +**asfDefaultRipple** +- Relevante para **emisores de tokens** (lo veremos en profundidad en el módulo de tokens) +- Permite que los tokens emitidos por tu cuenta puedan fluir entre terceros (rippling) +- Sin este flag, los tokens solo pueden moverse directamente hacia/desde el emisor +- Flag ID: \`8\` + +**asfRequireAuth** +- Requiere que tu cuenta **autorice** cada trust line antes de que alguien pueda mantener tus tokens +- Útil para tokens regulados donde necesitas controlar quién puede poseerlos +- Flag ID: \`2\` + +### Otros campos configurables + +**Domain**: Puedes asociar un dominio web a tu cuenta. Se almacena como el valor hexadecimal del dominio. Esto permite verificar que la cuenta pertenece al dueño de ese dominio. + +**EmailHash**: Hash MD5 de tu email, utilizado para mostrar un avatar (como Gravatar). No expone tu email directamente. + +### Account Delete: eliminar tu cuenta + +En Xahau es posible **eliminar una cuenta** del ledger para recuperar parte de la reserva: + +Requisitos: +- El número de secuencia de la cuenta debe ser al menos 256 +- La cuenta no debe poseer objetos en el ledger (ofertas, trust lines, etc.) +- Se debe especificar una cuenta de destino para los fondos restantes +- Se cobra una tarifa especial de 2 XAH (que se destruye) +- La reserva base se envía a la cuenta de destino + +Después de eliminarse, la dirección queda libre pero no se puede reutilizar con la misma seed (por seguridad). + +### Flags como bits + +Los flags de cuenta se almacenan como un campo numérico donde cada bit representa un flag. Puedes activar flags con el campo \`SetFlag\` y desactivarlos con \`ClearFlag\` en la transacción AccountSet. + +| Flag | ID | Propósito | +|------|----|-----------| +| asfRequireDest | 1 | Requerir Destination Tag | +| asfRequireAuth | 2 | Requerir autorización de trust lines | +| asfDisallowXRP | 3 | Señalar que no se desea recibir XAH | +| asfDisableMaster | 4 | Desactivar clave maestra | +| asfDefaultRipple | 8 | Permitir rippling de tokens emitidos |`, + en: `In Xahau, your account has multiple configuration options that you can enable or disable using the **AccountSet** transaction. These settings control your account's behavior regarding incoming payments, trust lines, and more. + +### The AccountSet transaction + +\`AccountSet\` is the transaction type that allows you to modify your account's properties. It does not send or receive funds; it simply changes the **flags** and other configuration fields of your account. + +### Important flags + +**asfRequireDest (RequireDestTag)** +- Requires that all incoming payments include a **Destination Tag** +- Useful for exchanges and services that use a tag to identify the user +- Without this flag, someone could send you XAH without a tag and it would be impossible to know who it came from +- Flag ID: \`1\` + +**asfDisallowXRP (DisallowXAH)** +- Signals that your account **does not wish to receive XAH directly** +- It is only a signal; technically, payments can still arrive +- Useful for accounts that only work with issued tokens (IOUs) +- Flag ID: \`3\` + +**asfDefaultRipple** +- Relevant for **token issuers** (we will cover this in depth in the tokens module) +- Allows tokens issued by your account to flow between third parties (rippling) +- Without this flag, tokens can only move directly to/from the issuer +- Flag ID: \`8\` + +**asfRequireAuth** +- Requires your account to **authorize** each trust line before someone can hold your tokens +- Useful for regulated tokens where you need to control who can hold them +- Flag ID: \`2\` + +### Other configurable fields + +**Domain**: You can associate a web domain with your account. It is stored as the hexadecimal value of the domain. This allows verification that the account belongs to the owner of that domain. + +**EmailHash**: MD5 hash of your email, used to display an avatar (like Gravatar). It does not expose your email directly. + +### Account Delete: deleting your account + +In Xahau it is possible to **delete an account** from the ledger to recover part of the reserve: + +Requirements: +- The account's sequence number must be at least 256 +- The account must not own any objects on the ledger (offers, trust lines, etc.) +- A destination account must be specified for the remaining funds +- A special fee of 2 XAH is charged (which is destroyed) +- The base reserve is sent to the destination account + +After deletion, the address becomes free but cannot be reused with the same seed (for security). + +### Flags as bits + +Account flags are stored as a numeric field where each bit represents a flag. You can enable flags with the \`SetFlag\` field and disable them with \`ClearFlag\` in the AccountSet transaction. + +| Flag | ID | Purpose | +|------|----|---------| +| asfRequireDest | 1 | Require Destination Tag | +| asfRequireAuth | 2 | Require trust line authorization | +| asfDisallowXRP | 3 | Signal that XAH is not desired | +| asfDisableMaster | 4 | Disable master key | +| asfDefaultRipple | 8 | Allow rippling of issued tokens |`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Activar el flag RequireDestTag en tu cuenta", + en: "Enable the RequireDestTag flag on your account", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function setRequireDestTag() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Use your testnet wallet (replace with your seed) + const wallet = Wallet.fromSeed("sEdVHBhkL2next8NH9cMPyPJoXXXXXX", {algorithm: 'secp256k1'}); + + // AccountSet with SetFlag to enable RequireDestTag + const tx = { + TransactionType: "AccountSet", + Account: wallet.address, + // asfRequireDest = 1 + SetFlag: 1, + }; + console.log("Account: ",wallet.address); + console.log("Sending AccountSet transaction..."); + console.log(" Enabling flag: RequireDestTag (asfRequireDest = 1)"); + + const result = await client.submitAndWait(tx, { wallet }); + + console.log("\\nResult:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("RequireDestTag flag enabled successfully!"); + console.log("Now all incoming payments must include a DestinationTag."); + + // Verify that the flag was enabled + const accountInfo = await client.request({ + command: "account_info", + account: wallet.address, + ledger_index: "validated", + }); + + const flags = accountInfo.result.account_data.Flags; + console.log("\\nAccount flags (number):", flags); + + // lsfRequireDestTag = 0x00020000 = 131072 + const requireDestTag = (flags & 0x00020000) !== 0; + console.log("RequireDestTag active:", requireDestTag); + } + + await client.disconnect(); +} + +setRequireDestTag();`, + }, + { + title: { + es: "Leer e interpretar los flags de una cuenta", + en: "Read and interpret account flags", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function readAccountFlags(address) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + try { + const response = await client.request({ + command: "account_info", + account: address, + ledger_index: "validated", + }); + + const account = response.result.account_data; + const flags = account.Flags; + + console.log("=== Account information ==="); + console.log("Address:", account.Account); + console.log("Balance:", Number(account.Balance) / 1_000_000, "XAH"); + console.log("Flags (numeric value):", flags); + console.log(""); + + // Interpret each individual flag + // Ledger flags (lsf) have different values than AccountSet flags (asf) + const flagDefinitions = [ + { name: "lsfRequireDestTag", mask: 0x00020000, desc: "Requires Destination Tag" }, + { name: "lsfRequireAuth", mask: 0x00040000, desc: "Requires trust line authorization" }, + { name: "lsfDisallowXRP", mask: 0x00080000, desc: "Does not wish to receive XAH" }, + { name: "lsfDisableMaster", mask: 0x00100000, desc: "Master key disabled" }, + { name: "lsfDefaultRipple", mask: 0x00800000, desc: "Default rippling enabled" }, + ]; + + console.log("=== Active flags ==="); + let anyActive = false; + for (const flag of flagDefinitions) { + const active = (flags & flag.mask) !== 0; + if (active) { + console.log(\` ✅ \${flag.name}: \${flag.desc}\`); + anyActive = true; + } + } + + if (!anyActive) { + console.log(" No special flags active (default configuration)"); + } + + console.log(""); + console.log("=== Other fields ==="); + console.log("Domain:", account.Domain + ? Buffer.from(account.Domain, "hex").toString("utf-8") + : "(not configured)"); + console.log("EmailHash:", account.EmailHash || "(not configured)"); + console.log("RegularKey:", account.RegularKey || "(not configured)"); + console.log("Sequence:", account.Sequence); + console.log("OwnerCount:", account.OwnerCount); + + } catch (error) { + if (error.data?.error === "actNotFound") { + console.log("The account does not exist on the ledger."); + } else { + console.error("Error:", error.message); + } + } + + await client.disconnect(); +} + +// Replace with a testnet address +readAccountFlags("rYourXahauAddressHere");`, + }, + ], + slides: [ + { + title: { es: "AccountSet: configura tu cuenta", en: "AccountSet: configure your account", jp: "" }, + content: { + es: "La transacción AccountSet modifica\nlos flags y propiedades de tu cuenta\n\nFlags principales:\n🏷️ RequireDestTag → Exigir tag en pagos\n🚫 DisallowXAH → Señal de no recibir XAH\n🔄 DefaultRipple → Para emisores de tokens\n🔐 RequireAuth → Autorizar trust lines", + en: "The AccountSet transaction modifies\nyour account's flags and properties\n\nMain flags:\n🏷️ RequireDestTag → Require tag on payments\n🚫 DisallowXAH → Signal not to receive XAH\n🔄 DefaultRipple → For token issuers\n🔐 RequireAuth → Authorize trust lines", + jp: "", + }, + visual: "⚙️", + }, + { + title: { es: "Flags como bits", en: "Flags as bits", jp: "" }, + content: { + es: "Los flags se almacenan como un número binario\nCada bit = un flag diferente\n\nActivar: campo SetFlag + ID del flag\nDesactivar: campo ClearFlag + ID del flag\n\n| Flag | ID |\n| RequireDest | 1 |\n| RequireAuth | 2 |\n| DisallowXRP | 3 |\n| DisableMaster | 4 |\n| DefaultRipple | 8 |", + en: "Flags are stored as a binary number\nEach bit = a different flag\n\nEnable: SetFlag field + flag ID\nDisable: ClearFlag field + flag ID\n\n| Flag | ID |\n| RequireDest | 1 |\n| RequireAuth | 2 |\n| DisallowXRP | 3 |\n| DisableMaster | 4 |\n| DefaultRipple | 8 |", + jp: "", + }, + visual: "🔢", + }, + ], + }, + { + id: "m3l5", + title: { + es: "Cómo importar tu cuenta en Xaman", + en: "How to import your account into Xaman", + jp: "", + }, + theory: { + es: `**Xaman** (anteriormente XUMM) es la wallet móvil más utilizada del ecosistema XRPL y Xahau. Hasta ahora hemos trabajado con wallets desde código JavaScript, pero para gestionar tu cuenta de forma visual, firmar transacciones desde el móvil e interactuar con aplicaciones descentralizadas, necesitas importar tu cuenta en Xaman. + +### ¿Qué es Xaman? + +Xaman es una aplicación móvil disponible para **iOS** y **Android** que funciona como: +- **Wallet**: Almacena tus claves de forma segura en tu dispositivo +- **Firmador de transacciones**: Puedes aprobar transacciones escaneando un QR o desde una xApp +- **Gestor de cuentas**: Puedes gestionar múltiples cuentas de Xahau y XRPL +- **Puerta de entrada a xApps**: Aplicaciones descentralizadas integradas en Xaman + +Descarga: [xaman.app](https://xaman.app) + +### Instalar Xaman + +1. Abre la **App Store** (iOS) o **Google Play** (Android) +2. Busca **"Xaman"** (antes se llamaba XUMM) +3. Descarga e instala la aplicación +4. Abre Xaman y sigue la configuración inicial: + - Configura un **código PIN** o **biometría** (huella/Face ID) + - Acepta los términos de uso + +### Importar tu cuenta de testnet + +Una vez instalado Xaman, puedes importar la cuenta que generaste por código usando tu **family seed** (la cadena que empieza por \`s\`): + +1. Abre Xaman +2. Toca el botón **"Añadir cuenta"** (o el icono \`+\` arriba) +3. Selecciona **"Importar una cuenta existente"** +4. Selecciona **"Family Seed (s...)"** como método de importación +5. Introduce tu seed (la cadena que empieza por \`s\` que obtuviste al generar la wallet) +6. Selecciona el nivel de acceso: + - **Acceso completo**: Puedes firmar transacciones (necesitas el seed) + - **Solo lectura**: Solo puedes ver el balance y transacciones (solo necesitas la dirección) +7. Confirma con tu PIN o biometría +8. Tu cuenta aparecerá en la lista de cuentas de Xaman + +### Añadir la red Xahau en Xaman + +Por defecto, Xaman se conecta a **XRPL Mainnet**. Para trabajar con **Xahau**, debes añadir la red: + +1. En Xaman, ve a **Ajustes** (icono de engranaje) +2. Busca la sección **"Advanced"** o **"Avanzado"** +3. Busca la sección **"Debug"** +4. Activa **Developer Mode** o **Modo Desarrollador** +5. Selecciona **Xahau Testnet** como red activa en el menú principal pulsando en la esquina superior derecha. +6. Ahora tu cuenta mostrará el balance de XAH en testnet + +### Verificar la importación + +Después de importar, verifica que todo es correcto: +- La **dirección** que muestra Xaman debe coincidir con la que generaste por código +- Puedes enviar una pequeña transacción de prueba para confirmar que la firma funciona + +### Firmar transacciones con Xaman + +Xaman puede firmar transacciones de dos formas: + +**Desde la propia app**: +- Puedes enviar pagos directamente desde Xaman +- Toca **"Enviar"**, introduce la dirección destino y la cantidad +- Confirma con tu PIN o biometría + +**Desde una xApp o sitio web (QR)**: +- Algunas aplicaciones muestran un código QR +- Escaneas el QR con Xaman +- Xaman te muestra los detalles de la transacción +- Apruebas o rechazas firmando con tu PIN + +### Seguridad en Xaman + +- Tu seed **nunca sale de tu dispositivo**. Xaman almacena las claves de forma encriptada en el almacenamiento seguro del sistema operativo (Keychain en iOS, Keystore en Android) +- Las transacciones se **firman localmente** en tu dispositivo +- Xaman **nunca envía** tu clave privada a ningún servidor +- Si pierdes tu dispositivo, puedes restaurar tu cuenta en otro dispositivo usando tu seed +- **Guarda siempre una copia de tu seed fuera del dispositivo** (papel, metal backup) +- Xaman no permite exportar tu seed desde la app por seguridad, así que asegúrate de tenerlo guardado antes de importar + + +### Importar con solo lectura + +Si solo quieres **monitorizar** una cuenta sin poder firmar transacciones: + +1. En Xaman, toca **"Añadir cuenta"** +2. Selecciona **"Importar una cuenta existente"** +3. Selecciona **"Dirección de la cuenta (r...)"** +4. Introduce la dirección \`r...\` (no el seed) +5. La cuenta se añade en modo solo lectura + +Esto es útil para: +- Monitorizar cuentas de otros (exchanges, contratos) +- Vigilar tu cuenta de mainnet sin exponer el seed en el móvil +- Comprobar balances rápidamente`, + en: `**Xaman** (formerly XUMM) is the most widely used mobile wallet in the XRPL and Xahau ecosystem. So far we have been working with wallets from JavaScript code, but to manage your account visually, sign transactions from your phone, and interact with decentralized applications, you need to import your account into Xaman. + +### What is Xaman? + +Xaman is a mobile application available for **iOS** and **Android** that works as: +- **Wallet**: Stores your keys securely on your device +- **Transaction signer**: You can approve transactions by scanning a QR or from an xApp +- **Account manager**: You can manage multiple Xahau and XRPL accounts +- **Gateway to xApps**: Decentralized applications integrated into Xaman + +Download: [xaman.app](https://xaman.app) + +### Install Xaman + +1. Open the **App Store** (iOS) or **Google Play** (Android) +2. Search for **"Xaman"** (it was previously called XUMM) +3. Download and install the application +4. Open Xaman and follow the initial setup: + - Set up a **PIN code** or **biometrics** (fingerprint/Face ID) + - Accept the terms of use + +### Import your testnet account + +Once Xaman is installed, you can import the account you generated via code using your **family seed** (the string starting with \`s\`): + +1. Open Xaman +2. Tap the **"Add account"** button (or the \`+\` icon at the top) +3. Select **"Import an existing account"** +4. Select **"Family Seed (s...)"** as the import method +5. Enter your seed (the string starting with \`s\` that you obtained when generating the wallet) +6. Select the access level: + - **Full access**: You can sign transactions (requires the seed) + - **Read-only**: You can only view balance and transactions (only requires the address) +7. Confirm with your PIN or biometrics +8. Your account will appear in the Xaman account list + +### Add the Xahau network in Xaman + +By default, Xaman connects to **XRPL Mainnet**. To work with **Xahau**, you need to add the network: + +1. In Xaman, go to **Settings** (gear icon) +2. Find the **"Advanced"** section +3. Find the **"Debug"** section +4. Enable **Developer Mode** +5. Select **Xahau Testnet** as the active network in the main menu by tapping in the upper right corner. +6. Now your account will show the XAH balance on testnet + +### Verify the import + +After importing, verify that everything is correct: +- The **address** shown in Xaman should match the one you generated via code +- You can send a small test transaction to confirm that signing works + +### Sign transactions with Xaman + +Xaman can sign transactions in two ways: + +**From the app itself**: +- You can send payments directly from Xaman +- Tap **"Send"**, enter the destination address and amount +- Confirm with your PIN or biometrics + +**From an xApp or website (QR)**: +- Some applications display a QR code +- You scan the QR with Xaman +- Xaman shows you the transaction details +- You approve or reject by signing with your PIN + +### Security in Xaman + +- Your seed **never leaves your device**. Xaman stores keys in encrypted form in the operating system's secure storage (Keychain on iOS, Keystore on Android) +- Transactions are **signed locally** on your device +- Xaman **never sends** your private key to any server +- If you lose your device, you can restore your account on another device using your seed +- **Always keep a copy of your seed outside the device** (paper, metal backup) +- Xaman does not allow exporting your seed from the app for security, so make sure you have it saved before importing + + +### Import as read-only + +If you only want to **monitor** an account without being able to sign transactions: + +1. In Xaman, tap **"Add account"** +2. Select **"Import an existing account"** +3. Select **"Account address (r...)"** +4. Enter the address \`r...\` (not the seed) +5. The account is added in read-only mode + +This is useful for: +- Monitoring other accounts (exchanges, contracts) +- Watching your mainnet account without exposing the seed on your phone +- Checking balances quickly`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Generar una wallet y preparar datos para importar en Xaman", + en: "Generate a wallet and prepare data for importing into Xaman", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function prepareForXaman() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Generate and fund a wallet + const wallet = Wallet.generate(); + console.log("Generating testnet wallet...\\n"); + await client.fundWallet(wallet); + + // Check balance + const response = await client.request({ + command: "account_info", + account: wallet.address, + ledger_index: "validated", + }); + + const balance = Number(response.result.account_data.Balance) / 1_000_000; + + console.log("=== Data for importing into Xaman ===\\n"); + console.log("Address:", wallet.address); + console.log("Seed:", wallet.seed); + console.log("Balance:", balance, "XAH"); + console.log("\\n=== Instructions ==="); + console.log("1. Open Xaman on your phone"); + console.log("2. Tap 'Add account' → 'Import existing account'"); + console.log("4. Select Full Access"); + console.log("5. Select 'Family Seed (s...)'"); + console.log("6. Enter the seed:", wallet.seed); + console.log("\\n⚠️ Remember: we are on TESTNET."); + console.log(" Make sure to select the Xahau Testnet network in Xaman."); + + await client.disconnect(); +} + +prepareForXaman();`, + }, + ], + slides: [ + { + title: { es: "¿Qué es Xaman?", en: "What is Xaman?", jp: "" }, + content: { + es: "Wallet móvil del ecosistema XRPL/Xahau\n\n• iOS y Android\n• Almacena claves de forma segura\n• Firma transacciones desde el móvil\n• Gestiona múltiples cuentas\n• Acceso a xApps\n\nDescarga: xaman.app", + en: "Mobile wallet for the XRPL/Xahau ecosystem\n\n• iOS and Android\n• Stores keys securely\n• Sign transactions from your phone\n• Manage multiple accounts\n• Access to xApps\n\nDownload: xaman.app", + jp: "", + }, + visual: "📱", + }, + { + title: { es: "Importar tu cuenta", en: "Import your account", jp: "" }, + content: { + es: "1️⃣ Abre Xaman → 'Añadir cuenta'\n2️⃣ 'Importar cuenta existente'\n3️⃣ Elige 'Acceso completo'\n4️⃣ Selecciona 'Family Seed (s...)\n5️⃣ Introduce tu seed\n6️⃣ Confirma con PIN/biometría\n\n⚠️ Selecciona red Xahau Testnet\nen Ajustes → Redes", + en: "1️⃣ Open Xaman → 'Add account'\n2️⃣ 'Import existing account'\n3️⃣ Choose 'Full access'\n4️⃣ Select 'Family Seed (s...)'\n5️⃣ Enter your seed\n6️⃣ Confirm with PIN/biometrics\n\n⚠️ Select Xahau Testnet network\nin Settings → Networks", + jp: "", + }, + visual: "🔑", + }, + { + title: { es: "Seguridad en Xaman", en: "Security in Xaman", jp: "" }, + content: { + es: "🔐 El seed NUNCA sale del dispositivo\n📲 Firma local (no envía claves a servidores)\n🔒 Almacenamiento encriptado (Keychain/Keystore)\n\nModos de importación:\n• Acceso completo → Firmar transacciones\n• Solo lectura → Solo ver balance\n\n💡 Guarda siempre una copia del seed\nfuera del dispositivo", + en: "🔐 The seed NEVER leaves the device\n📲 Local signing (does not send keys to servers)\n🔒 Encrypted storage (Keychain/Keystore)\n\nImport modes:\n• Full access → Sign transactions\n• Read-only → Only view balance\n\n💡 Always keep a copy of the seed\noutside the device", + jp: "", + }, + visual: "🛡️", + }, + ], + }, + ], +} diff --git a/src/data/modules/m04-consulta-datos.js b/src/data/modules/m04-consulta-datos.js new file mode 100644 index 0000000..3940435 --- /dev/null +++ b/src/data/modules/m04-consulta-datos.js @@ -0,0 +1,710 @@ +export default { + id: "m4", + icon: "🔍", + title: { + es: "Consulta de datos a un nodo de la red", + en: "Querying data from a network node", + jp: "", + }, + lessons: [ + { + id: "m4l1", + title: { + es: "Conexión a nodos Xahau", + en: "Connecting to Xahau nodes", + jp: "", + }, + theory: { + es: `Para leer datos de la blockchain Xahau, necesitas conectarte a un **nodo de la red** mediante **WebSocket**. Los nodos exponen una API JSON-RPC que permite consultar toda la información del ledger. + +### Tipos de nodos + +- **Nodos públicos**: Mantenidos por la comunidad, accesibles para cualquiera. Ideales para desarrollo +- **Nodos propios**: Puedes ejecutar tu propio nodo para mayor control y fiabilidad + +### Endpoints principales + +| Red | WebSocket URL | +|---|---| +| Mainnet | \`wss://xahau.network\` | +| Testnet | \`wss://xahau-test.net\` | + +### Tipos de consultas + +La API de Xahau ofrece comandos para consultar: +- **Información del servidor**: \`server_info\`, \`server_state\` +- **Cuentas**: \`account_info\`, \`account_lines\`, \`account_objects\`, \`account_tx\` +- **Ledger**: \`ledger\`, \`ledger_data\`, \`ledger_entry\` +- **Transacciones**: \`tx\`, \`transaction_entry\` +- **Suscripciones**: \`subscribe\` / \`unsubscribe\` para eventos en tiempo real + +### Conceptos importantes + +- **Ledger index**: Puedes consultar un ledger específico por su número, o usar \`"validated"\` para el último validado +- **Drops**: Las cantidades de XAH se expresan en drops (1 XAH = 1,000,000 drops) +- **Marcadores (Markers)**: Para paginar resultados grandes, la API usa marcadores`, + en: `To read data from the Xahau blockchain, you need to connect to a **network node** via **WebSocket**. Nodes expose a JSON-RPC API that allows you to query all ledger information. + +### Node types + +- **Public nodes**: Maintained by the community, accessible to anyone. Ideal for development +- **Private nodes**: You can run your own node for greater control and reliability + +### Main endpoints + +| Network | WebSocket URL | +|---|---| +| Mainnet | \`wss://xahau.network\` | +| Testnet | \`wss://xahau-test.net\` | + +### Query types + +The Xahau API provides commands to query: +- **Server information**: \`server_info\`, \`server_state\` +- **Accounts**: \`account_info\`, \`account_lines\`, \`account_objects\`, \`account_tx\` +- **Ledger**: \`ledger\`, \`ledger_data\`, \`ledger_entry\` +- **Transactions**: \`tx\`, \`transaction_entry\` +- **Subscriptions**: \`subscribe\` / \`unsubscribe\` for real-time events + +### Important concepts + +- **Ledger index**: You can query a specific ledger by its number, or use \`"validated"\` for the latest validated one +- **Drops**: XAH amounts are expressed in drops (1 XAH = 1,000,000 drops) +- **Markers**: To paginate large result sets, the API uses markers`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Conectar y consultar información del servidor", + en: "Connect and query server information", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function getServerInfo() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const response = await client.request({ + command: "server_info" + }); + + const info = response.result.info; + console.log("=== Server Information ==="); + console.log("Version:", info.build_version); + console.log("Network ID:", info.network_id); + console.log("State:", info.server_state); + console.log("Connected peers:", info.peers); + console.log("Validated ledger:", info.validated_ledger.seq); + console.log("Validation quorum:", info.validation_quorum); + + await client.disconnect(); +} + +getServerInfo();`, + }, + { + title: { + es: "Consultar información detallada de una cuenta", + en: "Query detailed account information", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function getAccountInfo(address) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const response = await client.request({ + command: "account_info", + account: address, + ledger_index: "validated", + }); + + const data = response.result.account_data; + console.log("=== Account Data ==="); + console.log("Address:", data.Account); + console.log("Balance:", Number(data.Balance) / 1_000_000, "XAH"); + console.log("Sequence:", data.Sequence); + console.log("Owner Count:", data.OwnerCount); + console.log("Flags:", data.Flags); + + // Check if Hooks are installed + if (data.HookNamespaces) { + console.log("Hooks installed: Yes"); + console.log("Namespaces:", data.HookNamespaces); + } else { + console.log("Hooks installed: No"); + } + + await client.disconnect(); +} + +getAccountInfo("rYourAddressHere");`, + }, + ], + slides: [ + { + title: { es: "Conexión a Xahau", en: "Connecting to Xahau", jp: "" }, + content: { + es: "Conexión vía WebSocket a nodos públicos\n\n🌐 Mainnet: wss://xahau.network\n🧪 Testnet: wss://xahau-test.net\n\nAPI JSON-RPC para todas las consultas", + en: "WebSocket connection to public nodes\n\n🌐 Mainnet: wss://xahau.network\n🧪 Testnet: wss://xahau-test.net\n\nJSON-RPC API for all queries", + jp: "", + }, + visual: "🔌", + }, + { + title: { es: "Comandos principales", en: "Main commands", jp: "" }, + content: { + es: "• server_info → Estado del nodo\n• account_info → Datos de cuenta\n• account_lines → TrustLines\n• account_objects → Objetos de la cuenta\n• account_tx → Historial de transacciones\n• ledger → Info del ledger", + en: "• server_info → Node status\n• account_info → Account data\n• account_lines → TrustLines\n• account_objects → Account objects\n• account_tx → Transaction history\n• ledger → Ledger info", + jp: "", + }, + visual: "📡", + }, + { + title: { es: "Buenas prácticas de conexión", en: "Connection best practices", jp: "" }, + content: { + es: "• Envuelve conexiones en try/catch\n• Implementa reconexión automática\n• Escucha el evento 'disconnected'\n• Testnet para desarrollo, Mainnet para producción\n• Configura timeouts razonables\n• Valida respuestas antes de procesar", + en: "• Wrap connections in try/catch\n• Implement automatic reconnection\n• Listen for the 'disconnected' event\n• Testnet for development, Mainnet for production\n• Configure reasonable timeouts\n• Validate responses before processing", + jp: "", + }, + visual: "🛡️", + }, + ], + }, + { + id: "m4l2", + title: { + es: "Consultas avanzadas y suscripciones", + en: "Advanced queries and subscriptions", + jp: "", + }, + theory: { + es: `Más allá de las consultas básicas, Xahau permite consultar objetos específicos del ledger, el historial de transacciones de una cuenta y suscribirse a eventos en tiempo real. + +### Historial de transacciones + +El comando \`account_tx\` devuelve las transacciones asociadas a una cuenta. Puedes paginar los resultados usando el campo \`marker\`. + +### Objetos de una cuenta + +El comando \`account_objects\` devuelve todos los objetos del ledger asociados a una cuenta: +- TrustLines (líneas de confianza) +- Offers (órdenes en el DEX) +- URITokens (NFTs) +- Hooks instalados +- Estados de Hooks + +### Suscripciones en tiempo real + +Con el comando \`subscribe\` puedes recibir notificaciones cuando ocurren eventos: +- **ledger**: Notificación cada vez que se cierra un nuevo ledger +- **transactions**: Todas las transacciones de la red +- **accounts**: Transacciones que afectan a cuentas específicas + +### Consulta de transacciones individuales + +Puedes consultar los detalles de una transacción específica usando su **hash** con el comando \`tx\`.`, + en: `Beyond basic queries, Xahau allows you to query specific ledger objects, an account's transaction history, and subscribe to real-time events. + +### Transaction history + +The \`account_tx\` command returns the transactions associated with an account. You can paginate results using the \`marker\` field. + +### Account objects + +The \`account_objects\` command returns all ledger objects associated with an account: +- TrustLines +- Offers (DEX orders) +- URITokens (NFTs) +- Installed Hooks +- Hook states + +### Real-time subscriptions + +With the \`subscribe\` command you can receive notifications when events occur: +- **ledger**: Notification every time a new ledger closes +- **transactions**: All network transactions +- **accounts**: Transactions affecting specific accounts + +### Querying individual transactions + +You can query the details of a specific transaction using its **hash** with the \`tx\` command.`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Consultar historial de transacciones de una cuenta", + en: "Query an account's transaction history", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function getAccountTransactions(address) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const response = await client.request({ + command: "account_tx", + account: address, + ledger_index_min: -1, + ledger_index_max: -1, + limit: 10, + }); + + console.log("=== Latest transactions ==="); + for (const item of response.result.transactions) { + const tx = item.tx; + console.log(\`\\nType: \${tx.TransactionType}\`); + console.log(\` Hash: \${item.tx.hash}\`); + console.log(\` Date: \${new Date((tx.date + 946684800) * 1000).toISOString()}\`); + console.log(\` Result: \${item.meta.TransactionResult}\`); + + if (tx.TransactionType === "Payment") { + console.log(\` From: \${tx.Account}\`); + console.log(\` To: \${tx.Destination}\`); + console.log(\` Amount: \${Number(tx.Amount) / 1_000_000} XAH\`); + } + } + + await client.disconnect(); +} +//Example address: rDADDYfnLvVY9FBnS8zFXhwYFHPuU5q2Sk +getAccountTransactions("rYourAddressHere");`, + }, + { + title: { + es: "Consultar objetos de una cuenta y suscribirse a eventos", + en: "Query account objects and subscribe to events", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function getAccountObjects(address) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Query all account objects + const response = await client.request({ + command: "account_objects", + account: address, + ledger_index: "validated", + }); + + console.log("=== Account objects ==="); + for (const obj of response.result.account_objects) { + console.log(\`\\nType: \${obj.LedgerEntryType}\`); + + if (obj.LedgerEntryType === "RippleState") { + console.log(\` Token: \${obj.Balance.currency}\`); + console.log(\` Balance: \${obj.Balance.value}\`); + } else if (obj.LedgerEntryType === "URIToken") { + console.log(\` URI: \${obj.URI}\`); + } + } + + // Subscribe to transactions for this account + console.log("\\nSubscribed to account transactions..."); + await client.request({ + command: "subscribe", + accounts: [address] + }); + + client.on("transaction", (tx) => { + console.log("\\nNew transaction detected!"); + console.log("Type:", tx.transaction.TransactionType); + console.log("Result:", tx.meta.TransactionResult); + }); + + // Keep connection open for 60 seconds + setTimeout(() => client.disconnect(), 60000); +} +//Example address: rDADDYfnLvVY9FBnS8zFXhwYFHPuU5q2Sk +getAccountObjects("rYourAddressHere");`, + }, + ], + slides: [ + { + title: { es: "Historial de transacciones", en: "Transaction history", jp: "" }, + content: { + es: "account_tx → Historial de una cuenta\n\n• Paginar con marker\n• Filtrar por tipo de transacción\n• Ver resultados (éxito/fallo)\n• Consultar metadatos detallados", + en: "account_tx → Account history\n\n• Paginate with marker\n• Filter by transaction type\n• View results (success/failure)\n• Query detailed metadata", + jp: "", + }, + visual: "📜", + }, + { + title: { es: "Tiempo real", en: "Real time", jp: "" }, + content: { + es: "subscribe → Eventos en tiempo real\n\n• ledger → Cierre de ledgers\n• transactions → Todas las txs\n• accounts → Txs de cuentas específicas\n\nIdeal para monitorizar actividad", + en: "subscribe → Real-time events\n\n• ledger → Ledger closings\n• transactions → All txs\n• accounts → Txs for specific accounts\n\nIdeal for monitoring activity", + jp: "", + }, + visual: "⚡", + }, + { + title: { es: "Suscripciones en detalle", en: "Subscriptions in detail", jp: "" }, + content: { + es: "Comando subscribe para eventos en tiempo real:\n\n• Evento ledger → Nuevo ledger cerrado\n• Evento transaction → Tx confirmada\n• Escucha con client.on('transaction')\n• unsubscribe para dejar de escuchar\n• Mantén la conexión WebSocket abierta", + en: "subscribe command for real-time events:\n\n• ledger event → New ledger closed\n• transaction event → Tx confirmed\n• Listen with client.on('transaction')\n• unsubscribe to stop listening\n• Keep the WebSocket connection open", + jp: "", + }, + visual: "📡", + }, + ], + }, + { + id: "m4l3", + title: { + es: "Paginación y manejo de errores", + en: "Pagination and error handling", + jp: "", + }, + theory: { + es: `Cuando trabajas con la API de Xahau, es fundamental dominar dos aspectos: la **paginación** de resultados grandes y el **manejo de errores** para construir aplicaciones robustas. + +### El sistema de marcadores (marker) + +Muchos comandos de la API devuelven resultados paginados. Cuando hay más datos de los que caben en una sola respuesta, la API incluye un campo \`marker\` en el resultado. Para obtener la siguiente página, debes enviar el mismo comando incluyendo ese \`marker\`. + +- El campo \`limit\` controla cuántos resultados por página (máximo varía según el comando, generalmente 200-400) +- Si la respuesta incluye \`marker\`, hay más páginas disponibles +- Si no hay \`marker\` en la respuesta, has llegado al final +- El valor del \`marker\` es opaco: no lo modifiques, simplemente pásalo tal cual + +### Errores comunes de la API + +| Error | Significado | +|---|---| +| \`actNotFound\` | La cuenta consultada no existe en el ledger | +| \`lgrNotFound\` | El ledger solicitado no fue encontrado | +| \`invalidParams\` | Parámetros incorrectos en la petición | +| \`noCurrent\` | El servidor no tiene un ledger actual disponible | +| \`noNetwork\` | El servidor no está conectado a la red | +| \`tooBusy\` | El servidor está sobrecargado | + +### Buenas prácticas + +- **Siempre envuelve las peticiones en try/catch**: Los errores de red, timeouts y errores de API deben manejarse siempre +- **Implementa reintentos**: Para errores transitorios como \`tooBusy\` o timeouts, reintenta con backoff exponencial +- **Valida las respuestas**: Verifica que \`result.status === "success"\` antes de procesar datos +- **Maneja desconexiones**: Escucha el evento \`disconnected\` del cliente y reconecta automáticamente +- **Rate limiting**: Los nodos públicos pueden limitar las peticiones. Añade pausas entre peticiones masivas +- **Timeouts**: Configura un timeout razonable para evitar que tu aplicación se quede colgada`, + en: `When working with the Xahau API, it is essential to master two aspects: **pagination** of large result sets and **error handling** to build robust applications. + +### The marker system + +Many API commands return paginated results. When there is more data than fits in a single response, the API includes a \`marker\` field in the result. To get the next page, you must send the same command including that \`marker\`. + +- The \`limit\` field controls how many results per page (maximum varies by command, generally 200-400) +- If the response includes a \`marker\`, more pages are available +- If there is no \`marker\` in the response, you have reached the end +- The \`marker\` value is opaque: do not modify it, simply pass it as-is + +### Common API errors + +| Error | Meaning | +|---|---| +| \`actNotFound\` | The queried account does not exist in the ledger | +| \`lgrNotFound\` | The requested ledger was not found | +| \`invalidParams\` | Incorrect parameters in the request | +| \`noCurrent\` | The server does not have a current ledger available | +| \`noNetwork\` | The server is not connected to the network | +| \`tooBusy\` | The server is overloaded | + +### Best practices + +- **Always wrap requests in try/catch**: Network errors, timeouts, and API errors must always be handled +- **Implement retries**: For transient errors like \`tooBusy\` or timeouts, retry with exponential backoff +- **Validate responses**: Verify that \`result.status === "success"\` before processing data +- **Handle disconnections**: Listen for the client's \`disconnected\` event and reconnect automatically +- **Rate limiting**: Public nodes may throttle requests. Add pauses between bulk requests +- **Timeouts**: Configure a reasonable timeout to prevent your application from hanging`, + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Paginar todos los objetos de una cuenta usando marker", + en: "Paginate all account objects using marker", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function getAllAccountObjects(address) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + let allObjects = []; + let marker = undefined; + let page = 1; + + console.log("=== Getting all objects for", address, "===\\n"); + + do { + const request = { + command: "account_objects", + account: address, + ledger_index: "validated", + limit: 100, + }; + + // Include marker only if it exists (not on the first request) + if (marker) { + request.marker = marker; + } + + const response = await client.request(request); + const objects = response.result.account_objects; + allObjects = allObjects.concat(objects); + + console.log(\`Page \${page}: \${objects.length} objects received\`); + + // Update marker for the next page + marker = response.result.marker; + page++; + + // Small pause to avoid overloading the node + if (marker) { + await new Promise((resolve) => setTimeout(resolve, 200)); + } + } while (marker); + + console.log(\`\\nTotal objects retrieved: \${allObjects.length}\`); + + // Group by type + const byType = {}; + for (const obj of allObjects) { + const type = obj.LedgerEntryType; + byType[type] = (byType[type] || 0) + 1; + } + + console.log("\\nSummary by type:"); + for (const [type, count] of Object.entries(byType)) { + console.log(\` \${type}: \${count}\`); + } + + await client.disconnect(); +} +//Example account: rHh1YJN4kwRdw4Y29Xu1EY9qW8u36vAYLc +getAllAccountObjects("rYourAddressHere");`, + }, + ], + slides: [ + { + title: { es: "Paginación con marker", en: "Pagination with marker", jp: "" }, + content: { + es: "Cuando hay muchos resultados, la API pagina:\n\n1. Envía tu consulta con limit\n2. Si la respuesta tiene marker, hay más datos\n3. Reenvía la consulta incluyendo el marker\n4. Repite hasta que no haya marker\n\nNunca modifiques el valor del marker", + en: "When there are many results, the API paginates:\n\n1. Send your query with limit\n2. If the response has a marker, there is more data\n3. Resend the query including the marker\n4. Repeat until there is no marker\n\nNever modify the marker value", + jp: "", + }, + visual: "📄", + }, + { + title: { es: "Errores comunes", en: "Common errors", jp: "" }, + content: { + es: "• actNotFound → Cuenta no existe\n• lgrNotFound → Ledger no encontrado\n• invalidParams → Parámetros incorrectos\n• noCurrent → Sin ledger actual\n• noNetwork → Sin conexión a la red\n• tooBusy → Servidor sobrecargado", + en: "• actNotFound → Account does not exist\n• lgrNotFound → Ledger not found\n• invalidParams → Incorrect parameters\n• noCurrent → No current ledger\n• noNetwork → No network connection\n• tooBusy → Server overloaded", + jp: "", + }, + visual: "⚠️", + }, + { + title: { es: "Buenas prácticas", en: "Best practices", jp: "" }, + content: { + es: "• Siempre usar try/catch en las peticiones\n• Reintentar con backoff exponencial\n• Validar result.status === 'success'\n• Escuchar evento 'disconnected'\n• Pausar entre peticiones masivas\n• Configurar timeouts razonables", + en: "• Always use try/catch for requests\n• Retry with exponential backoff\n• Validate result.status === 'success'\n• Listen for the 'disconnected' event\n• Pause between bulk requests\n• Configure reasonable timeouts", + jp: "", + }, + visual: "🛡️", + }, + ], + }, + { + id: "m4l4", + title: { + es: "Trabajando con objetos del ledger", + en: "Working with ledger objects", + jp: "", + }, + theory: { + es: `El ledger de Xahau almacena toda la información en forma de **objetos** (ledger entries). Cada objeto tiene un tipo, un índice único (hash) y campos específicos. En esta lección aprenderemos a consultar y trabajar con estos objetos directamente. + +### El comando ledger_entry + +Con \`ledger_entry\` puedes consultar un objeto específico del ledger usando su **índice** (hash de 64 caracteres hex). Esto es útil cuando ya conoces el identificador exacto del objeto que necesitas. + +### Tipos de objetos consultables + +| Tipo | Descripción | +|---|---| +| \`AccountRoot\` | Datos principales de una cuenta | +| \`RippleState\` | Línea de confianza entre dos cuentas | +| \`Offer\` | Orden activa en el DEX | +| \`URIToken\` | Token no fungible (NFT de Xahau) | +| \`Hook\` | Definición de un Hook instalado | +| \`HookState\` | Estado almacenado por un Hook | + +### El comando account_objects con filtro de tipo + +El comando \`account_objects\` acepta el parámetro \`type\` para filtrar solo los objetos de un tipo específico. Los valores válidos incluyen: +- \`"state"\` → RippleState (trust lines) +- \`"offer"\` → Offers (órdenes del DEX) +- \`"uri_token"\` → URITokens +- \`"hook"\` → Hooks instalados + +### Entendiendo los índices del ledger + +Cada objeto en el ledger tiene un **índice único** calculado como un hash SHA-512Half de sus datos identificativos. Por ejemplo: +- El índice de un AccountRoot se calcula a partir de la dirección de la cuenta +- El índice de un RippleState se calcula a partir de las dos cuentas y la moneda + +Estos índices son determinísticos: siempre puedes recalcularlos si conoces los datos de entrada.`, + en: `The Xahau ledger stores all information as **objects** (ledger entries). Each object has a type, a unique index (hash), and specific fields. In this lesson we will learn how to query and work with these objects directly. + +### The ledger_entry command + +With \`ledger_entry\` you can query a specific ledger object using its **index** (64-character hex hash). This is useful when you already know the exact identifier of the object you need. + +### Queryable object types + +| Type | Description | +|---|---| +| \`AccountRoot\` | Main account data | +| \`RippleState\` | Trust line between two accounts | +| \`Offer\` | Active order on the DEX | +| \`URIToken\` | Non-fungible token (Xahau NFT) | +| \`Hook\` | Definition of an installed Hook | +| \`HookState\` | State stored by a Hook | + +### The account_objects command with type filter + +The \`account_objects\` command accepts the \`type\` parameter to filter only objects of a specific type. Valid values include: +- \`"state"\` → RippleState (trust lines) +- \`"offer"\` → Offers (DEX orders) +- \`"uri_token"\` → URITokens +- \`"hook"\` → Installed Hooks + +### Understanding ledger indexes + +Each object in the ledger has a **unique index** calculated as a SHA-512Half hash of its identifying data. For example: +- The index of an AccountRoot is calculated from the account address +- The index of a RippleState is calculated from the two accounts and the currency + +These indexes are deterministic: you can always recalculate them if you know the input data.`, + jp: "", + }, + codeBlocks: [ + + { + title: { + es: "Consultar account_objects filtrados por tipo", + en: "Query account_objects filtered by type", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function getObjectsByType(address, type) { + const client = new Client("wss://xahau.network"); + await client.connect(); + + let allObjects = []; + let marker = undefined; + + do { + const request = { + command: "account_objects", + account: address, + type: type, + ledger_index: "validated", + limit: 100, + }; + if (marker) request.marker = marker; + + const response = await client.request(request); + allObjects = allObjects.concat(response.result.account_objects); + marker = response.result.marker; + } while (marker); + + console.log(\`=== \${type.toUpperCase()} for \${address} ===\`); + console.log(\`Total found: \${allObjects.length}\\n\`); + + for (const obj of allObjects) { + switch (type) { + case "state": // RippleState (trust lines) + const currency = obj.Balance.currency; + const balance = obj.Balance.value; + const peer = obj.HighLimit.issuer === address + ? obj.LowLimit.issuer + : obj.HighLimit.issuer; + console.log(\` \${currency}: balance \${balance} (peer: \${peer})\`); + break; + + case "offer": + const pays = typeof obj.TakerPays === "string" + ? \`\${Number(obj.TakerPays) / 1_000_000} XAH\` + : \`\${obj.TakerPays.value} \${obj.TakerPays.currency}\`; + const gets = typeof obj.TakerGets === "string" + ? \`\${Number(obj.TakerGets) / 1_000_000} XAH\` + : \`\${obj.TakerGets.value} \${obj.TakerGets.currency}\`; + console.log(\` Offer: pays \${pays} → receives \${gets}\`); + break; + + case "uri_token": + const uri = Buffer.from(obj.URI || "", "hex").toString("utf8"); + console.log(\` URIToken: \${uri}\`); + console.log(\` Index: \${obj.index}\`); + break; + + default: + console.log(\` \${obj.LedgerEntryType}: \${obj.index}\`); + } + } + + await client.disconnect(); +} + +// Usage examples: +// View trust lines +getObjectsByType("rDk1xiArDMjDqnrR2yWypwQAKg4mKnQYvs", "state"); + +// View DEX orders +// getObjectsByType("rfmPQz4eSmisCVnWJkKj82hHKQdrUPv3Px", "offer"); + +// View URITokens +// getObjectsByType("rfPMnDQEzb5StPXj3Dkd34oKY4BVAJCwsn", "uri_token");`, + }, + ], + slides: [ + { + title: { es: "Objetos del ledger", en: "Ledger objects", jp: "" }, + content: { + es: "Todo en Xahau se almacena como objetos:\n\n• AccountRoot → Datos de cuenta\n• RippleState → Trust lines\n• Offer → Órdenes DEX\n• URIToken → NFTs\n• Hook → Hooks instalados\n\nCada objeto tiene un índice único (hash)", + en: "Everything in Xahau is stored as objects:\n\n• AccountRoot → Account data\n• RippleState → Trust lines\n• Offer → DEX orders\n• URIToken → NFTs\n• Hook → Installed Hooks\n\nEach object has a unique index (hash)", + jp: "", + }, + visual: "🗂️", + }, + { + title: { es: "Consultas por tipo", en: "Queries by type", jp: "" }, + content: { + es: "account_objects + type = filtro eficiente\n\n• type: 'state' → Trust lines\n• type: 'offer' → Órdenes DEX\n• type: 'uri_token' → NFTs\n• type: 'hook' → Hooks\n\nCombina con marker para paginar", + en: "account_objects + type = efficient filtering\n\n• type: 'state' → Trust lines\n• type: 'offer' → DEX orders\n• type: 'uri_token' → NFTs\n• type: 'hook' → Hooks\n\nCombine with marker to paginate", + jp: "", + }, + visual: "🔎", + }, + ], + }, + ], +} diff --git a/src/data/modules/m05-pagos.js b/src/data/modules/m05-pagos.js new file mode 100644 index 0000000..46bdb14 --- /dev/null +++ b/src/data/modules/m05-pagos.js @@ -0,0 +1,783 @@ +export default { + id: "m5", + icon: "💸", + title: { + es: "Creación y uso de pagos", + en: "", + jp: "", + }, + lessons: [ + { + id: "m5l1", + title: { + es: "Anatomía de una transacción de pago", + en: "", + jp: "", + }, + theory: { + es: `El **Payment** es la transacción más fundamental de Xahau. Permite enviar XAH (o tokens) de una cuenta a otra. + +### Campos de una transacción Payment + +| Campo | Descripción | +|---|---| +| \`TransactionType\` | Siempre \`"Payment"\` | +| \`Account\` | Dirección del emisor (quien paga) | +| \`Destination\` | Dirección del receptor | +| \`Amount\` | Cantidad a enviar (en drops para XAH nativo) | +| \`Fee\` | Coste de la transacción (en drops) | +| \`Sequence\` | Número de secuencia de la cuenta emisora | +| \`NetworkID\` | Identificador de la red (necesario en Xahau) | + +### Drops vs XAH + +Las cantidades de XAH nativo se expresan en **drops**: +- 1 XAH = **1,000,000 drops** +- El campo \`Amount\` para XAH nativo es un **string** con el número de drops +- Ejemplo: \`"10000000"\` = 10 XAH + +### Fees (costes de transacción) + +Los fees en Xahau son extremadamente bajos y predecibles: +- Un pago típico cuesta **12 drops** (0.000012 XAH) +- Los fees se **queman** (destruyen), no van a ningún validador +- La librería \`xahau\` puede calcular el fee automáticamente con \`autofill()\` + +### Ciclo de vida de una transacción + +1. **Construir**: Crear el objeto de transacción con los campos necesarios +2. **Autofill**: Rellenar automáticamente Fee, Sequence y NetworkID +3. **Firmar**: Firmar con la clave privada del emisor +4. **Enviar**: Enviar la transacción firmada al nodo +5. **Validar**: Esperar a que se incluya en un ledger validado`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Enviar un pago de XAH entre dos cuentas", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet, xahToDrops } = require("xahau"); + +async function sendPayment() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Wallet del emisor (usa tu seed de testnet) + const sender = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + + // Construir la transacción de pago + const payment = { + TransactionType: "Payment", + Account: sender.address, + Destination: "rDireccionDelDestinatario", + Amount: xahToDrops(10), // 10 XAH + }; + + // Autofill agrega Fee, Sequence, NetworkID automáticamente + const prepared = await client.autofill(payment); + console.log("Transacción preparada:", prepared); + + // Firmar la transacción + const signed = sender.sign(prepared); + console.log("Hash de la tx:", signed.hash); + + // Enviar y esperar validación + const result = await client.submitAndWait(signed.tx_blob); + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("¡Pago enviado con éxito!"); + } else { + console.log("Error en el pago"); + } + + await client.disconnect(); +} + +sendPayment();`, + }, + ], + slides: [ + { + title: { es: "Transacción Payment", en: "", jp: "" }, + content: { + es: "La transacción más básica de Xahau\n\n• Account → Quien envía\n• Destination → Quien recibe\n• Amount → Cantidad (en drops)\n• 1 XAH = 1,000,000 drops", + en: "", + jp: "", + }, + visual: "💸", + }, + { + title: { es: "Ciclo de vida", en: "", jp: "" }, + content: { + es: "1️⃣ Construir → Campos de la tx\n2️⃣ Autofill → Fee, Sequence, NetworkID\n3️⃣ Firmar → Con tu clave privada\n4️⃣ Enviar → submitAndWait()\n5️⃣ Validar → tesSUCCESS = éxito", + en: "", + jp: "", + }, + visual: "🔄", + }, + { + title: { es: "De submit a resultado final", en: "", jp: "" }, + content: { + es: "• Submit → Tx enviada al nodo\n• Nodo propaga a la red de validadores\n• Consenso → Incluida en un ledger\n• Resultado final en meta.TransactionResult\n• Fee se quema (no va a validadores)\n• submitAndWait espera la validación", + en: "", + jp: "", + }, + visual: "✅", + }, + ], + }, + { + id: "m5l2", + title: { + es: "Pagos con Destination Tag y memos", + en: "", + jp: "", + }, + theory: { + es: `Además del pago básico, Xahau soporta campos adicionales que permiten añadir contexto y funcionalidad a los pagos. + +### Destination Tag + +El **Destination Tag** es un número entero que permite al receptor identificar pagos individuales. Es especialmente útil para: +- **Exchanges**: Identificar a qué usuario pertenece un depósito +- **Servicios**: Asociar un pago con un pedido o factura +- Si una cuenta tiene activado el flag \`RequireDestTag\`, **no puedes enviarle un pago sin tag** + +### Memos + +Los **Memos** permiten adjuntar datos arbitrarios a una transacción: +- \`MemoType\`: Tipo del memo (ej: "text/plain", "application/json") +- \`MemoData\`: El contenido del memo +- Los memos se codifican en **hexadecimal** +- Son públicos y visibles para todos en el ledger + +### Resultados de transacción + +Cada transacción devuelve un código de resultado: +- \`tesSUCCESS\`: La transacción fue exitosa +- \`tecUNFUNDED_PAYMENT\`: No hay fondos suficientes +- \`tecNO_DST\`: La cuenta de destino no existe +- \`tecDST_TAG_NEEDED\`: Se requiere Destination Tag +- \`tecNO_DST_INSUF_XAH\`: El destino no tiene suficiente XAH para la reserva`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Pago con Destination Tag y Memos", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet, xahToDrops } = require("xahau"); + +// Función auxiliar para convertir texto a hexadecimal +function toHex(str) { + return Buffer.from(str, "utf8").toString("hex").toUpperCase(); +} + +async function sendPaymentWithMemo() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const sender = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + + const payment = { + TransactionType: "Payment", + Account: sender.address, + Destination: "rDireccionDelDestinatario", + Amount: xahToDrops(5), // 5 XAH + DestinationTag: 12345, // Tag para identificar el pago + Memos: [ + { + Memo: { + MemoType: toHex("text/plain"), + MemoData: toHex("Pago del curso de Xahau - Módulo 5"), + }, + }, + ], + }; + + const prepared = await client.autofill(payment); + const signed = sender.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + const txResult = result.result.meta.TransactionResult; + console.log("Resultado:", txResult); + + if (txResult === "tesSUCCESS") { + console.log("¡Pago con memo enviado!"); + console.log("Hash:", signed.hash); + console.log("Destination Tag:", 12345); + } + + await client.disconnect(); +} + +sendPaymentWithMemo();`, + }, + { + title: { + es: "Verificar un pago recibido", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function verifyPayment(txHash) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const response = await client.request({ + command: "tx", + transaction: txHash, + }); + + const tx = response.result; + console.log("=== Detalles del pago ==="); + console.log("Tipo:", tx.TransactionType); + console.log("De:", tx.Account); + console.log("A:", tx.Destination); + console.log("Cantidad:", Number(tx.Amount) / 1_000_000, "XAH"); + console.log("Fee:", Number(tx.Fee) / 1_000_000, "XAH"); + console.log("Resultado:", tx.meta.TransactionResult); + console.log("Ledger:", tx.ledger_index); + + if (tx.DestinationTag !== undefined) { + console.log("Destination Tag:", tx.DestinationTag); + } + + if (tx.Memos) { + for (const memo of tx.Memos) { + const type = Buffer.from(memo.Memo.MemoType, "hex").toString("utf8"); + const data = Buffer.from(memo.Memo.MemoData, "hex").toString("utf8"); + console.log(\`Memo [\${type}]: \${data}\`); + } + } + + await client.disconnect(); +} + +verifyPayment("TU_HASH_DE_TRANSACCION_AQUI");`, + }, + ], + slides: [ + { + title: { es: "Destination Tag", en: "", jp: "" }, + content: { + es: "Número para identificar pagos individuales\n\n• Usado por exchanges y servicios\n• Asocia pagos con usuarios/pedidos\n• Algunas cuentas lo requieren\n• Es un número entero (uint32)", + en: "", + jp: "", + }, + visual: "🏷️", + }, + { + title: { es: "Memos", en: "", jp: "" }, + content: { + es: "Datos adjuntos a una transacción\n\n• MemoType → Tipo (text/plain, etc.)\n• MemoData → Contenido\n• Codificados en hexadecimal\n• Públicos en el ledger", + en: "", + jp: "", + }, + visual: "📝", + }, + { + title: { es: "Seguridad del DestinationTag", en: "", jp: "" }, + content: { + es: "• Flag RequireDestTag en la cuenta destino\n• Sin tag → error tecDST_TAG_NEEDED\n• Exchanges exigen tag para depósitos\n• Sin tag correcto = fondos perdidos\n• Siempre valida el tag antes de enviar\n• Maneja errores: tecNO_DST, tecUNFUNDED", + en: "", + jp: "", + }, + visual: "🔒", + }, + ], + }, + { + id: "m5l3", + title: { + es: "Pagos cross-currency y pathfinding", + en: "", + jp: "", + }, + theory: { + es: `Xahau no solo permite enviar XAH nativo o tokens del mismo tipo: también soporta **pagos cross-currency**, donde el emisor envía una moneda y el receptor recibe otra diferente. Esto es posible gracias al **DEX integrado** y al sistema de **pathfinding**. + +### Pagos cross-currency + +Un pago cross-currency permite, por ejemplo, que el emisor pague en XAH y el receptor reciba USD. Xahau busca automáticamente el mejor camino a través del DEX para convertir las monedas. + +### El sistema de pathfinding + +El pathfinding es el mecanismo que encuentra rutas de conversión entre monedas: +- Xahau busca **caminos** a través de trust lines y órdenes del DEX +- Puede encadenar múltiples conversiones intermedias +- Siempre intenta encontrar la **mejor tasa** disponible + +### Campos clave en pagos cross-currency + +| Campo | Descripción | +|---|---| +| \`Amount\` | Lo que el receptor debe recibir (moneda de destino) | +| \`SendMax\` | Máximo que el emisor está dispuesto a gastar (moneda de origen) | +| \`DeliverMin\` | Mínimo que el receptor debe recibir (con pagos parciales) | +| \`Paths\` | Rutas de conversión encontradas por pathfinding | + +### El comando ripple_path_find + +Antes de enviar un pago cross-currency, usa \`ripple_path_find\` para: +- Ver si existe un camino entre las dos monedas +- Obtener el \`Paths\` necesario para la transacción +- Conocer el coste estimado (\`source_amount\`) + +### Pagos parciales (tfPartialPayment) + +El flag \`tfPartialPayment\` (valor: \`0x00020000\`) permite que un pago entregue **menos** de lo especificado en \`Amount\`: +- Útil cuando la liquidez puede variar entre la consulta y la ejecución +- Usa \`DeliverMin\` para establecer un mínimo aceptable +- **IMPORTANTE**: Al recibir pagos, siempre verifica \`delivered_amount\` en los metadatos, **no** el campo \`Amount\`. Un atacante podría enviar un pago parcial que muestre un \`Amount\` alto pero entregue mucho menos`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Buscar rutas de pago entre monedas con ripple_path_find", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function findPaymentPaths() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const senderAddress = "rDireccionDelEmisor"; + const receiverAddress = "rDireccionDelReceptor"; + const issuerAddress = "rDireccionDelEmisorDeUSD"; + + // Buscar rutas para entregar 100 USD al receptor + const pathResponse = await client.request({ + command: "ripple_path_find", + source_account: senderAddress, + destination_account: receiverAddress, + destination_amount: { + currency: "USD", + issuer: issuerAddress, + value: "100", + }, + }); + + const alternatives = pathResponse.result.alternatives; + console.log("=== Rutas de pago encontradas ==="); + console.log(\`Se encontraron \${alternatives.length} alternativas\\n\`); + + for (let i = 0; i < alternatives.length; i++) { + const alt = alternatives[i]; + console.log(\`--- Alternativa \${i + 1} ---\`); + + // El coste para el emisor + if (typeof alt.source_amount === "string") { + // XAH nativo (en drops) + const xah = Number(alt.source_amount) / 1_000_000; + console.log(\`Coste: \${xah} XAH\`); + } else { + // Token + console.log( + \`Coste: \${alt.source_amount.value} \${alt.source_amount.currency}\` + ); + } + + console.log(\`Paths: \${alt.paths_computed.length} saltos\`); + + // Mostrar los saltos intermedios + for (const path of alt.paths_computed) { + const steps = path.map((step) => { + if (step.currency) return step.currency; + if (step.account) return step.account.slice(0, 8) + "..."; + return "?"; + }); + console.log(\` Ruta: \${steps.join(" → ")}\`); + } + } + + await client.disconnect(); +} + +findPaymentPaths();`, + }, + { + title: { + es: "Enviar un pago cross-currency (XAH a USD token)", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function sendCrossCurrencyPayment() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const sender = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + const receiverAddress = "rDireccionDelReceptor"; + const usdIssuer = "rDireccionDelEmisorDeUSD"; + + // Primero, buscar rutas de pago + const pathResponse = await client.request({ + command: "ripple_path_find", + source_account: sender.address, + destination_account: receiverAddress, + destination_amount: { + currency: "USD", + issuer: usdIssuer, + value: "50", + }, + }); + + if (pathResponse.result.alternatives.length === 0) { + console.log("No se encontraron rutas de pago disponibles."); + await client.disconnect(); + return; + } + + const bestAlt = pathResponse.result.alternatives[0]; + console.log("Mejor ruta encontrada."); + + if (typeof bestAlt.source_amount === "string") { + console.log( + \`Coste estimado: \${Number(bestAlt.source_amount) / 1_000_000} XAH\` + ); + } else { + console.log( + \`Coste estimado: \${bestAlt.source_amount.value} \${bestAlt.source_amount.currency}\` + ); + } + + // Construir el pago cross-currency + const payment = { + TransactionType: "Payment", + Account: sender.address, + Destination: receiverAddress, + // Lo que el receptor debe recibir + Amount: { + currency: "USD", + issuer: usdIssuer, + value: "50", + }, + // Máximo que estamos dispuestos a gastar (añadir un 5% de margen) + SendMax: + typeof bestAlt.source_amount === "string" + ? String(Math.ceil(Number(bestAlt.source_amount) * 1.05)) + : { + currency: bestAlt.source_amount.currency, + issuer: bestAlt.source_amount.issuer, + value: String(Number(bestAlt.source_amount.value) * 1.05), + }, + // Rutas de conversión + Paths: bestAlt.paths_computed, + }; + + const prepared = await client.autofill(payment); + const signed = sender.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + const txResult = result.result.meta.TransactionResult; + console.log("\\nResultado:", txResult); + + if (txResult === "tesSUCCESS") { + // Siempre verificar delivered_amount, no Amount + const delivered = result.result.meta.delivered_amount; + if (typeof delivered === "string") { + console.log( + \`Entregado: \${Number(delivered) / 1_000_000} XAH\` + ); + } else { + console.log( + \`Entregado: \${delivered.value} \${delivered.currency}\` + ); + } + } + + await client.disconnect(); +} + +sendCrossCurrencyPayment();`, + }, + ], + slides: [ + { + title: { es: "Pagos cross-currency", en: "", jp: "" }, + content: { + es: "Envía una moneda, el receptor recibe otra\n\n• El DEX integrado convierte automáticamente\n• Amount = lo que recibe el receptor\n• SendMax = máximo que paga el emisor\n• Paths = rutas de conversión", + en: "", + jp: "", + }, + visual: "🔄", + }, + { + title: { es: "Pathfinding", en: "", jp: "" }, + content: { + es: "ripple_path_find busca rutas de conversión\n\n1. Indica cuenta origen y destino\n2. Especifica la moneda y cantidad destino\n3. Obtén alternativas con coste estimado\n4. Usa paths_computed en tu Payment", + en: "", + jp: "", + }, + visual: "🗺️", + }, + { + title: { es: "Pagos parciales", en: "", jp: "" }, + content: { + es: "Flag tfPartialPayment permite entregar menos\n\n• Útil cuando la liquidez varía\n• DeliverMin = mínimo aceptable\n• SIEMPRE verificar delivered_amount\n• NUNCA confiar en el campo Amount\n\n⚠️ Riesgo de seguridad si no se verifica", + en: "", + jp: "", + }, + visual: "⚠️", + }, + ], + }, + { + id: "m5l4", + title: { + es: "Escrows: pagos condicionales", + en: "", + jp: "", + }, + theory: { + es: `Un **Escrow** es un mecanismo de pago condicional que bloquea fondos hasta que se cumplan ciertas condiciones. Es como un sobre sellado con dinero que solo se puede abrir bajo circunstancias específicas. + +### Casos de uso + +- **Pagos programados**: Liberar fondos en una fecha futura determinada +- **Atomic swaps**: Intercambios condicionales entre partes que no confían entre sí +- **Liberación condicional**: Fondos que solo se liberan cuando se proporciona una prueba criptográfica +- **Vesting**: Distribución gradual de tokens a lo largo del tiempo + +### EscrowCreate: crear un escrow + +El tipo de transacción \`EscrowCreate\` bloquea una cantidad de XAH con condiciones: + +| Campo | Descripción | +|---|---| +| \`Amount\` | Cantidad de XAH a bloquear (en drops) | +| \`Destination\` | Cuenta que recibirá los fondos | +| \`FinishAfter\` | Timestamp mínimo para completar el escrow | +| \`CancelAfter\` | Timestamp a partir del cual se puede cancelar | +| \`Condition\` | Crypto-condición opcional para la liberación | + +**Reglas importantes**: +- Debes especificar al menos \`FinishAfter\` o \`Condition\` (o ambos) +- Si usas \`CancelAfter\`, debe ser posterior a \`FinishAfter\` +- Los timestamps usan la **Ripple Epoch** (segundos desde 01/01/2000 00:00:00 UTC) + +### EscrowFinish: completar el escrow + +Cualquier cuenta puede ejecutar \`EscrowFinish\` para liberar los fondos al destinatario: +- Solo funciona después de \`FinishAfter\` (si se especificó) +- Si hay \`Condition\`, debe proporcionarse el \`Fulfillment\` correcto +- Los campos \`Owner\` y \`OfferSequence\` identifican qué escrow completar + +### EscrowCancel: cancelar el escrow + +Con \`EscrowCancel\` se devuelven los fondos al creador: +- Solo funciona después de \`CancelAfter\` +- Cualquier cuenta puede ejecutar la cancelación +- Los fondos vuelven a la cuenta que creó el escrow + +### Crypto-condiciones + +Xahau soporta crypto-condiciones del protocolo **Interledger (ILP)**: +- Basadas en el estándar **PREIMAGE-SHA-256** +- El creador genera un \`Condition\` (hash) y guarda el \`Fulfillment\` (preimagen) +- Para completar el escrow, se debe proporcionar el \`Fulfillment\` que corresponda al \`Condition\` +- Esto permite escrows que solo se liberan cuando alguien demuestra conocer un secreto`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Crear un escrow con bloqueo temporal (FinishAfter = 5 minutos)", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet, xahToDrops } = require("xahau"); + +async function createTimeLockedEscrow() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const sender = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + + // Ripple Epoch: segundos desde 01/01/2000 00:00:00 UTC + // Diferencia con Unix Epoch: 946684800 segundos + const RIPPLE_EPOCH_OFFSET = 946684800; + const now = Math.floor(Date.now() / 1000); + + // FinishAfter: 5 minutos en el futuro + const finishAfter = now - RIPPLE_EPOCH_OFFSET + 5 * 60; + // CancelAfter: 24 horas en el futuro (si nadie lo completa, se puede cancelar) + const cancelAfter = now - RIPPLE_EPOCH_OFFSET + 24 * 60 * 60; + + const escrowCreate = { + TransactionType: "EscrowCreate", + Account: sender.address, + Destination: "rDireccionDelDestinatario", + Amount: xahToDrops(100), // Bloquear 100 XAH + FinishAfter: finishAfter, + CancelAfter: cancelAfter, + }; + + const prepared = await client.autofill(escrowCreate); + const signed = sender.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + const txResult = result.result.meta.TransactionResult; + console.log("=== EscrowCreate ==="); + console.log("Resultado:", txResult); + + if (txResult === "tesSUCCESS") { + console.log("Hash:", signed.hash); + console.log("Sequence:", prepared.Sequence); + console.log( + "FinishAfter:", + new Date((finishAfter + RIPPLE_EPOCH_OFFSET) * 1000).toISOString() + ); + console.log( + "CancelAfter:", + new Date((cancelAfter + RIPPLE_EPOCH_OFFSET) * 1000).toISOString() + ); + console.log("\\n¡Guarda el Sequence! Lo necesitas para EscrowFinish."); + console.log(\`Sequence del escrow: \${prepared.Sequence}\`); + } + + await client.disconnect(); +} + +createTimeLockedEscrow();`, + }, + { + title: { + es: "Completar (finish) un escrow después del tiempo de bloqueo", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function finishEscrow(ownerAddress, escrowSequence) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Cualquier cuenta puede ejecutar el EscrowFinish + const executor = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + + // Primero, verificar que el escrow existe consultando account_objects + const objects = await client.request({ + command: "account_objects", + account: ownerAddress, + type: "escrow", + ledger_index: "validated", + }); + + const escrow = objects.result.account_objects.find( + (obj) => obj.PreviousTxnLgrSeq !== undefined + ); + + if (!escrow) { + console.log("No se encontró el escrow. Puede que ya haya sido completado o cancelado."); + await client.disconnect(); + return; + } + + console.log("=== Escrow encontrado ==="); + console.log("Amount:", Number(escrow.Amount) / 1_000_000, "XAH"); + console.log("Destination:", escrow.Destination); + + // Verificar si ya pasó el FinishAfter + const RIPPLE_EPOCH_OFFSET = 946684800; + const now = Math.floor(Date.now() / 1000); + const finishAfterUnix = escrow.FinishAfter + RIPPLE_EPOCH_OFFSET; + + if (now < finishAfterUnix) { + const remaining = finishAfterUnix - now; + console.log( + \`\\nAún no puedes completar este escrow. Faltan \${remaining} segundos.\` + ); + console.log( + \`Disponible a partir de: \${new Date(finishAfterUnix * 1000).toISOString()}\` + ); + await client.disconnect(); + return; + } + + console.log("\\nEl tiempo de bloqueo ha pasado. Completando escrow..."); + + const escrowFinish = { + TransactionType: "EscrowFinish", + Account: executor.address, + Owner: ownerAddress, + OfferSequence: escrowSequence, + }; + + const prepared = await client.autofill(escrowFinish); + const signed = executor.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + const txResult = result.result.meta.TransactionResult; + console.log("\\n=== EscrowFinish ==="); + console.log("Resultado:", txResult); + + if (txResult === "tesSUCCESS") { + console.log("¡Escrow completado! Los fondos han sido entregados."); + console.log("Hash:", signed.hash); + } else if (txResult === "tecNO_TARGET") { + console.log("El escrow no fue encontrado. Puede haber sido cancelado."); + } + + await client.disconnect(); +} + +// Usa la dirección del creador y el Sequence del EscrowCreate +finishEscrow("rDireccionDelCreador", 12345);`, + }, + ], + slides: [ + { + title: { es: "¿Qué es un Escrow?", en: "", jp: "" }, + content: { + es: "Pago condicional que bloquea fondos\n\n• Bloqueo temporal (FinishAfter)\n• Cancelación automática (CancelAfter)\n• Condición criptográfica (Condition)\n\nUsos: pagos programados, vesting, atomic swaps", + en: "", + jp: "", + }, + visual: "🔐", + }, + { + title: { es: "Ciclo de vida del Escrow", en: "", jp: "" }, + content: { + es: "1. EscrowCreate → Bloquea los fondos\n ↓ (pasa el tiempo)\n2. EscrowFinish → Libera al destinatario\n ó\n2. EscrowCancel → Devuelve al creador\n\n• FinishAfter debe pasar antes de Finish\n• CancelAfter debe pasar antes de Cancel", + en: "", + jp: "", + }, + visual: "⏳", + }, + { + title: { es: "Crypto-condiciones", en: "", jp: "" }, + content: { + es: "Escrows con prueba criptográfica:\n\n• Condition = hash SHA-256\n• Fulfillment = preimagen secreta\n• Solo quien conozca el secreto puede completar\n• Basado en Interledger Protocol\n\nIdeal para intercambios trustless entre partes", + en: "", + jp: "", + }, + visual: "🔑", + }, + ], + }, + ], +} diff --git a/src/data/modules/m05b-anatomia-transacciones.js b/src/data/modules/m05b-anatomia-transacciones.js new file mode 100644 index 0000000..66d56a3 --- /dev/null +++ b/src/data/modules/m05b-anatomia-transacciones.js @@ -0,0 +1,1295 @@ +export default { + id: "m5b", + icon: "🔬", + title: { + es: "Anatomía de una transacción", + en: "", + jp: "", + }, + lessons: [ + { + id: "m5bl1", + title: { + es: "Ciclo de vida de una transacción", + en: "", + jp: "", + }, + theory: { + es: `Antes de profundizar en tokens, NFTs o smart contracts, es fundamental entender **cómo funciona una transacción de principio a fin** en Xahau. Este conocimiento te ayudará a diagnosticar problemas y construir aplicaciones robustas. + +### El flujo completo + +Una transacción en Xahau pasa por **5 fases** desde que la creas hasta que queda registrada permanentemente en el ledger: + +1. **Construir** — Defines los campos de la transacción (tipo, origen, destino, cantidad, etc.) +2. **Preparar (autofill)** — El cliente rellena automáticamente campos técnicos (Fee, Sequence, LastLedgerSequence, NetworkID) +3. **Firmar** — Tu clave privada genera una firma criptográfica que demuestra que autorizas la transacción +4. **Enviar** — La transacción firmada se envía a un nodo de la red +5. **Validar** — Los validadores la incluyen en un ledger mediante consenso y el resultado es final + +### Fase 1: Construir + +Defines un objeto JavaScript con los campos de la transacción: + +\`\`\` +const tx = { + TransactionType: "Payment", + Account: "rOrigen...", + Destination: "rDestino...", + Amount: "1000000", +}; +\`\`\` + +Solo necesitas los campos **esenciales**. Los campos técnicos se rellenan automáticamente en la siguiente fase. + +### Fase 2: Preparar (autofill) + +El método \`client.autofill(tx)\` consulta el nodo y rellena los campos que faltan: + +- **Fee**: El coste de la transacción (en drops). Se calcula según la carga actual de la red +- **Sequence**: El número de secuencia de tu cuenta (se incrementa con cada transacción) +- **LastLedgerSequence**: El ledger máximo en el que la transacción puede ser incluida (protección contra transacciones "fantasma") +- **NetworkID**: Identificador de la red (testnet vs mainnet) + +### Fase 3: Firmar + +El método \`wallet.sign(prepared)\` genera: +- Una **firma digital** usando tu clave privada (ed25519 o secp256k1) +- El **tx_blob**: la transacción serializada en formato hexadecimal, lista para enviar + +La firma demuestra que **tú y solo tú** autorizaste esta transacción. Nadie puede modificar la transacción después de firmada sin invalidar la firma. + +### Fase 4: Enviar + +La transacción firmada se envía al nodo con \`client.submit(tx_blob)\` o \`client.submitAndWait(tx_blob)\`: + +- **submit**: Envía y devuelve el resultado preliminar inmediatamente +- **submitAndWait**: Envía y **espera** hasta que la transacción sea validada o rechazada + +El nodo la propaga a otros nodos de la red. + +### Fase 5: Validar (Consenso) + +Los validadores de la red deciden si incluir la transacción en el próximo ledger: + +1. La transacción llega a las **colas de los validadores** +2. Los validadores proponen incluirla en el próximo ledger +3. Si al menos el **80% de la UNL** está de acuerdo, se incluye +4. El ledger se cierra y el resultado es **final e irreversible** + +### ¿Cuánto tarda? + +El tiempo desde enviar hasta validar es normalmente de **3 a 5 segundos**, el tiempo que tarda en cerrarse un ledger en Xahau. No hay bloques de 10 minutos como en Bitcoin ni tiempos de confirmación variables. + +### Finality: resultados irreversibles + +A diferencia de blockchains con finalidad probabilística (Bitcoin, Ethereum), en Xahau el resultado es **determinista**: +- Si una transacción es incluida en un ledger validado, es **final** +- No hay reorgs, ni forks, ni "confirmaciones pendientes" +- \`tesSUCCESS\` = éxito garantizado, para siempre`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "El flujo completo paso a paso", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function flujoCompleto() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const wallet = Wallet.fromSeed(process.env.WALLET_SEED, {algorithm: 'secp256k1'}); + + // ============================================= + // FASE 1: Construir la transacción + // ============================================= + const tx = { + TransactionType: "Payment", + Account: wallet.address, + Destination: "rDestinoDePrueba", + Amount: "5000000", // 5 XAH en drops + }; + + console.log("1. Transacción construida:"); + console.log(" Tipo:", tx.TransactionType); + console.log(" Campos definidos:", Object.keys(tx).length); + + // ============================================= + // FASE 2: Preparar (autofill) + // ============================================= + const prepared = await client.autofill(tx); + + console.log("\\n2. Transacción preparada (autofill):"); + console.log(" Fee:", prepared.Fee, "drops"); + console.log(" Sequence:", prepared.Sequence); + console.log(" LastLedgerSequence:", prepared.LastLedgerSequence); + console.log(" NetworkID:", prepared.NetworkID); + console.log(" Campos totales:", Object.keys(prepared).length); + + // ============================================= + // FASE 3: Firmar + // ============================================= + const signed = wallet.sign(prepared); + + console.log("\\n3. Transacción firmada:"); + console.log(" Hash:", signed.hash); + console.log(" tx_blob (primeros 60 chars):", signed.tx_blob.substring(0, 60) + "..."); + console.log(" Longitud del blob:", signed.tx_blob.length, "caracteres hex"); + + // ============================================= + // FASE 4: Enviar + // ============================================= + console.log("\\n4. Enviando al nodo..."); + const result = await client.submitAndWait(signed.tx_blob); + + // ============================================= + // FASE 5: Resultado validado + // ============================================= + console.log("\\n5. Resultado validado:"); + console.log(" TransactionResult:", result.result.meta.TransactionResult); + console.log(" Ledger:", result.result.ledger_index); + console.log(" Nodos afectados:", result.result.meta.AffectedNodes.length); + + await client.disconnect(); +} + +flujoCompleto().catch(console.error);`, + }, + ], + slides: [ + { + title: { es: "5 fases de una transacción", en: "", jp: "" }, + content: { + es: "1. Construir → Definir campos (tipo, origen, destino)\n2. Preparar → autofill (Fee, Sequence, NetworkID)\n3. Firmar → Firma digital con clave privada\n4. Enviar → submit / submitAndWait\n5. Validar → Consenso → Resultado final", + en: "", + jp: "", + }, + visual: "📋", + }, + { + title: { es: "Autofill: campos automáticos", en: "", jp: "" }, + content: { + es: "client.autofill() rellena por ti:\n\n• Fee → Coste según carga de red\n• Sequence → Número de tx de tu cuenta\n• LastLedgerSequence → Protección anti-fantasma\n• NetworkID → Testnet vs Mainnet", + en: "", + jp: "", + }, + visual: "⚙️", + }, + { + title: { es: "Finalidad determinista", en: "", jp: "" }, + content: { + es: "Validación en 3-5 segundos\n\n• Sin reorgs ni forks\n• Sin confirmaciones pendientes\n• tesSUCCESS = éxito para siempre\n• Resultado final e irreversible\n\nDiferente a Bitcoin/Ethereum (probabilístico)", + en: "", + jp: "", + }, + visual: "✅", + }, + ], + }, + { + id: "m5bl2", + title: { + es: "Campos de una transacción", + en: "", + jp: "", + }, + theory: { + es: `Cada transacción en Xahau es un **objeto con campos específicos**. Algunos campos son obligatorios, otros opcionales, y otros los rellena \`autofill()\`. Entender cada campo te dará control total sobre tus transacciones. + +### Campos comunes a todas las transacciones + +Estos campos existen en **todo tipo de transacción**: + +| Campo | Obligatorio | Descripción | +|---|---|---| +| **TransactionType** | Sí | Tipo: "Payment", "TrustSet", "OfferCreate", etc. | +| **Account** | Sí | Tu dirección (rXXX...) — quién envía la transacción | +| **Fee** | Autofill | Coste en drops (1 XAH = 1,000,000 drops) | +| **Sequence** | Autofill | Número de secuencia de tu cuenta | +| **LastLedgerSequence** | Autofill | Ledger máximo para incluir la tx | +| **NetworkID** | Autofill | ID de la red (21337 para mainnet Xahau) | +| **SigningPubKey** | Auto (firma) | Tu clave pública (se añade al firmar) | +| **TxnSignature** | Auto (firma) | La firma digital (se añade al firmar) | + +### TransactionType — Tipos de transacción + +Xahau soporta muchos tipos de transacción. Los más comunes: + +- **Payment** — Enviar XAH o tokens +- **TrustSet** — Crear o modificar una trust line +- **OfferCreate** — Crear una oferta en el DEX +- **OfferCancel** — Cancelar una oferta del DEX +- **AccountSet** — Configurar flags de tu cuenta +- **SetHook** — Instalar o gestionar Hooks (smart contracts) +- **URITokenMint** — Crear un NFT (URIToken) +- **URITokenBuy** — Comprar un URIToken +- **URITokenCreateSellOffer** — Poner a la venta un URIToken +- **EscrowCreate** — Crear un pago condicional +- **EscrowFinish** — Completar un escrow +- **EscrowCancel** — Cancelar un escrow + +### Fee — El coste de la transacción + +El Fee en Xahau funciona diferente a otras blockchains: + +- Se expresa en **drops** (1 XAH = 1,000,000 drops) +- El fee base es **12 drops** (0.000012 XAH) — extremadamente barato +- El fee **se quema** — no va a validadores ni a nadie. Se destruye +- Cuando la red está congestionada, el fee puede subir temporalmente (**fee escalation**) +- \`autofill()\` calcula el fee óptimo según la carga actual de la red + +### Sequence — Orden de transacciones + +El Sequence es un **contador incremental** de tu cuenta: + +- Empieza en el número asignado al activar la cuenta +- Se incrementa en 1 con cada transacción exitosa +- Garantiza que las transacciones se procesen **en orden** +- Si envías dos transacciones con el mismo Sequence, solo una se procesará +- Si falta un Sequence intermedio (ej: envías 5, 6, 8 sin 7), las transacciones 8+ quedan en cola hasta que la 7 se resuelva + +### LastLedgerSequence — Protección contra fantasmas + +El campo LastLedgerSequence es una **fecha de caducidad** para tu transacción: + +- Especifica el **número de ledger máximo** en el que puede ser incluida +- Si el ledger actual supera este número y la transacción no se ha procesado, se descarta +- Evita que transacciones "perdidas" se ejecuten minutos u horas después +- \`autofill()\` lo establece automáticamente (normalmente ledger actual + 20) + +### Flags — Modificadores de comportamiento + +Muchos tipos de transacción aceptan un campo **Flags** que modifica su comportamiento: + +- Los flags son **valores numéricos** que se combinan con operaciones de bits +- Ejemplo: \`Flags: 1\` en URITokenMint activa \`tfBurnable\` +- Ejemplo: \`Flags: 0x00020000\` en OfferCreate activa \`tfImmediateOrCancel\` +- Puedes combinar flags sumando sus valores + +### Memos — Datos adjuntos + +Puedes adjuntar datos a cualquier transacción usando el campo **Memos**: + +- **MemoType**: Tipo MIME en hexadecimal (ej: "text/plain") +- **MemoData**: El contenido en hexadecimal +- Los memos son **públicos** y permanentes en el ledger +- No afectan la lógica de la transacción, solo almacenan información adicional`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Inspeccionar los campos antes y después de autofill", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function inspeccionarCampos() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const wallet = Wallet.fromSeed(process.env.WALLET_SEED, {algorithm: 'secp256k1'}); + + // Transacción con solo los campos esenciales + const tx = { + TransactionType: "Payment", + Account: wallet.address, + Destination: "rDestinoAqui", + Amount: "1000000", // 1 XAH + }; + + console.log("=== ANTES de autofill ==="); + console.log("Campos definidos:", Object.keys(tx)); + console.log(JSON.stringify(tx, null, 2)); + + // Autofill rellena los campos técnicos + const prepared = await client.autofill(tx); + + console.log("\\n=== DESPUÉS de autofill ==="); + console.log("Campos totales:", Object.keys(prepared)); + console.log(JSON.stringify(prepared, null, 2)); + + // Mostrar los campos que autofill añadió + const camposNuevos = Object.keys(prepared).filter( + (k) => !Object.keys(tx).includes(k) + ); + console.log("\\n=== Campos añadidos por autofill ==="); + for (const campo of camposNuevos) { + console.log(" " + campo + ":", prepared[campo]); + } + + await client.disconnect(); +} + +inspeccionarCampos().catch(console.error);`, + }, + { + title: { + es: "Construir distintos tipos de transacción", + en: "", + jp: "", + }, + language: "javascript", + code: `// Ejemplos de cómo se construyen distintos tipos de transacción. +// Solo mostramos los campos esenciales — autofill() rellena el resto. + +// --- Payment: enviar XAH --- +const payment = { + TransactionType: "Payment", + Account: "rOrigen...", + Destination: "rDestino...", + Amount: "5000000", // 5 XAH en drops +}; + +// --- Payment: enviar token --- +const tokenPayment = { + TransactionType: "Payment", + Account: "rOrigen...", + Destination: "rDestino...", + Amount: { + currency: "USD", + value: "100", + issuer: "rEmisor...", + }, +}; + +// --- TrustSet: crear trust line --- +const trustSet = { + TransactionType: "TrustSet", + Account: "rReceptor...", + LimitAmount: { + currency: "USD", + value: "10000", + issuer: "rEmisor...", + }, +}; + +// --- OfferCreate: crear oferta en el DEX --- +const offer = { + TransactionType: "OfferCreate", + Account: "rTrader...", + TakerPays: { currency: "USD", value: "50", issuer: "rEmisor..." }, + TakerGets: "100000000", // 100 XAH +}; + +// --- AccountSet: activar flag --- +const accountSet = { + TransactionType: "AccountSet", + Account: "rMiCuenta...", + SetFlag: 8, // asfDefaultRipple +}; + +// --- URITokenMint: crear NFT --- +const mint = { + TransactionType: "URITokenMint", + Account: "rCreador...", + URI: "68747470733A2F2F...", // URL en hexadecimal + Flags: 1, // tfBurnable +}; + +console.log("Cada tipo tiene sus campos específicos."); +console.log("Todos comparten: TransactionType, Account, Fee, Sequence.");`, + }, + ], + slides: [ + { + title: { es: "Campos comunes", en: "", jp: "" }, + content: { + es: "Toda transacción tiene:\n\n• TransactionType → Tipo de operación\n• Account → Quién envía\n• Fee → Coste (en drops, se quema)\n• Sequence → Orden de txs de la cuenta\n• LastLedgerSequence → Caducidad\n• NetworkID → Red (testnet/mainnet)", + en: "", + jp: "", + }, + visual: "📝", + }, + { + title: { es: "Tipos de transacción", en: "", jp: "" }, + content: { + es: "• Payment → Enviar XAH o tokens\n• TrustSet → Trust lines\n• OfferCreate/Cancel → DEX\n• AccountSet → Configurar cuenta\n• SetHook → Smart contracts\n• URITokenMint/Buy → NFTs\n• EscrowCreate/Finish → Pagos condicionales", + en: "", + jp: "", + }, + visual: "📦", + }, + { + title: { es: "Fee, Sequence y Flags", en: "", jp: "" }, + content: { + es: "Fee: 12 drops base (~gratis), se quema\n\nSequence: contador incremental\n• Garantiza orden de ejecución\n• Sin huecos: txs quedan en cola\n\nFlags: modifican comportamiento\n• Se combinan sumando valores\n• Cada tipo tiene sus flags propios", + en: "", + jp: "", + }, + visual: "🔢", + }, + ], + }, + { + id: "m5bl3", + title: { + es: "Firma digital y serialización", + en: "", + jp: "", + }, + theory: { + es: `La firma digital es el mecanismo que garantiza que **solo tú puedes autorizar transacciones** desde tu cuenta. Entender cómo funciona te ayudará a comprender la seguridad de Xahau y a depurar problemas de firma. + +### ¿Qué es una firma digital? + +Una firma digital es una prueba matemática de que: +1. **Tú creaste la transacción** (autenticación) +2. **Nadie la modificó** después de firmarla (integridad) +3. **No puedes negar** haberla firmado (no repudio) + +### Algoritmos de firma en Xahau + +Xahau soporta dos algoritmos criptográficos: + +| Algoritmo | Prefijo del seed | Características | +|---|---|---| +| **ed25519** | sEd... | Más rápido, moderno, recomendado | +| **secp256k1** | s... (sin Ed) | Compatible con Bitcoin/Ethereum, más antiguo | + +Cuando generas una wallet con \`Wallet.generate()\`, por defecto se usa **ed25519**. Los seeds que empiezan por \`sEd\` usan ed25519. + +### El proceso de firma paso a paso + +1. **Serialización**: La transacción (objeto JSON) se convierte a **formato binario** siguiendo el protocolo de Xahau. Cada campo tiene un código de tipo y un orden específico. + +2. **Hashing**: El binario serializado se pasa por una función hash (SHA-512 half) para obtener un **resumen de 32 bytes**. + +3. **Firma**: Tu clave privada genera una firma criptográfica sobre ese hash. Esta firma solo puede verificarse con tu clave pública. + +4. **Ensamblaje**: La firma (\`TxnSignature\`) y tu clave pública (\`SigningPubKey\`) se añaden a la transacción serializada, generando el **tx_blob** final. + +### tx_blob: la transacción lista para enviar + +El \`tx_blob\` es una cadena hexadecimal que contiene **toda la transacción** (campos + firma) en formato binario. Es lo que realmente se envía a la red: + +\`\`\` +wallet.sign(prepared) +// Devuelve: { tx_blob: "1200002280000000...", hash: "A1B2C3..." } +\`\`\` + +- **tx_blob**: La transacción serializada y firmada (hex) +- **hash**: El identificador único de la transacción (para buscarla después) + +### Verificación de la firma + +Cuando un nodo recibe tu tx_blob: + +1. Deserializa el blob para extraer los campos +2. Extrae la \`SigningPubKey\` y la \`TxnSignature\` +3. Verifica que la firma corresponde a los datos y la clave pública +4. Verifica que la clave pública corresponde a la dirección \`Account\` +5. Si todo coincide, la transacción es válida + +Si alguien modifica **un solo bit** del tx_blob, la firma deja de ser válida y la transacción es rechazada. + +### Firma offline + +Puedes firmar transacciones **sin conexión a internet**: + +1. En un dispositivo conectado: prepara la transacción con \`autofill()\` +2. Copia la transacción preparada a un dispositivo offline +3. En el dispositivo offline: firma con \`wallet.sign()\` +4. Copia el \`tx_blob\` de vuelta al dispositivo conectado +5. Envía con \`client.submit(tx_blob)\` + +Esto es útil para **cold wallets** — las claves privadas nunca tocan un dispositivo con internet. + +### Multi-firma (MultiSign) + +Xahau soporta **multi-firma**: una transacción que requiere la firma de **múltiples cuentas** para ser válida. Se configura con \`SignerListSet\`: + +- Defines una lista de firmantes (SignerList) con sus pesos +- Estableces un quórum mínimo +- Cada firmante firma la transacción por separado +- Las firmas se combinan y se envían juntas +- Útil para cuentas compartidas, DAOs, o seguridad adicional`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Firma y verificación del tx_blob", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function firmaDetallada() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const wallet = Wallet.fromSeed(process.env.WALLET_SEED, {algorithm: 'secp256k1'}); + + console.log("=== INFORMACIÓN DE LA WALLET ==="); + console.log("Dirección:", wallet.address); + console.log("Clave pública:", wallet.publicKey); + console.log("Algoritmo:", wallet.publicKey.startsWith("ED") ? "ed25519" : "secp256k1"); + + // Construir y preparar + const tx = { + TransactionType: "Payment", + Account: wallet.address, + Destination: "rDestinoAqui", + Amount: "1000000", + }; + + const prepared = await client.autofill(tx); + + // Firmar + const signed = wallet.sign(prepared); + + console.log("\\n=== RESULTADO DE LA FIRMA ==="); + console.log("Hash (ID de la tx):", signed.hash); + console.log("tx_blob completo:", signed.tx_blob); + console.log("Longitud:", signed.tx_blob.length, "caracteres hex"); + console.log("Tamaño:", signed.tx_blob.length / 2, "bytes"); + + // Verificar que la transacción es válida + // (el nodo hace esto internamente al recibir el submit) + console.log("\\n=== VERIFICACIÓN ==="); + + // Decodificar el blob para inspeccionar + const decoded = client.request({ + command: "tx", + transaction: signed.hash, + }).catch(() => { + // La tx aún no existe en el ledger, es normal + console.log("La tx aún no se ha enviado (solo firmada)."); + }); + + // Enviar + console.log("\\nEnviando tx_blob al nodo..."); + const result = await client.submitAndWait(signed.tx_blob); + console.log("Resultado:", result.result.meta.TransactionResult); + + // Ahora sí podemos buscarla por hash + const txInfo = await client.request({ + command: "tx", + transaction: signed.hash, + }); + + console.log("\\n=== TX EN EL LEDGER ==="); + console.log("Tipo:", txInfo.result.TransactionType); + console.log("SigningPubKey:", txInfo.result.SigningPubKey); + console.log("Ledger:", txInfo.result.ledger_index); + + await client.disconnect(); +} + +firmaDetallada().catch(console.error);`, + }, + { + title: { + es: "Firma offline: preparar en un lado, firmar en otro", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +// ============================================= +// PASO 1: En el dispositivo CONECTADO +// Preparar la transacción (necesita conexión) +// ============================================= +async function prepararOnline() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const tx = { + TransactionType: "Payment", + Account: "rTuDireccionAqui", + Destination: "rDestinoAqui", + Amount: "10000000", // 10 XAH + }; + + const prepared = await client.autofill(tx); + await client.disconnect(); + + // Guardar como JSON para transferir al dispositivo offline + const txParaFirmar = JSON.stringify(prepared, null, 2); + console.log("=== COPIA ESTE JSON AL DISPOSITIVO OFFLINE ==="); + console.log(txParaFirmar); + + return prepared; +} + +// ============================================= +// PASO 2: En el dispositivo OFFLINE (sin internet) +// Firmar la transacción +// ============================================= +function firmarOffline(preparedJSON) { + // La clave privada SOLO existe en el dispositivo offline + const wallet = Wallet.fromSeed("sEdVxxxClaveDelDispositivoOffline", {algorithm: 'secp256k1'}); + + const signed = wallet.sign(preparedJSON); + + console.log("\\n=== COPIA ESTE tx_blob AL DISPOSITIVO CONECTADO ==="); + console.log("tx_blob:", signed.tx_blob); + console.log("hash:", signed.hash); + + return signed; +} + +// ============================================= +// PASO 3: En el dispositivo CONECTADO +// Enviar la transacción firmada +// ============================================= +async function enviarOnline(txBlob) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const result = await client.submitAndWait(txBlob); + console.log("\\nResultado:", result.result.meta.TransactionResult); + + await client.disconnect(); +} + +// Demo del flujo completo (en un solo script para simplicidad) +async function demo() { + const prepared = await prepararOnline(); + const signed = firmarOffline(prepared); + await enviarOnline(signed.tx_blob); +} + +demo().catch(console.error);`, + }, + ], + slides: [ + { + title: { es: "¿Qué es una firma digital?", en: "", jp: "" }, + content: { + es: "Prueba matemática de que:\n\n• Tú creaste la transacción (autenticación)\n• Nadie la modificó (integridad)\n• No puedes negar haberla firmado (no repudio)\n\nAlgoritmos: ed25519 (sEd...) o secp256k1 (s...)", + en: "", + jp: "", + }, + visual: "🔏", + }, + { + title: { es: "El proceso de firma", en: "", jp: "" }, + content: { + es: "1. Serializar → JSON a binario\n2. Hash → SHA-512 half (32 bytes)\n3. Firmar → Clave privada genera firma\n4. Ensamblar → tx_blob (hex)\n\nwallet.sign(prepared)\n→ { tx_blob: \"1200...\", hash: \"A1B2...\" }", + en: "", + jp: "", + }, + visual: "🔐", + }, + { + title: { es: "Firma offline y multi-firma", en: "", jp: "" }, + content: { + es: "Firma offline (cold wallet):\n• Preparar online → Firmar offline → Enviar online\n• Claves nunca tocan internet\n\nMulti-firma (MultiSign):\n• Múltiples firmantes con pesos\n• Quórum mínimo configurable\n• Ideal para cuentas compartidas", + en: "", + jp: "", + }, + visual: "🧊", + }, + ], + }, + { + id: "m5bl4", + title: { + es: "Envío, validación y resultados", + en: "", + jp: "", + }, + theory: { + es: `Una vez firmada la transacción, hay que enviarla a la red y entender los posibles resultados. Xahau tiene un sistema de **códigos de resultado** muy detallado que te indica exactamente qué pasó. + +### submit vs submitAndWait + +La librería \`xahau\` ofrece dos métodos para enviar transacciones: + +**client.submit(tx_blob)**: +- Envía la transacción y devuelve **inmediatamente** +- El resultado preliminar indica si la transacción fue aceptada por el nodo (no si fue validada) +- Necesitas consultar después con \`tx\` para ver el resultado final +- Útil cuando quieres enviar muchas transacciones rápidamente + +**client.submitAndWait(tx_blob)**: +- Envía la transacción y **espera** a que sea incluida en un ledger validado +- Devuelve el resultado final directamente +- Más cómodo para la mayoría de casos +- Puede tardar 3-10 segundos (1-2 ledgers) + +### Categorías de códigos de resultado + +Los resultados de una transacción se dividen en categorías según su **prefijo**: + +### tes — Éxito + +\`tesSUCCESS\` es el único código de éxito. Significa que la transacción se procesó correctamente y los cambios se aplicaron al ledger. + +### tec — Transacción incluida pero falló + +Los códigos \`tec\` significan que la transacción fue **incluida en un ledger** (y se cobró el fee), pero la operación **no se ejecutó**: + +| Código | Significado | +|---|---| +| **tecUNFUNDED_PAYMENT** | No tienes suficiente balance para el pago | +| **tecNO_LINE** | No existe trust line para el token | +| **tecNO_DST** | La cuenta destino no existe | +| **tecDST_TAG_NEEDED** | La cuenta destino requiere DestinationTag | +| **tecNO_PERMISSION** | No tienes permiso para esta operación | +| **tecINSUFFICIENT_RESERVE** | No tienes suficiente XAH para la reserva del nuevo objeto | +| **tecPATH_DRY** | No se encontró una ruta de pago viable | +| **tecKILLED** | Oferta cancelada por flag tfFillOrKill | + +**Importante**: En los errores \`tec\`, el fee **sí se cobra** aunque la operación falle. + +### tef — Error antes del procesamiento + +Los códigos \`tef\` indican que la transacción fue **rechazada antes de ser procesada**. El fee **no se cobra**: + +| Código | Significado | +|---|---| +| **tefPAST_SEQ** | El Sequence ya se usó (transacción duplicada) | +| **tefMAX_LEDGER** | LastLedgerSequence ya pasó (transacción caducada) | +| **tefALREADY** | La transacción ya está en la cola | + +### tem — Error de formato + +Los códigos \`tem\` indican que la transacción está **mal formada** y nunca podría ser válida: + +| Código | Significado | +|---|---| +| **temMALFORMED** | Campos inválidos o formato incorrecto | +| **temBAD_AMOUNT** | Cantidad inválida (negativa, cero en XAH, etc.) | +| **temBAD_FEE** | Fee inválido | +| **temDISABLED** | La funcionalidad está desactivada en esta red | +| **temINVALID_FLAG** | Flag no válido para este tipo de transacción | + +### ter — Error temporal (reintentar) + +Los códigos \`ter\` indican un error **temporal** que podría resolverse si reintentas: + +| Código | Significado | +|---|---| +| **terPRE_SEQ** | Hay una transacción anterior pendiente (Sequence previo) | +| **terQUEUED** | La transacción está en cola esperando (demasiadas en vuelo) | +| **terINSUF_FEE_B** | Fee insuficiente dada la carga actual | + +### Leer el resultado completo + +El objeto de resultado contiene toda la información que necesitas: + +\`\`\` +result.result.meta.TransactionResult → El código (tesSUCCESS, etc.) +result.result.meta.AffectedNodes → Qué cambió en el ledger +result.result.ledger_index → En qué ledger se incluyó +result.result.hash → Hash único de la transacción +\`\`\``, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Manejar todos los tipos de resultado", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function enviarConManejo() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const wallet = Wallet.fromSeed(process.env.WALLET_SEED, {algorithm: 'secp256k1'}); + + const tx = { + TransactionType: "Payment", + Account: wallet.address, + Destination: "rDestinoAqui", + Amount: "1000000", + }; + + try { + const prepared = await client.autofill(tx); + const signed = wallet.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + const codigo = result.result.meta.TransactionResult; + + // Analizar el resultado por categoría + if (codigo === "tesSUCCESS") { + console.log("ÉXITO: Transacción procesada correctamente."); + console.log("Ledger:", result.result.ledger_index); + console.log("Hash:", signed.hash); + + } else if (codigo.startsWith("tec")) { + // La tx se incluyó en el ledger pero la operación falló + // El fee SÍ se cobró + console.log("FALLO (tec):", codigo); + console.log("La operación no se ejecutó pero el fee se cobró."); + + // Diagnóstico específico + switch (codigo) { + case "tecUNFUNDED_PAYMENT": + console.log("→ No tienes suficiente balance."); + break; + case "tecNO_DST": + console.log("→ La cuenta destino no existe."); + break; + case "tecDST_TAG_NEEDED": + console.log("→ Falta el DestinationTag."); + break; + case "tecINSUFFICIENT_RESERVE": + console.log("→ No tienes suficiente XAH para la reserva."); + break; + default: + console.log("→ Consulta la documentación para:", codigo); + } + + } else if (codigo.startsWith("tef")) { + console.log("RECHAZADA (tef):", codigo); + console.log("La transacción fue rechazada antes de procesarse."); + console.log("El fee NO se cobró."); + + } else if (codigo.startsWith("tem")) { + console.log("MAL FORMADA (tem):", codigo); + console.log("La transacción tiene un error de formato."); + console.log("Revisa los campos y los valores."); + + } else if (codigo.startsWith("ter")) { + console.log("ERROR TEMPORAL (ter):", codigo); + console.log("Puedes reintentar en unos segundos."); + } + + } catch (error) { + console.error("Error de conexión o envío:", error.message); + } + + await client.disconnect(); +} + +enviarConManejo().catch(console.error);`, + }, + { + title: { + es: "Diferencia entre submit y submitAndWait", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function comparar() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const wallet = Wallet.fromSeed(process.env.WALLET_SEED, {algorithm: 'secp256k1'}); + + // --- Método 1: submitAndWait (recomendado) --- + console.log("=== submitAndWait ==="); + const tx1 = await client.autofill({ + TransactionType: "Payment", + Account: wallet.address, + Destination: "rDestinoAqui", + Amount: "1000000", + }); + const signed1 = wallet.sign(tx1); + + console.log("Enviando y esperando..."); + const inicio1 = Date.now(); + const result1 = await client.submitAndWait(signed1.tx_blob); + const tiempo1 = Date.now() - inicio1; + + console.log("Resultado:", result1.result.meta.TransactionResult); + console.log("Tiempo:", tiempo1, "ms"); + console.log("Ledger:", result1.result.ledger_index); + + // --- Método 2: submit (sin esperar) --- + console.log("\\n=== submit (sin esperar) ==="); + const tx2 = await client.autofill({ + TransactionType: "Payment", + Account: wallet.address, + Destination: "rDestinoAqui", + Amount: "1000000", + }); + const signed2 = wallet.sign(tx2); + + console.log("Enviando..."); + const inicio2 = Date.now(); + const result2 = await client.submit(signed2.tx_blob); + const tiempo2 = Date.now() - inicio2; + + console.log("Resultado preliminar:", result2.result.engine_result); + console.log("Tiempo:", tiempo2, "ms (mucho más rápido)"); + console.log("NOTA: Este resultado es PRELIMINAR, no final."); + + // Para ver el resultado final, hay que consultar después: + console.log("\\nEsperando 5 segundos para consultar el resultado final..."); + await new Promise((r) => setTimeout(r, 5000)); + + const txInfo = await client.request({ + command: "tx", + transaction: signed2.hash, + }); + console.log("Resultado final:", txInfo.result.meta.TransactionResult); + + await client.disconnect(); +} + +comparar().catch(console.error);`, + }, + ], + slides: [ + { + title: { es: "submit vs submitAndWait", en: "", jp: "" }, + content: { + es: "submit():\n• Envía y devuelve inmediatamente\n• Resultado preliminar (no final)\n• Rápido, para enviar muchas txs\n\nsubmitAndWait():\n• Envía y espera validación (3-10s)\n• Resultado final directo\n• Recomendado para la mayoría de casos", + en: "", + jp: "", + }, + visual: "📤", + }, + { + title: { es: "Códigos de resultado", en: "", jp: "" }, + content: { + es: "• tesSUCCESS → Éxito\n• tec... → Incluida pero falló (fee cobrado)\n• tef... → Rechazada (fee NO cobrado)\n• tem... → Mal formada (error de formato)\n• ter... → Error temporal (reintentar)\n\nSiempre verifica meta.TransactionResult", + en: "", + jp: "", + }, + visual: "🏷️", + }, + { + title: { es: "Errores tec más comunes", en: "", jp: "" }, + content: { + es: "• tecUNFUNDED_PAYMENT → Sin balance\n• tecNO_DST → Destino no existe\n• tecDST_TAG_NEEDED → Falta tag\n• tecNO_LINE → Sin trust line\n• tecINSUFFICIENT_RESERVE → Sin reserva\n• tecPATH_DRY → Sin ruta de pago\n\nEl fee SE cobra en errores tec", + en: "", + jp: "", + }, + visual: "⚠️", + }, + ], + }, + { + id: "m5bl5", + title: { + es: "Transacciones a nivel del ledger", + en: "", + jp: "", + }, + theory: { + es: `Para entender realmente cómo funcionan las transacciones, necesitas ver lo que ocurre **dentro del ledger** cuando una transacción se procesa. Esto te ayudará a depurar problemas complejos y a entender la metadata. + +### ¿Cómo modifica una transacción el ledger? + +Cuando una transacción se procesa con éxito, modifica el **estado del ledger** — los objetos almacenados en la base de datos del ledger. Estos cambios se registran en la **metadata** de la transacción. + +### AffectedNodes — La huella de la transacción + +El campo \`meta.AffectedNodes\` es un array que describe **exactamente qué cambió** en el ledger. Cada nodo afectado puede ser de tres tipos: + +### CreatedNode — Objeto nuevo + +Se creó un nuevo objeto en el ledger: + +\`\`\` +{ + "CreatedNode": { + "LedgerEntryType": "RippleState", // Tipo de objeto + "LedgerIndex": "ABC123...", // ID único del objeto + "NewFields": { // Los campos del nuevo objeto + "Balance": { "value": "100" }, + "LowLimit": { ... }, + "HighLimit": { ... } + } + } +} +\`\`\` + +Ejemplos: nueva trust line, nueva oferta en el DEX, nuevo URIToken. + +### ModifiedNode — Objeto modificado + +Se modificó un objeto existente: + +\`\`\` +{ + "ModifiedNode": { + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "DEF456...", + "PreviousFields": { // Estado ANTES + "Balance": "100000000" + }, + "FinalFields": { // Estado DESPUÉS + "Balance": "95000000", + "Sequence": 43 + } + } +} +\`\`\` + +\`PreviousFields\` solo muestra los campos que **cambiaron** (no todos los campos del objeto). \`FinalFields\` muestra el estado completo después del cambio. + +### DeletedNode — Objeto eliminado + +Se eliminó un objeto del ledger: + +\`\`\` +{ + "DeletedNode": { + "LedgerEntryType": "Offer", + "LedgerIndex": "GHI789...", + "FinalFields": { // Estado al momento de eliminación + "TakerPays": "0", + "TakerGets": "0" + } + } +} +\`\`\` + +Ejemplos: oferta completada/cancelada, trust line eliminada (balance 0), URIToken quemado. + +### Balance changes — Seguir el dinero + +En una transacción de pago, puedes rastrear exactamente cómo se movió el dinero observando los \`ModifiedNode\` de tipo \`AccountRoot\`: + +- La cuenta de origen: \`Balance\` disminuye (envió XAH) +- La cuenta de destino: \`Balance\` aumenta (recibió XAH) +- La diferencia entre los balances es el \`Amount\` + \`Fee\` + +Para tokens (IOUs), los cambios se ven en los \`ModifiedNode\` de tipo \`RippleState\`. + +### Reserves — El sistema de reservas + +El ledger de Xahau usa un sistema de **reservas** que afecta tu balance disponible: + +- **Reserva base**: 1 XAH — mínimo para que una cuenta exista +- **Reserva por objeto**: 0.2 XAH por cada objeto que tu cuenta posee + +Cada objeto en el ledger (trust line, oferta, URIToken, Hook) aumenta tu reserva. El XAH reservado no se puede gastar hasta que elimines el objeto. + +### Orden de procesamiento en un ledger + +Dentro de un ledger, las transacciones se procesan en un **orden determinista**: + +1. Las transacciones se ordenan por **hash canónico** (no por Sequence ni por hora de envío) +2. Se procesan secuencialmente en ese orden +3. Cada transacción ve el estado del ledger después de la transacción anterior +4. Si dos transacciones compiten por los mismos recursos, la primera (por hash) gana + +Esto garantiza que **todos los validadores calculen exactamente el mismo resultado**, independientemente del orden en que recibieron las transacciones. + +### El hash del ledger + +Cuando se cierra un ledger, se calcula un **hash** que resume: +- El hash del ledger anterior (cadena de ledgers) +- Todas las transacciones incluidas y sus metadatas +- El estado completo del ledger (árbol de estado) + +Si un validador calcula un hash diferente al 80% de la UNL, su ledger se descarta — esto garantiza la consistencia de la red.`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Analizar los AffectedNodes de una transacción", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function analizarMetadata() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const wallet = Wallet.fromSeed(process.env.WALLET_SEED, {algorithm: 'secp256k1'}); + + // Enviar un pago para analizar su metadata + const tx = { + TransactionType: "Payment", + Account: wallet.address, + Destination: "rDestinoAqui", + Amount: "5000000", // 5 XAH + }; + + const prepared = await client.autofill(tx); + const signed = wallet.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + const meta = result.result.meta; + console.log("=== ANÁLISIS DE METADATA ===\\n"); + console.log("Resultado:", meta.TransactionResult); + console.log("Nodos afectados:", meta.AffectedNodes.length); + + // Clasificar los nodos afectados + const creados = []; + const modificados = []; + const eliminados = []; + + for (const node of meta.AffectedNodes) { + if (node.CreatedNode) { + creados.push(node.CreatedNode); + } else if (node.ModifiedNode) { + modificados.push(node.ModifiedNode); + } else if (node.DeletedNode) { + eliminados.push(node.DeletedNode); + } + } + + // Mostrar objetos creados + if (creados.length > 0) { + console.log("\\n--- OBJETOS CREADOS ---"); + for (const n of creados) { + console.log(" +", n.LedgerEntryType); + console.log(" Index:", n.LedgerIndex); + } + } + + // Mostrar objetos modificados + if (modificados.length > 0) { + console.log("\\n--- OBJETOS MODIFICADOS ---"); + for (const n of modificados) { + console.log(" ~", n.LedgerEntryType); + if (n.PreviousFields && n.FinalFields) { + // Mostrar cambios en balance (AccountRoot) + if (n.PreviousFields.Balance && n.FinalFields.Balance) { + const antes = Number(n.PreviousFields.Balance) / 1000000; + const despues = Number(n.FinalFields.Balance) / 1000000; + const diff = despues - antes; + console.log(" Balance:", antes, "→", despues, "XAH"); + console.log(" Cambio:", diff > 0 ? "+" : "", diff.toFixed(6), "XAH"); + } + // Mostrar cambio de Sequence + if (n.FinalFields.Sequence) { + console.log(" Sequence:", n.FinalFields.Sequence); + } + } + } + } + + // Mostrar objetos eliminados + if (eliminados.length > 0) { + console.log("\\n--- OBJETOS ELIMINADOS ---"); + for (const n of eliminados) { + console.log(" -", n.LedgerEntryType); + } + } + + // Resumen de balance + console.log("\\n--- RESUMEN ---"); + console.log("Fee pagado:", Number(result.result.Fee) / 1000000, "XAH"); + console.log("El fee se quemó (no fue a ninguna cuenta)."); + + await client.disconnect(); +} + +analizarMetadata().catch(console.error);`, + }, + { + title: { + es: "Consultar la reserva actual de tu cuenta", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function consultarReserva(address) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Obtener info del servidor para las reservas actuales + const serverInfo = await client.request({ command: "server_info" }); + const ledgerInfo = serverInfo.result.info.validated_ledger; + const reservaBase = ledgerInfo.reserve_base / 1000000; // En XAH + const reservaObjeto = ledgerInfo.reserve_inc / 1000000; // En XAH + + console.log("=== RESERVAS DE LA RED ==="); + console.log("Reserva base (por cuenta):", reservaBase, "XAH"); + console.log("Reserva por objeto:", reservaObjeto, "XAH"); + + // Obtener info de la cuenta + const accountInfo = await client.request({ + command: "account_info", + account: address, + ledger_index: "validated", + }); + + const account = accountInfo.result.account_data; + const balance = Number(account.Balance) / 1000000; + const ownerCount = account.OwnerCount; + const reservaTotal = reservaBase + (ownerCount * reservaObjeto); + const disponible = balance - reservaTotal; + + console.log("\\n=== TU CUENTA ==="); + console.log("Dirección:", address); + console.log("Balance total:", balance, "XAH"); + console.log("Objetos en el ledger:", ownerCount); + console.log("Reserva total:", reservaTotal, "XAH"); + console.log(" →", reservaBase, "XAH (base)"); + console.log(" +", ownerCount, "x", reservaObjeto, "=", ownerCount * reservaObjeto, "XAH (objetos)"); + console.log("Disponible para gastar:", disponible, "XAH"); + + // Mostrar qué objetos tienes + const objects = await client.request({ + command: "account_objects", + account: address, + ledger_index: "validated", + }); + + const porTipo = {}; + for (const obj of objects.result.account_objects) { + const tipo = obj.LedgerEntryType; + porTipo[tipo] = (porTipo[tipo] || 0) + 1; + } + + console.log("\\n=== OBJETOS POR TIPO ==="); + for (const [tipo, cantidad] of Object.entries(porTipo)) { + console.log(" " + tipo + ":", cantidad, "(reserva:", cantidad * reservaObjeto, "XAH)"); + } + + await client.disconnect(); +} + +consultarReserva("rTuDireccionAqui");`, + }, + ], + slides: [ + { + title: { es: "AffectedNodes", en: "", jp: "" }, + content: { + es: "Cada transacción registra qué cambió:\n\n• CreatedNode → Nuevo objeto en el ledger\n• ModifiedNode → Objeto existente modificado\n (PreviousFields → FinalFields)\n• DeletedNode → Objeto eliminado\n\nLa huella exacta de la transacción", + en: "", + jp: "", + }, + visual: "🔍", + }, + { + title: { es: "Sistema de reservas", en: "", jp: "" }, + content: { + es: "Reserva base: 1 XAH por cuenta\nReserva por objeto: 0.2 XAH cada uno\n\nObjetos que consumen reserva:\n• Trust lines, Ofertas DEX\n• URITokens, Hooks\n\nEliminar objeto = liberar reserva\nDisponible = Balance - Reserva total", + en: "", + jp: "", + }, + visual: "💰", + }, + { + title: { es: "Orden y consistencia", en: "", jp: "" }, + content: { + es: "Dentro de un ledger:\n\n• Txs ordenadas por hash canónico\n• Procesadas secuencialmente\n• Mismo resultado en todos los nodos\n\nHash del ledger resume:\n• Ledger anterior + Txs + Estado\n• 80% UNL debe coincidir\n• Garantiza consistencia total", + en: "", + jp: "", + }, + visual: "🔗", + }, + ], + }, + ], +} diff --git a/src/data/modules/m06-tokens.js b/src/data/modules/m06-tokens.js new file mode 100644 index 0000000..c39e799 --- /dev/null +++ b/src/data/modules/m06-tokens.js @@ -0,0 +1,720 @@ +export default { + id: "m6", + icon: "🪙", + title: { + es: "Creación y gestión de tokens propios", + en: "", + jp: "", + }, + lessons: [ + { + id: "m6l1", + title: { + es: "TrustLines y el modelo de tokens en Xahau", + en: "", + jp: "", + }, + theory: { + es: `En Xahau (y XRPL), los tokens fungibles funcionan de manera diferente a ERC-20 en Ethereum. No necesitas desplegar un smart contract para crear un token. En su lugar, se usa un sistema basado en **TrustLines** (líneas de confianza). + +### ¿Cómo funciona? + +1. **Emisor (Issuer)**: Cualquier cuenta puede emitir un token. La cuenta emisora se convierte en el "banco central" de ese token +2. **TrustLine**: Para recibir un token, el receptor debe crear primero una **TrustLine** hacia el emisor. Esto es como decir "confío en esta cuenta hasta X cantidad de este token" +3. **Transferencia**: Una vez que existe la TrustLine, el emisor puede enviar tokens al receptor mediante un Payment + +### Identificación de tokens + +Cada token se identifica por dos campos: +- **currency**: Código de 3 caracteres (ej: "USD", "EUR") o código hexadecimal de 40 caracteres para nombres largos +- **issuer**: Dirección de la cuenta emisora + +Dos tokens con el mismo \`currency\` pero diferente \`issuer\` son **tokens completamente diferentes**. + +### TrustLine vs ERC-20 + +| Característica | ERC-20 (Ethereum) | TrustLine (Xahau) | +|---|---|---| +| Crear token | Desplegar contrato Solidity | Simplemente emitir desde tu cuenta | +| Recibir token | Automático (sin permiso) | Requiere crear TrustLine (opt-in) | +| Límite de cantidad | Definido en el contrato | Definido por el receptor en la TrustLine | +| Transferencia | Función del contrato | Transacción nativa Payment | +| Coste | Gas costoso | Fee mínimo (~12 drops) | + +### Reserva de cuenta + +Cada TrustLine consume una **reserva de propietario** (owner reserve) de la cuenta. Esto significa que necesitas tener XAH adicional bloqueado por cada TrustLine que crees.`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Crear una TrustLine hacia un emisor de tokens", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function createTrustLine() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Wallet del receptor (quien quiere recibir el token) + const receiver = Wallet.fromSeed("sEdVxxxTuSeedReceptor", {algorithm: 'secp256k1'}); + + // Crear TrustLine: "confío en el emisor para hasta 1,000,000 USD" + const trustSet = { + TransactionType: "TrustSet", + Account: receiver.address, + LimitAmount: { + currency: "USD", + issuer: "rDireccionDelEmisor", + value: "1000000", // Límite máximo que acepto + }, + }; + + const prepared = await client.autofill(trustSet); + const signed = receiver.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("¡TrustLine creada con éxito!"); + console.log("Ahora puedes recibir USD del emisor"); + } + + await client.disconnect(); +} + +createTrustLine();`, + }, + { + title: { + es: "Emitir (enviar) tokens a una cuenta con TrustLine", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function issueTokens() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Wallet del emisor del token + const issuer = Wallet.fromSeed("sEdVxxxSeedDelEmisor", {algorithm: 'secp256k1'}); + + // Enviar 100 USD al receptor (que ya tiene TrustLine) + const payment = { + TransactionType: "Payment", + Account: issuer.address, + Destination: "rDireccionDelReceptor", + Amount: { + currency: "USD", + issuer: issuer.address, + value: "100", // 100 USD + }, + }; + + const prepared = await client.autofill(payment); + const signed = issuer.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("¡100 USD emitidos con éxito!"); + } + + await client.disconnect(); +} + +issueTokens();`, + }, + ], + slides: [ + { + title: { es: "Modelo de tokens en Xahau", en: "", jp: "" }, + content: { + es: "No necesitas smart contracts para crear tokens\n\n1️⃣ Emisor: Cualquier cuenta\n2️⃣ TrustLine: El receptor opta-in\n3️⃣ Payment: Transferencia nativa\n\nTokens = currency + issuer", + en: "", + jp: "", + }, + visual: "🪙", + }, + { + title: { es: "TrustLine = Opt-in", en: "", jp: "" }, + content: { + es: "El receptor ELIGE recibir un token\n\n• Crea una TrustLine hacia el emisor\n• Define el límite máximo\n• Consume reserva de propietario\n• Protege contra spam de tokens", + en: "", + jp: "", + }, + visual: "🤝", + }, + { + title: { es: "Sistema de reservas", en: "", jp: "" }, + content: { + es: "Cada TrustLine aumenta la reserva de la cuenta\n\n• Reserva base + reserva por objeto\n• Más TrustLines = más XAH bloqueado\n• Los usuarios deben planificar sus TrustLines\n• Eliminar TrustLine (balance 0) libera reserva\n• Impacto directo en el XAH disponible", + en: "", + jp: "", + }, + visual: "💎", + }, + ], + }, + { + id: "m6l2", + title: { + es: "Gestión avanzada de tokens", + en: "", + jp: "", + }, + theory: { + es: `Una vez creado tu token, puedes gestionar diversos aspectos: consultar balances, configurar la cuenta emisora y transferir tokens entre usuarios. + +### Consultar TrustLines y balances + +El comando \`account_lines\` devuelve todas las TrustLines de una cuenta, mostrando cada token que posee o ha emitido, con su balance actual. + +### Configuración del emisor + +La cuenta emisora puede configurar flags importantes: + +- **DefaultRipple**: Permite que los tokens se transfieran entre terceros sin pasar por el emisor. **Es necesario activarlo** si quieres que tus tokens sean libremente transferibles +- **RequireAuth**: Requiere que el emisor autorice cada TrustLine antes de que alguien pueda recibir tokens +- **DisallowXRP**: Señala que la cuenta no quiere recibir XAH (es solo una señal, no lo bloquea técnicamente) + +### Transferencia entre terceros (Rippling) + +Sin el flag **DefaultRipple**, los tokens solo se pueden transferir de vuelta al emisor. Con él activado, los tokens pueden "ripplear" — es decir, transferirse entre cuentas que tienen TrustLine con el mismo emisor. + +### Códigos de moneda especiales + +Para nombres de token de más de 3 caracteres, se usa un código hexadecimal de 40 caracteres: +- Formato: el nombre convertido a hex, rellenado con ceros +- Ejemplo: "XAHAU" → hex → relleno a 40 chars`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Consultar los tokens (TrustLines) de una cuenta", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function getTokenBalances(address) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const response = await client.request({ + command: "account_lines", + account: address, + ledger_index: "validated", + }); + + console.log("=== Tokens de la cuenta ==="); + console.log("Dirección:", address); + + if (response.result.lines.length === 0) { + console.log("No tiene TrustLines (tokens)."); + } + + for (const line of response.result.lines) { + console.log(\`\\nToken: \${line.currency}\`); + console.log(\` Emisor: \${line.account}\`); + console.log(\` Balance: \${line.balance}\`); + console.log(\` Límite: \${line.limit}\`); + } + + await client.disconnect(); +} + +getTokenBalances("rTuDireccionAqui");`, + }, + { + title: { + es: "Configurar cuenta emisora con DefaultRipple", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function configureIssuer() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const issuer = Wallet.fromSeed("sEdVxxxSeedDelEmisor", {algorithm: 'secp256k1'}); + + // Activar DefaultRipple para que los tokens + // se puedan transferir entre terceros + const accountSet = { + TransactionType: "AccountSet", + Account: issuer.address, + SetFlag: 8, // asfDefaultRipple + }; + + const prepared = await client.autofill(accountSet); + const signed = issuer.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("¡DefaultRipple activado!"); + console.log("Tus tokens ahora son libremente transferibles"); + } + + await client.disconnect(); +} + +configureIssuer();`, + }, + ], + slides: [ + { + title: { es: "Consultar tokens", en: "", jp: "" }, + content: { + es: "account_lines → TrustLines de una cuenta\n\n• currency → Código del token\n• account → Emisor\n• balance → Balance actual\n• limit → Límite de confianza", + en: "", + jp: "", + }, + visual: "📊", + }, + { + title: { es: "DefaultRipple", en: "", jp: "" }, + content: { + es: "Flag esencial para emisores de tokens\n\n• Sin DefaultRipple → Solo ida y vuelta al emisor\n• Con DefaultRipple → Transferible entre terceros\n\nActívalo ANTES de emitir tokens", + en: "", + jp: "", + }, + visual: "🔀", + }, + { + title: { es: "Flags importantes para emisores", en: "", jp: "" }, + content: { + es: "RequireAuth (asfRequireAuth):\n• El emisor autoriza cada TrustLine\n• Ideal para tokens con KYC\n\nDefaultRipple (asfDefaultRipple):\n• Permite transferencia entre terceros\n\nConfigurar ANTES de emitir tokens\nUsar AccountSet con SetFlag/ClearFlag", + en: "", + jp: "", + }, + visual: "🚩", + }, + ], + }, + { + id: "m6l3", + title: { + es: "Trading en el DEX nativo", + en: "", + jp: "", + }, + theory: { + es: `Xahau incluye un **exchange descentralizado (DEX) nativo** directamente en el protocolo. No necesitas smart contracts ni plataformas externas para intercambiar tokens — todo se hace con transacciones nativas. + +### OfferCreate: colocar órdenes en el DEX + +La transacción \`OfferCreate\` permite colocar una orden de compra o venta en el libro de órdenes del DEX. Tiene dos campos clave: + +- **TakerPays**: Lo que quieres **recibir** (lo que el "taker" paga) +- **TakerGets**: Lo que estás **dispuesto a dar** (lo que el "taker" obtiene) + +Por ejemplo, si quieres vender 100 USD por XAH, configurarías: +- TakerPays: cantidad de XAH que quieres recibir +- TakerGets: 100 USD (lo que entregas) + +### OfferCancel: cancelar órdenes abiertas + +Si tienes una orden abierta en el DEX que aún no se ha ejecutado, puedes cancelarla con \`OfferCancel\`, especificando el \`OfferSequence\` de la orden original. + +### Cómo funciona el libro de órdenes + +El DEX mantiene un **order book** (libro de órdenes) para cada par de tokens: +- **Bids (ofertas de compra)**: Órdenes que quieren comprar un token +- **Asks (ofertas de venta)**: Órdenes que quieren vender un token + +Cuando una nueva orden coincide con una existente (el precio se cruza), se ejecuta automáticamente — total o parcialmente. + +### Flags especiales de OfferCreate + +- **tfImmediateOrCancel**: La orden se ejecuta inmediatamente contra las órdenes existentes. Lo que no se llene se cancela al instante. No queda nada en el libro de órdenes +- **tfPassive**: La orden solo se ejecuta contra órdenes existentes que tengan un precio igual o mejor. No se coloca en el libro si no hay match inmediato + +### Consultar el libro de órdenes: book_offers + +El comando \`book_offers\` permite ver las órdenes abiertas para un par de tokens. Devuelve las mejores ofertas ordenadas por precio. + +### Auto-bridging a través de XAH + +El DEX de Xahau puede enrutar operaciones multi-salto automáticamente a través de XAH. Si quieres intercambiar USD por EUR y no hay ofertas directas USD/EUR, el DEX puede: +1. Vender USD por XAH +2. Comprar EUR con XAH + +Todo en una sola transacción, de forma transparente. Esto mejora la liquidez del DEX significativamente.`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Consultar el libro de órdenes de un par de tokens (USD/XAH)", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function viewOrderBook() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const issuerAddress = "rDireccionDelEmisorUSD"; + + // Consultar ofertas: ¿quién vende USD a cambio de XAH? + const response = await client.request({ + command: "book_offers", + taker_pays: { + currency: "XAH", + }, + taker_gets: { + currency: "USD", + issuer: issuerAddress, + }, + limit: 10, + }); + + console.log("=== Libro de órdenes: USD → XAH ==="); + console.log(\`Ofertas encontradas: \${response.result.offers.length}\\n\`); + + for (const offer of response.result.offers) { + const getsUSD = offer.TakerGets.value || offer.TakerGets; + const paysXAH = + typeof offer.TakerPays === "string" + ? Number(offer.TakerPays) / 1_000_000 + : offer.TakerPays.value; + + console.log(\`Cuenta: \${offer.Account}\`); + console.log(\` Vende: \${getsUSD} USD\`); + console.log(\` Pide: \${paysXAH} XAH\`); + console.log(\` Sequence: \${offer.Sequence}\\n\`); + } + + await client.disconnect(); +} + +viewOrderBook();`, + }, + { + title: { + es: "Crear una oferta en el DEX (vender 100 USD por XAH)", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet, xahToDrops } = require("xahau"); + +async function createOffer() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const trader = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + const issuerAddress = "rDireccionDelEmisorUSD"; + + // Vender 100 USD a cambio de 500 XAH + const offer = { + TransactionType: "OfferCreate", + Account: trader.address, + // Lo que quiero recibir: 500 XAH + TakerPays: xahToDrops(500), + // Lo que estoy dispuesto a dar: 100 USD + TakerGets: { + currency: "USD", + issuer: issuerAddress, + value: "100", + }, + }; + + const prepared = await client.autofill(offer); + const signed = trader.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("¡Oferta creada en el DEX!"); + console.log(\`Vendiendo 100 USD por 500 XAH (5 XAH/USD)\`); + console.log(\`Sequence de la oferta: \${prepared.Sequence}\`); + } + + await client.disconnect(); +} + +createOffer();`, + }, + { + title: { + es: "Cancelar una oferta existente en el DEX", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function cancelOffer() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const trader = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + + // Cancelar una oferta usando su OfferSequence + const cancel = { + TransactionType: "OfferCancel", + Account: trader.address, + OfferSequence: 12345, // Sequence de la oferta a cancelar + }; + + const prepared = await client.autofill(cancel); + const signed = trader.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("¡Oferta cancelada con éxito!"); + } + + await client.disconnect(); +} + +cancelOffer();`, + }, + ], + slides: [ + { + title: { es: "DEX nativo de Xahau", en: "", jp: "" }, + content: { + es: "Exchange descentralizado integrado en el protocolo\n\n• Sin smart contracts\n• Sin plataformas externas\n• Liquidación atómica\n• Auto-bridging a través de XAH\n\nTodo con transacciones nativas", + en: "", + jp: "", + }, + visual: "📈", + }, + { + title: { es: "OfferCreate: anatomía de una orden", en: "", jp: "" }, + content: { + es: "TakerPays → Lo que quieres RECIBIR\nTakerGets → Lo que estás dispuesto a DAR\n\nFlags especiales:\n• tfImmediateOrCancel → Ejecutar o cancelar\n• tfPassive → Solo match existente\n\nOfferCancel → Cancelar orden abierta", + en: "", + jp: "", + }, + visual: "🔄", + }, + { + title: { es: "Auto-bridging y order book", en: "", jp: "" }, + content: { + es: "El DEX enruta trades multi-salto vía XAH\n\nEjemplo: USD → XAH → EUR\n\n• book_offers → Ver el libro de órdenes\n• Bids y Asks se cruzan automáticamente\n• Ejecución parcial o total\n• Liquidez compartida entre pares", + en: "", + jp: "", + }, + visual: "🌐", + }, + ], + }, + { + id: "m6l4", + title: { + es: "Control avanzado de tokens: Freeze y Clawback", + en: "", + jp: "", + }, + theory: { + es: `Xahau ofrece a los emisores de tokens herramientas avanzadas de control: **Freeze** (congelación), **Clawback** (recuperación forzada), **Transfer fees** (comisiones de transferencia) y **Authorized TrustLines** (líneas de confianza autorizadas). + +### Freeze: congelar líneas de confianza + +El emisor de un token puede congelar TrustLines para impedir que los holders transfieran sus tokens. Hay tres niveles: + +#### Freeze individual +Congela una TrustLine específica entre el emisor y un holder. Se hace con \`TrustSet\` usando el flag \`tfSetFreeze\`. El holder no podrá enviar ni recibir ese token mientras esté congelado. Para descongelar, se usa \`tfClearFreeze\`. + +#### Global Freeze +Congela **todas** las TrustLines de tu token emitido. Se activa con \`AccountSet\` usando \`SetFlag: 7\` (asfGlobalFreeze). Todos los holders quedan congelados simultáneamente. Se puede desactivar con \`ClearFlag: 7\`. + +#### NoFreeze (irreversible) +Al activar \`SetFlag: 6\` (asfNoFreeze) en \`AccountSet\`, el emisor renuncia **permanentemente** a la capacidad de congelar. Esto no se puede deshacer. Es una señal de confianza para los holders. + +### Casos de uso para Freeze +- **Cumplimiento regulatorio**: Congelar fondos ante una orden judicial +- **Brechas de seguridad**: Detener transferencias si una cuenta es comprometida +- **Resolución de disputas**: Congelar temporalmente mientras se investiga + +### Clawback: recuperar tokens de holders + +El **Clawback** permite al emisor reclamar tokens de vuelta desde cualquier holder. Es una herramienta poderosa que debe configurarse **antes** de emitir tokens: + +1. Activar \`asfAllowTrustLineClawback\` (flag 16) con \`AccountSet\` **antes** de crear cualquier TrustLine +2. Una vez activado, usar la transacción \`Clawback\` para reclamar tokens +3. **No se puede combinar** con NoFreeze — si renuncias a congelar, no puedes hacer clawback + +### Transfer fees: comisiones en transferencias + +El emisor puede cobrar un porcentaje en cada transferencia de su token entre terceros: + +- Se configura con el campo \`TransferRate\` en \`AccountSet\` +- El valor es un entero: 1000000000 = 0%, 1001000000 = 0.1%, 1010000000 = 1% +- Solo aplica en transferencias entre terceros, no cuando envías al emisor +- Ejemplo: Con 0.1% de fee, al enviar 100 tokens se cobran 100.1 del remitente + +### Authorized TrustLines: RequireAuth + +El flag \`RequireAuth\` (asfRequireAuth) en la cuenta emisora requiere que el emisor **autorice explícitamente** cada TrustLine antes de que un holder pueda recibir tokens. Útil para tokens que necesitan KYC o verificación previa.`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Congelar la TrustLine de un usuario específico", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function freezeTrustLine() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const issuer = Wallet.fromSeed("sEdVxxxSeedDelEmisor", {algorithm: 'secp256k1'}); + const holderAddress = "rDireccionDelHolder"; + + // Congelar la TrustLine de USD con este holder + const trustSet = { + TransactionType: "TrustSet", + Account: issuer.address, + LimitAmount: { + currency: "USD", + issuer: holderAddress, + value: "0", // No importa el valor para freeze + }, + Flags: 0x00100000, // tfSetFreeze + }; + + const prepared = await client.autofill(trustSet); + const signed = issuer.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log(\`TrustLine de USD congelada para \${holderAddress}\`); + console.log("El holder no puede enviar ni recibir este token"); + } + + // Para descongelar, usar flag tfClearFreeze (0x00200000) + // const unfreeze = { ...trustSet, Flags: 0x00200000 }; + + await client.disconnect(); +} + +freezeTrustLine();`, + }, + { + title: { + es: "Activar Clawback y recuperar tokens de un holder", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function enableClawbackAndReclaim() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const issuer = Wallet.fromSeed("sEdVxxxSeedDelEmisor", {algorithm: 'secp256k1'}); + + // PASO 1: Activar clawback (ANTES de emitir tokens) + const enableClawback = { + TransactionType: "AccountSet", + Account: issuer.address, + SetFlag: 16, // asfAllowTrustLineClawback + }; + + const prep1 = await client.autofill(enableClawback); + const signed1 = issuer.sign(prep1); + const result1 = await client.submitAndWait(signed1.tx_blob); + + console.log("Activar Clawback:", result1.result.meta.TransactionResult); + + if (result1.result.meta.TransactionResult !== "tesSUCCESS") { + console.log("Error: ¿Ya tienes TrustLines creadas?"); + console.log("Clawback solo se puede activar ANTES de emitir tokens."); + await client.disconnect(); + return; + } + + // PASO 2: Recuperar 50 USD de un holder + const clawback = { + TransactionType: "Clawback", + Account: issuer.address, + Amount: { + currency: "USD", + issuer: "rDireccionDelHolder", // De quién reclamar + value: "50", // Cantidad a recuperar + }, + }; + + const prep2 = await client.autofill(clawback); + const signed2 = issuer.sign(prep2); + const result2 = await client.submitAndWait(signed2.tx_blob); + + console.log("Clawback:", result2.result.meta.TransactionResult); + + if (result2.result.meta.TransactionResult === "tesSUCCESS") { + console.log("¡50 USD recuperados del holder!"); + } + + await client.disconnect(); +} + +enableClawbackAndReclaim();`, + }, + ], + slides: [ + { + title: { es: "Freeze: congelación de tokens", en: "", jp: "" }, + content: { + es: "El emisor puede congelar transferencias\n\n• Individual Freeze → Una TrustLine específica\n• Global Freeze → TODAS las TrustLines\n• NoFreeze → Renunciar permanentemente\n\nCasos: regulación, seguridad, disputas", + en: "", + jp: "", + }, + visual: "🧊", + }, + { + title: { es: "Clawback: recuperación forzada", en: "", jp: "" }, + content: { + es: "Reclamar tokens de cualquier holder\n\n1️⃣ Activar asfAllowTrustLineClawback\n2️⃣ Usar transacción Clawback\n\n⚠️ Debe activarse ANTES de emitir tokens\n⚠️ Incompatible con NoFreeze", + en: "", + jp: "", + }, + visual: "🔙", + }, + { + title: { es: "Transfer fees y RequireAuth", en: "", jp: "" }, + content: { + es: "Transfer fees:\n• TransferRate en AccountSet\n• Porcentaje en cada transferencia entre terceros\n• Ejemplo: 0.1% → 1001000000\n\nRequireAuth:\n• El emisor autoriza cada TrustLine\n• Ideal para tokens con KYC", + en: "", + jp: "", + }, + visual: "🔐", + }, + ], + }, + ], +} diff --git a/src/data/modules/m07-nfts.js b/src/data/modules/m07-nfts.js new file mode 100644 index 0000000..7bdac24 --- /dev/null +++ b/src/data/modules/m07-nfts.js @@ -0,0 +1,861 @@ +export default { + id: "m7", + icon: "🎨", + title: { + es: "Creación y uso de NFTs", + en: "", + jp: "", + }, + lessons: [ + { + id: "m7l1", + title: { + es: "URITokens: NFTs nativos en Xahau", + en: "", + jp: "", + }, + theory: { + es: `En Xahau, los NFTs se implementan como **URITokens** — objetos nativos del ledger que representan tokens no fungibles con una URI asociada. + +### ¿Qué es un URIToken? + +Un URIToken es un objeto del ledger que contiene: +- **URI**: Un enlace a los metadatos o contenido del NFT (imagen, JSON, etc.) +- **Digest**: Hash opcional del contenido al que apunta la URI (para verificar integridad) +- **Owner**: La cuenta propietaria actual +- **Issuer**: La cuenta que lo creó originalmente + +### URIToken vs ERC-721 + +| Característica | ERC-721 (Ethereum) | URIToken (Xahau) | +|---|---|---| +| Crear colección | Desplegar contrato Solidity | No necesario | +| Mintear NFT | Función del contrato | Transacción \`URITokenMint\` | +| Transferir | Función del contrato | Transacción \`URITokenBuy\` | +| Metadata | tokenURI en contrato | URI nativa en el objeto | +| Coste | Gas costoso | Fee mínimo (~12 drops) | +| Verificación | Depende del contrato | Digest nativo en el ledger | + +### Transacciones relacionadas con URITokens + +- **URITokenMint**: Crear un nuevo URIToken +- **URITokenBurn**: Destruir un URIToken +- **URITokenCreateSellOffer**: Poner un URIToken a la venta +- **URITokenCancelSellOffer**: Cancelar la oferta de venta +- **URITokenBuy**: Comprar un URIToken que está a la venta + +### Flags de URITokenMint + +- **tfBurnable (1)**: Permite que el emisor pueda quemar el token aunque ya no sea el propietario`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Crear (mintear) un URIToken", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +function toHex(str) { + return Buffer.from(str, "utf8").toString("hex").toUpperCase(); +} + +async function mintURIToken() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const creator = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + + // Crear un URIToken con una URI que apunta a los metadatos + const mint = { + TransactionType: "URITokenMint", + Account: creator.address, + URI: toHex("https://ejemplo.com/nft/metadata.json"), + Flags: 1, // tfBurnable: el emisor puede quemar el token + }; + + const prepared = await client.autofill(mint); + const signed = creator.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("¡URIToken creado con éxito!"); + console.log("Hash tx:", signed.hash); + + // Buscar el URIToken creado en los nodos afectados + const created = result.result.meta.AffectedNodes.find( + (n) => n.CreatedNode?.LedgerEntryType === "URIToken" + ); + if (created) { + console.log("URIToken ID:", created.CreatedNode.LedgerIndex); + } + } + + await client.disconnect(); +} + +mintURIToken();`, + }, + { + title: { + es: "Consultar los URITokens de una cuenta", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function getURITokens(address) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const response = await client.request({ + command: "account_objects", + account: address, + type: "uri_token", + ledger_index: "validated", + }); + + const tokens = response.result.account_objects; + console.log(\`=== URITokens de \${address} ===\`); + console.log(\`Total: \${tokens.length}\\n\`); + + for (const token of tokens) { + const uri = Buffer.from(token.URI, "hex").toString("utf8"); + console.log(\`URIToken ID: \${token.index}\`); + console.log(\` URI: \${uri}\`); + console.log(\` Emisor: \${token.Issuer}\`); + console.log(\` Owner: \${token.Owner}\`); + if (token.Digest) { + console.log(\` Digest: \${token.Digest}\`); + } + if (token.Amount) { + console.log(\` En venta por: \${Number(token.Amount) / 1_000_000} XAH\`); + } + console.log(); + } + + await client.disconnect(); +} + +getURITokens("rTuDireccionAqui");`, + }, + ], + slides: [ + { + title: { es: "URITokens en Xahau", en: "", jp: "" }, + content: { + es: "NFTs nativos del ledger de Xahau\n\n• URI → Enlace a metadatos\n• Digest → Hash de verificación\n• Owner → Propietario actual\n• Issuer → Creador original\n\nSin necesidad de smart contracts", + en: "", + jp: "", + }, + visual: "🎨", + }, + { + title: { es: "Operaciones con URITokens", en: "", jp: "" }, + content: { + es: "• URITokenMint → Crear NFT\n• URITokenBurn → Destruir NFT\n• URITokenCreateSellOffer → Vender\n• URITokenCancelSellOffer → Cancelar venta\n• URITokenBuy → Comprar", + en: "", + jp: "", + }, + visual: "🔧", + }, + { + title: { es: "URIToken vs ERC-721", en: "", jp: "" }, + content: { + es: "URIToken (Xahau):\n• Nativo del ledger, sin contratos\n• Fee mínimo (~12 drops)\n• Digest nativo para verificación\n\nERC-721 (Ethereum):\n• Requiere contrato Solidity\n• Gas costoso y variable\n• Verificación depende del contrato", + en: "", + jp: "", + }, + visual: "⚖️", + }, + ], + }, + { + id: "m7l2", + title: { + es: "Compra-venta de URITokens", + en: "", + jp: "", + }, + theory: { + es: `Xahau incluye un sistema nativo para la compra-venta de URITokens, sin necesidad de marketplaces externos ni smart contracts. + +### Flujo de venta + +1. El propietario crea una **oferta de venta** con \`URITokenCreateSellOffer\`, indicando el precio en XAH +2. Cualquiera puede **comprar** el URIToken con \`URITokenBuy\`, pagando el precio establecido +3. El propietario puede **cancelar** la oferta con \`URITokenCancelSellOffer\` + +### Venta a un destinatario específico + +Puedes crear una oferta de venta dirigida a una cuenta específica usando el campo \`Destination\`. Solo esa cuenta podrá comprar el URIToken. + +### Transferencia gratuita + +Para transferir un URIToken sin coste (regalar), puedes crear una oferta de venta con \`Amount: "0"\` y un \`Destination\` específico. + +### Quemar un URIToken + +El propietario actual siempre puede quemar (destruir) su URIToken con \`URITokenBurn\`. Si el token fue creado con el flag \`tfBurnable\`, el emisor original también puede quemarlo.`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Poner un URIToken a la venta", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet, xahToDrops } = require("xahau"); + +async function sellURIToken() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const owner = Wallet.fromSeed("sEdVxxxSeedDelPropietario", {algorithm: 'secp256k1'}); + + // Crear oferta de venta por 50 XAH + const sellOffer = { + TransactionType: "URITokenCreateSellOffer", + Account: owner.address, + URITokenID: "TU_URITOKEN_ID_AQUI", // ID del URIToken a vender + Amount: xahToDrops(50), // Precio: 50 XAH + }; + + const prepared = await client.autofill(sellOffer); + const signed = owner.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("¡URIToken puesto a la venta por 50 XAH!"); + } + + await client.disconnect(); +} + +sellURIToken();`, + }, + { + title: { + es: "Comprar un URIToken que está a la venta", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet, xahToDrops } = require("xahau"); + +async function buyURIToken() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const buyer = Wallet.fromSeed("sEdVxxxSeedDelComprador", {algorithm: 'secp256k1'}); + + // Comprar el URIToken pagando el precio de venta + const buy = { + TransactionType: "URITokenBuy", + Account: buyer.address, + URITokenID: "TU_URITOKEN_ID_AQUI", // ID del URIToken a comprar + Amount: xahToDrops(50), // Debe coincidir con el precio de venta + }; + + const prepared = await client.autofill(buy); + const signed = buyer.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("¡URIToken comprado con éxito!"); + console.log("El NFT ahora es tuyo."); + } + + await client.disconnect(); +} + +buyURIToken();`, + }, + ], + slides: [ + { + title: { es: "Flujo de venta", en: "", jp: "" }, + content: { + es: "1️⃣ URITokenCreateSellOffer → Poner precio\n2️⃣ URITokenBuy → Comprador paga\n3️⃣ Transferencia automática\n\nTodo nativo, sin marketplace externo", + en: "", + jp: "", + }, + visual: "💰", + }, + { + title: { es: "Transferir y quemar", en: "", jp: "" }, + content: { + es: "Transferir gratis:\n• SellOffer con Amount: 0 + Destination\n\nQuemar (destruir):\n• URITokenBurn por el propietario\n• O por el emisor si tiene flag tfBurnable", + en: "", + jp: "", + }, + visual: "🔥", + }, + { + title: { es: "Quemar URITokens en detalle", en: "", jp: "" }, + content: { + es: "Flag tfBurnable (1) al mintear:\n• Permite al emisor quemar el token\n• Incluso si ya no es propietario\n\nSin tfBurnable:\n• Solo el propietario actual puede quemar\n\nUsos: eliminar errores de minteo,\ncontenido expirado, tokens revocables", + en: "", + jp: "", + }, + visual: "🗑️", + }, + ], + }, + { + id: "m7l3", + title: { + es: "Metadatos y estándares para URITokens", + en: "", + jp: "", + }, + theory: { + es: `Los metadatos son la clave para que un NFT sea útil y verificable. En Xahau, los URITokens usan los campos **URI** y **Digest** para enlazar y verificar el contenido asociado. + +### El campo URI: qué poner en él + +La URI es un enlace que apunta al contenido o metadatos del NFT. Hay varias opciones: + +- **IPFS links** (\`ipfs://QmXxx...\`): Almacenamiento descentralizado. El contenido es inmutable y direccionado por hash. Es la opción **recomendada** para producción +- **HTTPS links** (\`https://mi-servidor.com/metadata/1.json\`): Almacenamiento centralizado. Fácil de implementar pero depende de que el servidor esté disponible +- **Data URIs** (\`data:application/json;base64,...\`): Para datos pequeños incrustados directamente. Útil para metadatos simples sin dependencia externa + +### El campo Digest: verificación de integridad + +El **Digest** es un hash SHA-256 del contenido al que apunta la URI. Permite a cualquiera verificar que el contenido no ha sido alterado desde que se creó el NFT. Se almacena como una cadena hexadecimal de 64 caracteres en el ledger. + +### Estándar de metadatos JSON + +Siguiendo un estándar similar a ERC-721, los metadatos JSON de un URIToken típicamente incluyen: + +\`\`\`json +{ + "name": "Mi NFT #1", + "description": "Descripción del NFT", + "image": "ipfs://QmXxxImageHash...", + "attributes": [ + { "trait_type": "Color", "value": "Azul" }, + { "trait_type": "Rareza", "value": "Legendario" }, + { "trait_type": "Poder", "value": 95 } + ] +} +\`\`\` + +### Opciones de almacenamiento + +| Opción | Ventajas | Desventajas | +|---|---|---| +| **IPFS** | Descentralizado, inmutable, direccionado por hash | Necesita pinning para persistencia | +| **Arweave** | Permanente, pago único | Coste por almacenamiento | +| **Servidor centralizado** | Simple, rápido | Punto único de fallo, mutable | + +### Buenas prácticas + +- **Siempre establece el Digest**: Permite verificar la integridad del contenido en cualquier momento +- **Usa IPFS para producción**: La inmutabilidad y descentralización protegen el valor del NFT +- **Mantén el JSON consistente**: Sigue el estándar de metadatos para compatibilidad con marketplaces y exploradores +- **No pongas datos sensibles en la URI**: Todo es público en el ledger`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Mintear un URIToken con URI de IPFS y Digest", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); +const crypto = require("crypto"); + +function toHex(str) { + return Buffer.from(str, "utf8").toString("hex").toUpperCase(); +} + +async function mintWithIPFSAndDigest() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const creator = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + + // Metadatos JSON del NFT (esto se sube a IPFS) + const metadata = JSON.stringify({ + name: "Xahau NFT #1", + description: "Mi primer NFT en Xahau con IPFS", + image: "ipfs://QmExampleImageHash123456789", + attributes: [ + { trait_type: "Colección", value: "Xahau Academy" }, + { trait_type: "Número", value: 1 }, + ], + }); + + // Calcular el digest SHA-256 del contenido + const digest = crypto + .createHash("sha256") + .update(metadata) + .digest("hex") + .toUpperCase(); + + console.log("Digest SHA-256:", digest); + + // URI apuntando al JSON en IPFS (después de subirlo) + const ipfsURI = "ipfs://QmExampleMetadataHash123456789"; + + const mint = { + TransactionType: "URITokenMint", + Account: creator.address, + URI: toHex(ipfsURI), + Digest: digest, + Flags: 1, // tfBurnable + }; + + const prepared = await client.autofill(mint); + const signed = creator.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("¡URIToken creado con IPFS URI y Digest!"); + + const created = result.result.meta.AffectedNodes.find( + (n) => n.CreatedNode?.LedgerEntryType === "URIToken" + ); + if (created) { + console.log("URIToken ID:", created.CreatedNode.LedgerIndex); + } + } + + await client.disconnect(); +} + +mintWithIPFSAndDigest();`, + }, + { + title: { + es: "Leer un URIToken y verificar su Digest contra el contenido", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); +const crypto = require("crypto"); +const https = require("https"); + +async function verifyURITokenDigest(ownerAddress, uriTokenID) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Obtener los URITokens de la cuenta + const response = await client.request({ + command: "account_objects", + account: ownerAddress, + type: "uri_token", + ledger_index: "validated", + }); + + // Buscar el URIToken específico + const token = response.result.account_objects.find( + (t) => t.index === uriTokenID + ); + + if (!token) { + console.log("URIToken no encontrado"); + await client.disconnect(); + return; + } + + const uri = Buffer.from(token.URI, "hex").toString("utf8"); + const digestOnLedger = token.Digest; + + console.log("=== Verificación de URIToken ==="); + console.log("ID:", token.index); + console.log("URI:", uri); + console.log("Digest en ledger:", digestOnLedger); + + if (!digestOnLedger) { + console.log("\\n⚠ Este URIToken no tiene Digest. No se puede verificar."); + await client.disconnect(); + return; + } + + // Simular la obtención del contenido (en producción, + // descargarías el contenido real de la URI) + const contenidoSimulado = '{"name":"Xahau NFT #1","description":"Ejemplo"}'; + + // Calcular el hash del contenido descargado + const digestCalculado = crypto + .createHash("sha256") + .update(contenidoSimulado) + .digest("hex") + .toUpperCase(); + + console.log("Digest calculado:", digestCalculado); + + if (digestCalculado === digestOnLedger) { + console.log("\\n✓ ¡Verificación exitosa! El contenido es auténtico."); + } else { + console.log("\\n✗ ¡ATENCIÓN! El contenido ha sido modificado."); + console.log("El digest no coincide con el registrado en el ledger."); + } + + await client.disconnect(); +} + +verifyURITokenDigest("rDireccionDelOwner", "URI_TOKEN_ID_AQUI");`, + }, + ], + slides: [ + { + title: { es: "El campo URI: opciones de enlace", en: "", jp: "" }, + content: { + es: "¿A dónde apunta tu NFT?\n\n• ipfs://Qm... → Descentralizado e inmutable\n• https://... → Centralizado pero simple\n• data:... → Datos inline pequeños\n\nRecomendado: IPFS para producción", + en: "", + jp: "", + }, + visual: "🔗", + }, + { + title: { es: "Digest: verificación de integridad", en: "", jp: "" }, + content: { + es: "SHA-256 del contenido → grabado en el ledger\n\n• Cualquiera puede verificar\n• Detecta alteraciones\n• 64 caracteres hexadecimales\n\nSiempre establece el Digest para proteger tu NFT", + en: "", + jp: "", + }, + visual: "🔏", + }, + { + title: { es: "Estándar de metadatos JSON", en: "", jp: "" }, + content: { + es: "Estructura recomendada (similar a ERC-721):\n\n• name → Nombre del NFT\n• description → Descripción\n• image → Enlace a la imagen\n• attributes → Array de propiedades\n\nConsistencia = compatibilidad con exploradores", + en: "", + jp: "", + }, + visual: "📋", + }, + ], + }, + { + id: "m7l4", + title: { + es: "Proyecto práctico: crear una colección de NFTs", + en: "", + jp: "", + }, + theory: { + es: `En esta lección práctica vamos a crear una colección completa de NFTs en Xahau: desde el minteo programático hasta la transferencia, pasando por la consulta y gestión de los tokens. + +### Planificando tu colección de NFTs + +Antes de mintear, define: +- **Nombre de la colección** y tema visual +- **Cantidad de NFTs**: Cuántos tokens vas a crear +- **Metadatos**: Estructura JSON consistente para todos los NFTs +- **Almacenamiento**: Dónde guardar las imágenes y metadatos (IPFS recomendado) +- **Flags**: ¿Quieres que sean quemables por el emisor? (tfBurnable) + +### Minteo programático: crear múltiples URITokens + +Para crear una colección, iteras sobre tus metadatos y ejecutas \`URITokenMint\` para cada uno. Es importante esperar la confirmación de cada transacción antes de enviar la siguiente para evitar problemas de secuencia. + +### Listar todos los URITokens de un emisor + +Usando \`account_objects\` con filtro \`type: "uri_token"\` puedes obtener todos los URITokens de una cuenta. Esto te permite construir un catálogo o galería de tu colección. + +### Construir una galería simple + +Con la lista de URITokens puedes: +1. Obtener cada URI +2. Descargar los metadatos JSON +3. Mostrar nombre, descripción, imagen y atributos + +### Flujo de transferencia + +Para transferir un URIToken a otro usuario: +1. El propietario crea una **oferta de venta** (\`URITokenCreateSellOffer\`) — puede ser con precio 0 para regalo, o con \`Destination\` para venta privada +2. El comprador ejecuta \`URITokenBuy\` pagando el monto establecido +3. La propiedad se transfiere automáticamente + +### Quemar URITokens no deseados + +Si necesitas eliminar URITokens de tu colección (errores de minteo, tokens sobrantes), usa \`URITokenBurn\`. El propietario siempre puede quemar sus tokens. Si se usó \`tfBurnable\` al mintear, el emisor original también puede hacerlo.`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Mintear un lote de 3 URITokens con diferentes metadatos", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); +const crypto = require("crypto"); + +function toHex(str) { + return Buffer.from(str, "utf8").toString("hex").toUpperCase(); +} + +async function mintCollection() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const creator = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + + // Definir los metadatos de cada NFT de la colección + const collection = [ + { + name: "Xahau Warrior #1", + description: "Guerrero legendario de la colección Xahau", + image: "ipfs://QmImageHash1", + attributes: [ + { trait_type: "Clase", value: "Guerrero" }, + { trait_type: "Poder", value: 85 }, + ], + }, + { + name: "Xahau Mage #2", + description: "Mago ancestral de la colección Xahau", + image: "ipfs://QmImageHash2", + attributes: [ + { trait_type: "Clase", value: "Mago" }, + { trait_type: "Poder", value: 92 }, + ], + }, + { + name: "Xahau Healer #3", + description: "Sanador sagrado de la colección Xahau", + image: "ipfs://QmImageHash3", + attributes: [ + { trait_type: "Clase", value: "Sanador" }, + { trait_type: "Poder", value: 78 }, + ], + }, + ]; + + const mintedTokens = []; + + for (let i = 0; i < collection.length; i++) { + const metadata = JSON.stringify(collection[i]); + + // Calcular digest del contenido + const digest = crypto + .createHash("sha256") + .update(metadata) + .digest("hex") + .toUpperCase(); + + // En producción, subirías metadata a IPFS y usarías el CID real + const uri = \`ipfs://QmCollectionMetadata\${i + 1}\`; + + const mint = { + TransactionType: "URITokenMint", + Account: creator.address, + URI: toHex(uri), + Digest: digest, + Flags: 1, // tfBurnable + }; + + const prepared = await client.autofill(mint); + const signed = creator.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + const txResult = result.result.meta.TransactionResult; + console.log(\`[\${i + 1}/\${collection.length}] \${collection[i].name}: \${txResult}\`); + + if (txResult === "tesSUCCESS") { + const created = result.result.meta.AffectedNodes.find( + (n) => n.CreatedNode?.LedgerEntryType === "URIToken" + ); + if (created) { + mintedTokens.push({ + id: created.CreatedNode.LedgerIndex, + name: collection[i].name, + }); + } + } + } + + console.log("\\n=== Colección minteada ==="); + for (const token of mintedTokens) { + console.log(\` \${token.name} → ID: \${token.id}\`); + } + + await client.disconnect(); +} + +mintCollection();`, + }, + { + title: { + es: "Listar todos los URITokens de una cuenta con sus metadatos", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function listCollectionWithMetadata(address) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const response = await client.request({ + command: "account_objects", + account: address, + type: "uri_token", + ledger_index: "validated", + }); + + const tokens = response.result.account_objects; + console.log(\`=== Colección de NFTs de \${address} ===\`); + console.log(\`Total: \${tokens.length} URITokens\\n\`); + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + const uri = Buffer.from(token.URI, "hex").toString("utf8"); + + console.log(\`--- NFT #\${i + 1} ---\`); + console.log(\` ID: \${token.index}\`); + console.log(\` URI: \${uri}\`); + console.log(\` Emisor: \${token.Issuer}\`); + + if (token.Digest) { + console.log(\` Digest: \${token.Digest}\`); + } + + if (token.Amount) { + const precio = Number(token.Amount) / 1_000_000; + console.log(\` Estado: En venta por \${precio} XAH\`); + } else { + console.log(\` Estado: No está a la venta\`); + } + + // En producción, aquí descargarías el JSON de la URI + // y mostrarías name, description, image, attributes + // const metadata = await fetch(convertIPFStoHTTP(uri)); + // console.log(" Nombre:", metadata.name); + + console.log(); + } + + await client.disconnect(); +} + +listCollectionWithMetadata("rTuDireccionAqui");`, + }, + { + title: { + es: "Transferir un URIToken a otra cuenta (venta + compra)", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet, xahToDrops } = require("xahau"); + +async function transferURIToken() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const seller = Wallet.fromSeed("sEdVxxxSeedDelVendedor", {algorithm: 'secp256k1'}); + const buyer = Wallet.fromSeed("sEdVxxxSeedDelComprador", {algorithm: 'secp256k1'}); + + const uriTokenID = "TU_URITOKEN_ID_AQUI"; + const precioXAH = 25; // Precio de venta: 25 XAH + + // PASO 1: El vendedor crea la oferta de venta + console.log("Paso 1: Creando oferta de venta..."); + const sellOffer = { + TransactionType: "URITokenCreateSellOffer", + Account: seller.address, + URITokenID: uriTokenID, + Amount: xahToDrops(precioXAH), + Destination: buyer.address, // Venta dirigida al comprador + }; + + const prepSell = await client.autofill(sellOffer); + const signedSell = seller.sign(prepSell); + const resultSell = await client.submitAndWait(signedSell.tx_blob); + + console.log("Oferta de venta:", resultSell.result.meta.TransactionResult); + + if (resultSell.result.meta.TransactionResult !== "tesSUCCESS") { + console.log("Error al crear la oferta de venta"); + await client.disconnect(); + return; + } + + // PASO 2: El comprador acepta y compra el URIToken + console.log("\\nPaso 2: Comprando el URIToken..."); + const buyTx = { + TransactionType: "URITokenBuy", + Account: buyer.address, + URITokenID: uriTokenID, + Amount: xahToDrops(precioXAH), + }; + + const prepBuy = await client.autofill(buyTx); + const signedBuy = buyer.sign(prepBuy); + const resultBuy = await client.submitAndWait(signedBuy.tx_blob); + + console.log("Compra:", resultBuy.result.meta.TransactionResult); + + if (resultBuy.result.meta.TransactionResult === "tesSUCCESS") { + console.log(\`\\n¡Transferencia completada!\`); + console.log(\`El URIToken ahora pertenece a \${buyer.address}\`); + console.log(\`El vendedor recibió \${precioXAH} XAH\`); + } + + await client.disconnect(); +} + +transferURIToken();`, + }, + ], + slides: [ + { + title: { es: "Planificar tu colección de NFTs", en: "", jp: "" }, + content: { + es: "Antes de mintear, define:\n\n• Nombre y tema de la colección\n• Cantidad de NFTs a crear\n• Estructura de metadatos JSON\n• Almacenamiento: IPFS recomendado\n• Flags: tfBurnable si necesitas control", + en: "", + jp: "", + }, + visual: "📝", + }, + { + title: { es: "Minteo y gestión programática", en: "", jp: "" }, + content: { + es: "Crear colección en un loop:\n\n1️⃣ Preparar metadatos para cada NFT\n2️⃣ Calcular Digest SHA-256\n3️⃣ URITokenMint por cada uno\n4️⃣ Esperar confirmación entre cada mint\n\naccount_objects → Listar toda la colección", + en: "", + jp: "", + }, + visual: "⚙️", + }, + { + title: { es: "Transferencia y ciclo de vida", en: "", jp: "" }, + content: { + es: "Flujo de transferencia:\n\n1️⃣ Vendedor → URITokenCreateSellOffer\n2️⃣ Comprador → URITokenBuy\n3️⃣ Propiedad transferida automáticamente\n\nQuemar: URITokenBurn para eliminar\nGratis: Amount 0 + Destination", + en: "", + jp: "", + }, + visual: "🔄", + }, + ], + }, + ], +} diff --git a/src/data/modules/m08-smart-contracts.js b/src/data/modules/m08-smart-contracts.js new file mode 100644 index 0000000..b5416ba --- /dev/null +++ b/src/data/modules/m08-smart-contracts.js @@ -0,0 +1,1037 @@ +export default { + id: "m8", + icon: "🪝", + title: { + es: "Introducción a smart contracts en entornos No-EVM", + en: "", + jp: "", + }, + lessons: [ + { + id: "m8l1", + title: { + es: "¿Qué son los Hooks?", + en: "", + jp: "", + }, + theory: { + es: `Los **Hooks** son el sistema de smart contracts nativo de Xahau. A diferencia de Solidity en Ethereum, los Hooks se escriben en **C** y se compilan a **WebAssembly (WASM)**. + +### Hooks vs Smart Contracts EVM + +| Característica | Smart Contracts EVM | Hooks (Xahau) | +|---|---|---| +| Lenguaje | Solidity / Vyper | C | +| Compilación | Bytecode EVM | WebAssembly (WASM) | +| Ejecución | En la EVM | Directamente en el nodo | +| Modelo | Se invocan activamente | Se ejecutan reactivamente | +| Gas/Fees | Gas variable | Fees fijos y bajos | +| Almacenamiento | Storage ilimitado | Estado con namespace | +| Despliegue | Transacción de creación | Transacción SetHook | + +### Modelo reactivo + +La diferencia más importante es el **modelo de ejecución**: + +- En Ethereum, **tú llamas** al smart contract enviando una transacción al contrato +- En Xahau, los Hooks se **ejecutan automáticamente** cuando una transacción pasa por una cuenta que tiene un Hook instalado + +Los Hooks son como **filtros** o **interceptores** que reaccionan a las transacciones. Pueden: +- **Aceptar** la transacción (\`accept()\`) +- **Rechazar** la transacción (\`rollback()\`) +- **Emitir** nuevas transacciones (\`emit()\`) +- **Leer y escribir** estado persistente (\`state()\`, \`state_set()\`) + +### Funciones obligatorias + +Todo Hook debe implementar dos funciones: +- \`hook(uint32_t reserved)\` — Se ejecuta cuando una transacción llega a la cuenta. Es obligatoria +- \`cbak(uint32_t reserved)\` — Se ejecuta como callback de transacciones emitidas por el Hook. Es obligatoria pero puede estar vacía + +### Guard (\`_g\`) + +Cada Hook debe incluir una llamada a \`_g(id, maxiter)\` para evitar bucles infinitos. El guard define el máximo de iteraciones que puede ejecutar el Hook.`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Hook mínimo — Acepta todas las transacciones", + en: "", + jp: "", + }, + language: "c", + code: `#include "hookapi.h" + +/** + * Hook: accept_all.c + * El Hook más simple posible. + * Acepta todas las transacciones sin condiciones. + */ + +int64_t hook(uint32_t reserved) { + // Aceptar la transacción con un mensaje + accept(SBUF("accept_all: Transacción aceptada."), __LINE__); + + // Guard: nunca se llega aquí, pero es obligatorio + _g(1, 1); + return 0; +} + +int64_t cbak(uint32_t reserved) { + // Callback vacío (obligatorio) + return 0; +}`, + }, + { + title: { + es: "Hook que rechaza pagos menores a un mínimo", + en: "", + jp: "", + }, + language: "c", + code: `#include "hookapi.h" + +/** + * Hook: min_payment.c + * Rechaza pagos de XAH menores a 10 XAH. + * Acepta todas las demás transacciones. + */ + +int64_t hook(uint32_t reserved) { + // Obtener el tipo de transacción + int64_t tt = otxn_type(); + + // Si no es un pago (tipo 0), aceptar + if (tt != 0) { + accept(SBUF("min_payment: No es un pago."), __LINE__); + } + + // Obtener la cantidad del pago + unsigned char amount_buf[48]; + int64_t amount_len = otxn_field(SBUF(amount_buf), sfAmount); + + // Si no es XAH nativo (8 bytes), aceptar + if (amount_len != 8) { + accept(SBUF("min_payment: Pago no-XAH."), __LINE__); + } + + // Convertir a drops y comparar + int64_t drops = AMOUNT_TO_DROPS(amount_buf); + int64_t min_drops = 10000000; // 10 XAH = 10,000,000 drops + + if (drops < min_drops) { + // Rechazar: el pago es muy pequeño + rollback( + SBUF("min_payment: Pago rechazado. Mínimo 10 XAH."), + __LINE__ + ); + } + + // Aceptar: el pago cumple el mínimo + accept(SBUF("min_payment: Pago aceptado."), __LINE__); + + _g(1, 1); + return 0; +} + +int64_t cbak(uint32_t reserved) { + return 0; +}`, + }, + ], + slides: [ + { + title: { es: "¿Qué son los Hooks?", en: "", jp: "" }, + content: { + es: "Smart contracts nativos de Xahau\n\n• Escritos en C\n• Compilados a WebAssembly\n• Se ejecutan reactivamente\n• Filtran/interceptan transacciones", + en: "", + jp: "", + }, + visual: "🪝", + }, + { + title: { es: "Modelo reactivo", en: "", jp: "" }, + content: { + es: "EVM: Tú llamas al contrato\nHooks: Se ejecutan automáticamente\n\n• accept() → Aceptar transacción\n• rollback() → Rechazar transacción\n• emit() → Emitir nueva transacción\n• state() → Leer/escribir estado", + en: "", + jp: "", + }, + visual: "⚡", + }, + { + title: { es: "Estructura de un Hook", en: "", jp: "" }, + content: { + es: "Dos funciones obligatorias:\n\n🪝 hook() → Punto de entrada principal\n🔄 cbak() → Callback de emisiones\n🛡️ _g() → Guard anti-bucles infinitos", + en: "", + jp: "", + }, + visual: "📐", + }, + ], + }, + { + id: "m8l2", + title: { + es: "Despliegue de un Hook en Xahau", + en: "", + jp: "", + }, + theory: { + es: `Una vez que tienes tu Hook escrito en C, necesitas **compilarlo a WebAssembly** y **desplegarlo** en tu cuenta de Xahau mediante una transacción \`SetHook\`. + +### Opciones de desarrollo + +**1. Hooks Builder (Online)** +La forma más rápida de empezar. [hooks-builder.xrpl.org](https://hooks-builder.xrpl.org) te permite escribir, compilar y desplegar Hooks desde el navegador. + +**2. Desarrollo local** +Para desarrollo local necesitas: +- **Compilador C** (clang) +- **wasm-cc**: Compilador de C a WebAssembly para Hooks +- **Node.js**: Para scripts de despliegue con \`xahau\` + +### Transacción SetHook + +La transacción \`SetHook\` instala, actualiza o elimina Hooks de tu cuenta: + +- **CreateCode**: El binario WASM del Hook (en hexadecimal) +- **HookOn**: Máscara de bits que define qué tipos de transacción activan el Hook +- **HookNamespace**: Espacio de nombres para el estado del Hook (32 bytes hex) +- **HookApiVersion**: Versión de la API de Hooks (actualmente 0) +- **HookParameters**: Parámetros de configuración opcionales + +### HookOn — Filtro de transacciones + +El campo \`HookOn\` es una máscara de bits invertida que controla en qué tipos de transacción se activa el Hook: +- \`"0000000000000000"\` → Se activa en TODOS los tipos de transacción +- Puedes configurar bits específicos para activar o desactivar tipos + +### Límites + +- Máximo **10 Hooks** por cuenta +- Cada Hook tiene su propio **namespace** para estado +- El WASM tiene un tamaño máximo permitido`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Instalar dependencias para desarrollo de Hooks", + en: "", + jp: "", + }, + language: "bash", + code: `# Crear proyecto +mkdir mi-primer-hook +cd mi-primer-hook +npm init -y + +# Instalar la librería xahau +npm install xahau`, + }, + { + title: { + es: "Desplegar un Hook con xahau.js", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); +const fs = require("fs"); + +async function deployHook() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Tu cuenta de testnet + const account = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + + // Leer el WASM compilado del Hook + const wasmBytes = fs.readFileSync("./build/accept_all.wasm"); + const hookBinary = wasmBytes.toString("hex").toUpperCase(); + + // Construir la transacción SetHook + const setHook = { + TransactionType: "SetHook", + Account: account.address, + Hooks: [ + { + Hook: { + CreateCode: hookBinary, + HookOn: "0000000000000000", // Todos los tipos de tx + HookNamespace: "0".repeat(64), // Namespace por defecto + HookApiVersion: 0, + Flags: 1, + }, + }, + ], + }; + + const prepared = await client.autofill(setHook); + const signed = account.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("¡Hook desplegado con éxito!"); + console.log("Tu cuenta ahora ejecuta el Hook"); + console.log("en cada transacción entrante/saliente."); + } + + await client.disconnect(); +} + +deployHook();`, + }, + { + title: { + es: "Verificar los Hooks instalados en una cuenta", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function checkHooks(address) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const response = await client.request({ + command: "account_objects", + account: address, + type: "hook", + ledger_index: "validated", + }); + + const hooks = response.result.account_objects; + console.log(\`=== Hooks de \${address} ===\`); + console.log(\`Total instalados: \${hooks.length}\\n\`); + + for (let i = 0; i < hooks.length; i++) { + const hook = hooks[i]; + console.log(\`Hook #\${i + 1}:\`); + console.log(\` HookHash: \${hook.HookHash}\`); + console.log(\` HookOn: \${hook.HookOn}\`); + if (hook.HookNamespace) { + console.log(\` Namespace: \${hook.HookNamespace}\`); + } + console.log(); + } + + await client.disconnect(); +} + +checkHooks("rTuDireccionAqui");`, + }, + ], + slides: [ + { + title: { es: "SetHook", en: "", jp: "" }, + content: { + es: "Transacción para gestionar Hooks\n\n• CreateCode → WASM del Hook\n• HookOn → Filtro de transacciones\n• HookNamespace → Estado aislado\n• Hasta 10 Hooks por cuenta", + en: "", + jp: "", + }, + visual: "⚙️", + }, + { + title: { es: "Flujo de desarrollo", en: "", jp: "" }, + content: { + es: "1️⃣ Escribir Hook en C\n2️⃣ Compilar a WebAssembly\n3️⃣ SetHook → Desplegar en cuenta\n4️⃣ ¡Hook activo!\n\n🌐 Online: hooks-builder.xrpl.org\n💻 Local: clang + wasm-cc + xahau.js", + en: "", + jp: "", + }, + visual: "🚀", + }, + { + title: { es: "HookOn y límites de despliegue", en: "", jp: "" }, + content: { + es: "HookOn — máscara de bits invertida:\n• \"0000000000000000\" → todos los tipos de tx\n• Configura bits para filtrar tipos específicos\n\nLímites de despliegue:\n• Máximo 10 Hooks por cuenta\n• Tamaño máximo de WASM limitado\n• Cada Hook tiene su propio namespace", + en: "", + jp: "", + }, + visual: "🎯", + }, + ], + }, + { + id: "m8l3", + title: { + es: "Estado persistente en Hooks", + en: "", + jp: "", + }, + theory: { + es: `Los Hooks pueden almacenar **datos persistentes** entre ejecuciones usando el sistema de estado (\`state\`). Esto permite que un Hook recuerde información entre transacciones. + +### Funciones de estado + +- \`state()\` — Lee un valor del estado usando una clave +- \`state_set()\` — Escribe un valor en el estado para una clave +- \`state_foreign()\` — Lee el estado de un Hook instalado en **otra cuenta** + +### Estructura del estado + +El estado se organiza como pares **clave-valor**: +- **Clave**: 32 bytes (256 bits). Si tu clave es más corta, se rellena con ceros +- **Valor**: hasta 256 bytes por entrada +- Cada entrada de estado se identifica por su clave dentro de un **namespace** + +### HookNamespace — Aislamiento de estado + +Cada Hook tiene un **HookNamespace** (32 bytes hex) que aísla su estado: + +- Dos Hooks diferentes en la **misma cuenta** tienen estados separados si usan namespaces distintos +- Esto evita colisiones: un Hook no puede accidentalmente sobrescribir el estado de otro +- El namespace se define al instalar el Hook con \`SetHook\` + +### state_foreign() — Leer estado ajeno + +Con \`state_foreign()\` puedes leer el estado de un Hook en otra cuenta: +- Necesitas conocer la **cuenta**, el **namespace** y la **clave** +- Es de solo lectura: no puedes modificar el estado de otro Hook +- Útil para Hooks que necesitan consultar datos de otros Hooks + +### Usos prácticos del estado + +- **Contadores**: contar transacciones procesadas, pagos recibidos, etc. +- **Listas blancas/negras**: almacenar direcciones permitidas o bloqueadas +- **Configuración**: guardar parámetros que el Hook consulta en cada ejecución +- **Tracking**: registrar la última transacción procesada, timestamps, etc. +- **Acumuladores**: sumar montos, promediar valores, llevar balances internos`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Hook que cuenta pagos procesados", + en: "", + jp: "", + }, + language: "c", + code: `#include "hookapi.h" + +/** + * Hook: payment_counter.c + * Cuenta cuántos pagos ha procesado la cuenta. + * Almacena el contador en el estado del Hook. + */ + +int64_t hook(uint32_t reserved) { + _g(1, 1); + + // Solo contar pagos (tipo 0) + int64_t tt = otxn_type(); + if (tt != 0) { + accept(SBUF("payment_counter: No es un pago."), __LINE__); + } + + // Clave de estado para el contador (32 bytes, rellena con ceros) + uint8_t state_key[32] = { 0 }; + state_key[0] = 'C'; // 'C' de Counter + + // Leer el contador actual del estado + int64_t counter = 0; + uint8_t counter_buf[8] = { 0 }; + int64_t bytes_read = state(SBUF(counter_buf), SBUF(state_key)); + + if (bytes_read == 8) { + // El contador ya existe, leer su valor + counter = *((int64_t*)counter_buf); + } + + // Incrementar el contador + counter++; + + // Escribir el nuevo valor en el estado + *((int64_t*)counter_buf) = counter; + int64_t result = state_set(SBUF(counter_buf), SBUF(state_key)); + + if (result < 0) { + rollback(SBUF("payment_counter: Error al guardar estado."), __LINE__); + } + + // Aceptar la transacción + accept(SBUF("payment_counter: Pago contado."), __LINE__); + return 0; +} + +int64_t cbak(uint32_t reserved) { + return 0; +}`, + }, + { + title: { + es: "Hook con lista blanca de remitentes", + en: "", + jp: "", + }, + language: "c", + code: `#include "hookapi.h" + +/** + * Hook: whitelist.c + * Solo acepta pagos de direcciones que están en la + * lista blanca almacenada en el estado del Hook. + * Las direcciones se agregan al estado externamente + * (por ejemplo, con un script de administración). + */ + +int64_t hook(uint32_t reserved) { + _g(1, 1); + + // Solo filtrar pagos (tipo 0) + int64_t tt = otxn_type(); + if (tt != 0) { + accept(SBUF("whitelist: No es un pago, aceptado."), __LINE__); + } + + // Obtener la cuenta de origen de la transacción (20 bytes) + uint8_t sender_acc[20]; + int64_t sender_len = otxn_field(SBUF(sender_acc), sfAccount); + + if (sender_len != 20) { + rollback(SBUF("whitelist: No se pudo leer el remitente."), __LINE__); + } + + // Usar la cuenta del remitente como clave de estado + // La clave es de 32 bytes; los primeros 20 son la cuenta + uint8_t state_key[32] = { 0 }; + COPY_20(state_key, sender_acc); + + // Intentar leer el estado para esta clave + uint8_t is_allowed[1] = { 0 }; + int64_t bytes_read = state(SBUF(is_allowed), SBUF(state_key)); + + // Si existe una entrada y su valor es 1, está en la whitelist + if (bytes_read == 1 && is_allowed[0] == 1) { + accept(SBUF("whitelist: Remitente autorizado."), __LINE__); + } + + // No está en la whitelist: rechazar + rollback( + SBUF("whitelist: Remitente no autorizado. Pago rechazado."), + __LINE__ + ); + + return 0; +} + +int64_t cbak(uint32_t reserved) { + return 0; +}`, + }, + ], + slides: [ + { + title: { es: "Estado persistente", en: "", jp: "" }, + content: { + es: "Los Hooks recuerdan datos entre ejecuciones\n\n• state() → Leer un valor por clave\n• state_set() → Escribir un valor\n• state_foreign() → Leer estado de otra cuenta\n\nPares clave-valor: clave 32 bytes, valor hasta 256 bytes", + en: "", + jp: "", + }, + visual: "💾", + }, + { + title: { es: "HookNamespace", en: "", jp: "" }, + content: { + es: "Aislamiento de estado entre Hooks\n\n• Cada Hook tiene su propio namespace\n• Evita colisiones entre Hooks en la misma cuenta\n• Se define al instalar con SetHook\n• 32 bytes hexadecimales", + en: "", + jp: "", + }, + visual: "🔒", + }, + { + title: { es: "Usos prácticos del estado", en: "", jp: "" }, + content: { + es: "• Contadores de transacciones\n• Listas blancas / negras\n• Configuración dinámica\n• Tracking y registros\n• Acumuladores y balances internos", + en: "", + jp: "", + }, + visual: "📋", + }, + ], + }, + { + id: "m8l4", + title: { + es: "Emitir transacciones desde un Hook", + en: "", + jp: "", + }, + theory: { + es: `Una de las capacidades más poderosas de los Hooks es la posibilidad de **emitir transacciones nuevas** de forma autónoma. Cuando un Hook emite una transacción, esta se ejecuta como si la cuenta del Hook la hubiera enviado. + +### La función emit() + +La función \`emit()\` permite que un Hook cree y envíe una **transacción emitida (etxn)**. Estas transacciones: +- Son creadas por el Hook, no por un usuario +- Se ejecutan de forma autónoma en el ledger +- Pueden ser pagos, ofertas, o cualquier tipo de transacción soportado + +### Reservar espacio con etxn_reserve() + +Antes de emitir, debes **reservar** cuántas transacciones vas a emitir en esta ejecución: + +\`\`\` +etxn_reserve(1); // Reservar espacio para 1 emisión +\`\`\` + +Esto es obligatorio. Si intentas emitir sin reservar, el Hook fallará. + +### Paso a paso para emitir + +1. **\`etxn_reserve(N)\`** — Reservar espacio para N emisiones +2. **Construir la transacción** — Llenar un buffer con los campos de la transacción serializada +3. **\`etxn_details()\`** — Preparar los detalles de emisión (genera el hash de emisión) +4. **\`emit()\`** — Enviar la transacción al ledger + +### La función cbak() + +Cuando una transacción emitida se **completa** (con éxito o fallo), Xahau llama a la función \`cbak()\` del Hook que la emitió: + +- \`cbak()\` recibe información sobre el resultado de la emisión +- Puedes usar \`cbak()\` para actualizar estado, registrar resultados, o tomar acciones adicionales +- Si no necesitas hacer nada, \`cbak()\` puede simplemente retornar 0 + +### Casos de uso + +- **Auto-forwarding**: reenviar automáticamente un porcentaje de cada pago recibido +- **Splitting**: dividir un pago entrante entre varias cuentas +- **Refunds**: devolver pagos que no cumplen ciertas condiciones +- **Acciones programadas**: emitir transacciones basadas en condiciones de estado + +### Limitaciones + +- Existe un **máximo de emisiones por ejecución** del Hook +- Las transacciones emitidas tienen **requisitos de fees** propios +- No puedes emitir transacciones infinitas (el guard \`_g\` lo previene) +- Las emisiones aumentan la carga computacional del Hook`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Hook que reenvía el 10% de cada pago recibido", + en: "", + jp: "", + }, + language: "c", + code: `#include "hookapi.h" + +/** + * Hook: forward_ten_percent.c + * Cuando la cuenta recibe un pago en XAH, + * reenvía automáticamente el 10% a una dirección fija. + */ + +// Dirección destino del 10% (account ID en hex, 20 bytes) +// Reemplazar con la dirección real deseada +#define FORWARD_TO "rDestinationAddressInHexHere0000" + +int64_t hook(uint32_t reserved) { + _g(1, 1); + + // Solo procesar pagos (tipo 0) + int64_t tt = otxn_type(); + if (tt != 0) { + accept(SBUF("forward10: No es un pago."), __LINE__); + } + + // Verificar que somos el destino (pago entrante) + uint8_t hook_acc[20]; + hook_account(SBUF(hook_acc)); + + uint8_t dest_acc[20]; + int64_t dest_len = otxn_field(SBUF(dest_acc), sfDestination); + + int is_incoming = 0; + for (int i = 0; GUARD(20), i < 20; i++) { + if (hook_acc[i] != dest_acc[i]) { + is_incoming = 0; + break; + } + if (i == 19) is_incoming = 1; + } + + if (!is_incoming) { + accept(SBUF("forward10: Pago saliente, ignorar."), __LINE__); + } + + // Obtener el monto del pago + unsigned char amount_buf[48]; + int64_t amount_len = otxn_field(SBUF(amount_buf), sfAmount); + + // Solo XAH nativo (8 bytes) + if (amount_len != 8) { + accept(SBUF("forward10: No es XAH nativo."), __LINE__); + } + + int64_t drops = AMOUNT_TO_DROPS(amount_buf); + + // Calcular el 10% + int64_t forward_drops = drops / 10; + + if (forward_drops < 1) { + accept(SBUF("forward10: Monto muy pequeño."), __LINE__); + } + + // Reservar espacio para 1 emisión + etxn_reserve(1); + + // Preparar la transacción emitida + uint8_t tx_buf[PREPARE_PAYMENT_SIMPLE_SIZE]; + PREPARE_PAYMENT_SIMPLE( + tx_buf, + forward_drops, + FORWARD_TO, + 0, 0 + ); + + // Emitir la transacción + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), SBUF(tx_buf)); + + if (emit_result < 0) { + rollback(SBUF("forward10: Error al emitir."), __LINE__); + } + + accept(SBUF("forward10: 10% reenviado."), __LINE__); + return 0; +} + +int64_t cbak(uint32_t reserved) { + return 0; +}`, + }, + { + title: { + es: "cbak() que registra el resultado de una emisión", + en: "", + jp: "", + }, + language: "c", + code: `#include "hookapi.h" + +/** + * Hook: cbak_logger.c + * Ejemplo de cbak() que registra si la transacción + * emitida fue exitosa o falló, guardando el resultado + * en el estado del Hook. + */ + +int64_t hook(uint32_t reserved) { + _g(1, 1); + // ... lógica del hook y emit() aquí ... + accept(SBUF("cbak_logger: Hook ejecutado."), __LINE__); + return 0; +} + +int64_t cbak(uint32_t reserved) { + _g(1, 1); + + // Clave de estado para el último resultado de emisión + uint8_t state_key[32] = { 0 }; + state_key[0] = 'E'; // 'E' de Emission result + + // Obtener el hash de la transacción emitida + uint8_t emit_hash[32]; + int64_t hash_len = otxn_field(SBUF(emit_hash), sfTransactionHash); + + // Obtener el resultado de la transacción + uint8_t meta[512]; + int64_t meta_len = otxn_field(SBUF(meta), sfTransactionResult); + + // Guardar el resultado en el estado + // 1 = éxito, 0 = fallo + uint8_t result_val[1]; + result_val[0] = (meta_len >= 0) ? 1 : 0; + state_set(SBUF(result_val), SBUF(state_key)); + + return 0; +}`, + }, + ], + slides: [ + { + title: { es: "Emitir transacciones", en: "", jp: "" }, + content: { + es: "Los Hooks pueden crear transacciones nuevas\n\n• emit() → Enviar una transacción al ledger\n• etxn_reserve() → Reservar espacio (obligatorio)\n• Las emisiones son autónomas\n• Se ejecutan como si la cuenta las enviara", + en: "", + jp: "", + }, + visual: "📤", + }, + { + title: { es: "Paso a paso para emitir", en: "", jp: "" }, + content: { + es: "1. etxn_reserve(N) → Reservar para N emisiones\n2. Construir la transacción en un buffer\n3. etxn_details() → Preparar detalles\n4. emit() → Enviar al ledger\n\ncbak() se llama cuando la emisión completa", + en: "", + jp: "", + }, + visual: "📝", + }, + { + title: { es: "Casos de uso de emisiones", en: "", jp: "" }, + content: { + es: "• Auto-forwarding de pagos\n• Splitting entre varias cuentas\n• Refunds automáticos\n• Acciones programadas\n\nLimitaciones: máximo de emisiones por ejecución y fees propios", + en: "", + jp: "", + }, + visual: "🔀", + }, + ], + }, + { + id: "m8l5", + title: { + es: "Parámetros, namespaces y gestión de Hooks", + en: "", + jp: "", + }, + theory: { + es: `Los Hooks ofrecen varias herramientas para configuración, organización y gestión avanzada. En esta lección veremos **HookParameters**, **HookNamespace** en profundidad, y cómo gestionar múltiples Hooks en una cuenta. + +### HookParameters — Configuración sin recompilar + +Los **HookParameters** permiten pasar configuración a un Hook **sin necesidad de recompilarlo**. Se definen al instalar el Hook con \`SetHook\`: + +- Cada parámetro tiene un **HookParameterName** (clave) y un **HookParameterValue** (valor) +- Ambos son cadenas hexadecimales +- Dentro del Hook, se leen con \`hook_param()\` + +**Casos de uso de parámetros**: +- Umbrales configurables (monto mínimo, máximo) +- Direcciones de destino configurables +- Feature flags (activar/desactivar funcionalidades) +- Cualquier valor que quieras cambiar sin recompilar el WASM + +### hook_param() — Leer parámetros + +Dentro del Hook, usas \`hook_param()\` para leer un parámetro por su nombre: + +\`\`\`c +uint8_t value[32]; +int64_t val_len = hook_param(SBUF(value), "MI_PARAM", 8); +\`\`\` + +Si el parámetro existe, \`hook_param()\` devuelve la longitud del valor. Si no existe, devuelve un número negativo. + +### HookNamespace en profundidad + +El **HookNamespace** es un identificador de 32 bytes (64 caracteres hex) que: + +- **Aísla el estado** de cada Hook en la cuenta +- Dos Hooks con **distinto namespace** no comparten estado +- Dos Hooks con el **mismo namespace** comparten estado (útil para colaboración entre Hooks) + +**Cómo elegir un namespace**: +- Usa un hash del nombre de tu Hook para namespaces únicos +- Usa un namespace compartido si necesitas que dos Hooks lean/escriban los mismos datos +- El namespace \`"0".repeat(64)\` es el namespace por defecto + +### Múltiples Hooks en una cuenta + +Xahau permite **hasta 10 Hooks** por cuenta: + +- Los Hooks se instalan en **posiciones** (0 a 9) del array \`Hooks\` +- **Orden de ejecución**: los Hooks se ejecutan en orden, empezando por la posición 0 +- Si un Hook en posición 0 hace \`rollback()\`, los Hooks siguientes **no se ejecutan** +- Cada Hook puede tener su propio \`HookOn\` para activarse solo en ciertos tipos de transacción + +### HookOn — Control granular + +El campo \`HookOn\` es una **máscara de bits** que define qué tipos de transacción activan el Hook: + +- \`"0000000000000000"\` → Se activa en **todos** los tipos +- Cada bit corresponde a un tipo de transacción +- Puedes configurar Hooks para que solo reaccionen a pagos, ofertas, etc. + +### Actualizar un Hook + +Para actualizar un Hook existente, envías una nueva transacción \`SetHook\` con el nuevo \`CreateCode\` (WASM) en la misma posición. + +### Eliminar un Hook + +Para eliminar un Hook de una posición, envías \`SetHook\` con un objeto Hook vacío (\`{}\`) en esa posición, junto con el flag de eliminación. + +### Limpiar estado (Namespace reset) + +Al eliminar un Hook o cambiar su namespace, puedes limpiar todo el estado almacenado. Esto es útil para "resetear" un Hook sin necesidad de limpiarlo manualmente clave por clave.`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Hook que lee un monto mínimo desde un parámetro", + en: "", + jp: "", + }, + language: "c", + code: `#include "hookapi.h" + +/** + * Hook: configurable_min.c + * Rechaza pagos menores a un mínimo configurable. + * El mínimo se pasa como HookParameter llamado "MIN" + * (en hex: 4D494E). + * El valor del parámetro son los drops en formato int64. + */ + +int64_t hook(uint32_t reserved) { + _g(1, 1); + + // Solo filtrar pagos (tipo 0) + int64_t tt = otxn_type(); + if (tt != 0) { + accept(SBUF("configurable_min: No es un pago."), __LINE__); + } + + // Leer el parámetro "MIN" (3 bytes: 0x4D 0x49 0x4E) + uint8_t min_buf[8] = { 0 }; + int64_t param_len = hook_param( + SBUF(min_buf), + "MIN", 3 + ); + + // Si el parámetro no existe, usar 1 XAH por defecto + int64_t min_drops = 1000000; // 1 XAH + if (param_len == 8) { + min_drops = *((int64_t*)min_buf); + } + + // Obtener el monto del pago + unsigned char amount_buf[48]; + int64_t amount_len = otxn_field(SBUF(amount_buf), sfAmount); + + // Solo XAH nativo + if (amount_len != 8) { + accept(SBUF("configurable_min: No es XAH."), __LINE__); + } + + int64_t drops = AMOUNT_TO_DROPS(amount_buf); + + if (drops < min_drops) { + rollback( + SBUF("configurable_min: Pago bajo el mínimo."), + __LINE__ + ); + } + + accept(SBUF("configurable_min: Pago aceptado."), __LINE__); + return 0; +} + +int64_t cbak(uint32_t reserved) { + return 0; +}`, + }, + { + title: { + es: "Script para instalar un Hook con parámetros personalizados", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); +const fs = require("fs"); + +async function deployHookWithParams() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const wallet = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + + // Leer el WASM compilado + const wasmBytes = fs.readFileSync("./build/configurable_min.wasm"); + const hookBinary = wasmBytes.toString("hex").toUpperCase(); + + // Definir parámetros del Hook + // "MIN" en hex = 4D494E + // Valor: 5000000 drops (5 XAH) como int64 little-endian + const minDrops = BigInt(5000000); + const minBuffer = Buffer.alloc(8); + minBuffer.writeBigInt64LE(minDrops); + const minValueHex = minBuffer.toString("hex").toUpperCase(); + + const setHook = { + TransactionType: "SetHook", + Account: wallet.address, + Hooks: [ + { + Hook: { + CreateCode: hookBinary, + HookOn: "0000000000000000", + HookNamespace: + "AABBCCDD".repeat(8), // Namespace personalizado + HookApiVersion: 0, + Flags: 1, + HookParameters: [ + { + HookParameter: { + HookParameterName: "4D494E", // "MIN" + HookParameterValue: minValueHex, + }, + }, + ], + }, + }, + ], + }; + + const prepared = await client.autofill(setHook); + const signed = wallet.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log("Hook desplegado con parámetro MIN =", Number(minDrops), "drops"); + console.log("(equivalente a", Number(minDrops) / 1000000, "XAH)"); + } + + await client.disconnect(); +} + +deployHookWithParams();`, + }, + ], + slides: [ + { + title: { es: "HookParameters", en: "", jp: "" }, + content: { + es: "Configuración sin recompilar\n\n• Se definen al instalar con SetHook\n• Se leen con hook_param() dentro del Hook\n• Clave + Valor en hexadecimal\n• Ideal para umbrales, direcciones y flags", + en: "", + jp: "", + }, + visual: "🎛️", + }, + { + title: { es: "Múltiples Hooks", en: "", jp: "" }, + content: { + es: "Hasta 10 Hooks por cuenta\n\n• Posiciones 0 a 9\n• Se ejecutan en orden (0 primero)\n• rollback() en uno detiene los siguientes\n• Cada Hook tiene su propio HookOn", + en: "", + jp: "", + }, + visual: "📚", + }, + { + title: { es: "Gestión de Hooks", en: "", jp: "" }, + content: { + es: "• Actualizar: SetHook con nuevo CreateCode\n• Eliminar: SetHook con objeto vacío\n• Namespace reset: limpiar todo el estado\n• HookOn: control granular por tipo de tx", + en: "", + jp: "", + }, + visual: "🔧", + }, + ], + }, + ], +} diff --git a/src/data/modules/m09-dex.js b/src/data/modules/m09-dex.js new file mode 100644 index 0000000..564c7b6 --- /dev/null +++ b/src/data/modules/m09-dex.js @@ -0,0 +1,647 @@ +export default { + id: "m9", + icon: "📊", + title: { + es: "El DEX nativo de Xahau", + en: "", + jp: "", + }, + lessons: [ + { + id: "m9l1", + title: { + es: "¿Qué es un DEX y cómo funciona en Xahau?", + en: "", + jp: "", + }, + theory: { + es: `Un **DEX** (Decentralized Exchange) es un exchange descentralizado que permite intercambiar tokens sin intermediarios. Lo que hace especial al DEX de Xahau es que está **integrado directamente en el protocolo** — no necesitas smart contracts para operar. + +### DEX nativo vs DEX basado en contratos + +| Característica | DEX EVM (Uniswap, etc.) | DEX Xahau | +|---|---|---| +| Implementación | Smart contract | Nativo del protocolo | +| Modelo | AMM (Automated Market Maker) | Order Book (libro de órdenes) | +| Despliegue | Necesitas desplegar contratos | Ya existe en cada cuenta | +| Fees de swap | Fee del protocolo + fee del contrato | Solo fee de transacción estándar | +| Liquidez | Pools de liquidez | Órdenes individuales | + +### Modelo de libro de órdenes (Order Book) + +A diferencia de los AMM populares en Ethereum, Xahau usa un **modelo de libro de órdenes**: + +- Los **makers** colocan órdenes en el libro (ofertas de compra o venta) +- Los **takers** llenan esas órdenes al operar contra ellas +- Las órdenes se emparejan automáticamente por el protocolo cuando los precios coinciden + +### Pares de divisas + +En el DEX de Xahau puedes operar: +- **Token contra XAH** (ej: USD/XAH, EUR/XAH) +- **Token contra token** (ej: USD/EUR) +- Cualquier token emitido en Xahau puede ser intercambiado + +### Auto-bridging + +Cuando no hay liquidez directa entre dos tokens, Xahau usa **auto-bridging**: +- El protocolo enruta automáticamente a través de XAH como intermediario +- Ejemplo: Si quieres vender EUR por USD pero no hay ofertas directas, el DEX busca EUR→XAH y luego XAH→USD +- Esto sucede automáticamente — no necesitas hacer nada especial + +### Fees + +El DEX de Xahau es extremadamente eficiente en costos: +- Solo pagas el **fee de transacción estándar** (fracciones de XAH) +- No hay fees de swap adicionales como en Uniswap (0.3%) +- No hay fees de proveedor de liquidez +- Esto hace que el trading sea mucho más barato que en DEXs basados en contratos`, + en: "", + jp: "", + }, + codeBlocks: [], + slides: [ + { + title: { es: "¿Qué es el DEX de Xahau?", en: "", jp: "" }, + content: { + es: "Exchange descentralizado nativo del protocolo\n\n• No necesita smart contracts\n• Modelo de libro de órdenes (Order Book)\n• Cualquier token puede ser intercambiado\n• Solo fee de transacción estándar", + en: "", + jp: "", + }, + visual: "📊", + }, + { + title: { es: "Order Book vs AMM", en: "", jp: "" }, + content: { + es: "Xahau: Order Book\n• Makers colocan órdenes\n• Takers llenan órdenes\n• Emparejamiento automático\n\nEVM (Uniswap): AMM\n• Pools de liquidez\n• Fórmula matemática (x*y=k)\n• Fees de swap del 0.3%+", + en: "", + jp: "", + }, + visual: "📖", + }, + { + title: { es: "Auto-bridging", en: "", jp: "" }, + content: { + es: "Xahau enruta automáticamente a través de XAH\n\n• EUR → XAH → USD (automático)\n• Aumenta la liquidez efectiva\n• No requiere acción del usuario\n• El protocolo busca la mejor ruta", + en: "", + jp: "", + }, + visual: "🌉", + }, + ], + }, + { + id: "m9l2", + title: { + es: "Consultar el libro de órdenes", + en: "", + jp: "", + }, + theory: { + es: `Antes de operar en el DEX, necesitas poder **consultar el libro de órdenes** para ver qué ofertas existen y a qué precios. + +### El comando book_offers + +El comando \`book_offers\` te permite consultar las órdenes disponibles para un par de divisas: + +- **taker_pays**: lo que el taker paga (lo que tú ofreces) +- **taker_gets**: lo que el taker recibe (lo que tú quieres) + +Si quieres **comprar USD con XAH**, entonces: +- \`taker_pays\` = XAH (lo que ofreces) +- \`taker_gets\` = USD (lo que quieres) + +### Bids y Asks + +El libro de órdenes tiene dos lados: +- **Bids (ofertas de compra)**: personas que quieren comprar el token base +- **Asks (ofertas de venta)**: personas que quieren vender el token base + +### Estructura de una oferta + +Cada oferta en el libro tiene: +- **TakerPays**: lo que el creador de la oferta quiere recibir +- **TakerGets**: lo que el creador ofrece dar +- **quality**: la relación TakerPays/TakerGets (el precio) +- **Account**: la cuenta que creó la oferta +- **Sequence**: número de secuencia de la oferta (su identificador) + +### Calcular el precio + +El precio efectivo de una oferta se calcula como: + +\`\`\` +precio = TakerPays / TakerGets +\`\`\` + +Para tokens con decimales, necesitas tener en cuenta que XAH se expresa en **drops** (1 XAH = 1,000,000 drops) y los tokens IOU tienen su propia precisión. + +### Top of Book + +La **mejor oferta** (top of book) es: +- Para compras: la oferta con el **precio más bajo** (comprar barato) +- Para ventas: la oferta con el **precio más alto** (vender caro) + +Las ofertas se devuelven ordenadas por calidad (precio), así que la primera oferta es siempre la mejor disponible.`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Consultar el libro de órdenes para un par token/XAH", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function getOrderBook() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Consultar ofertas: comprar USD (del emisor rIssuer...) + // pagando con XAH + const response = await client.request({ + command: "book_offers", + taker_pays: { + currency: "XAH", + }, + taker_gets: { + currency: "USD", + issuer: "rIssuerAddressHere", + }, + limit: 10, + ledger_index: "validated", + }); + + const offers = response.result.offers; + console.log("=== Libro de órdenes: USD/XAH ==="); + console.log("Ofertas disponibles:", offers.length); + console.log(); + + for (const offer of offers) { + // TakerPays = XAH (en drops) + const paysDrops = typeof offer.TakerPays === "string" + ? Number(offer.TakerPays) + : Number(offer.TakerPays.value); + + // TakerGets = USD + const getsValue = typeof offer.TakerGets === "string" + ? Number(offer.TakerGets) / 1000000 + : Number(offer.TakerGets.value); + + const paysXAH = paysDrops / 1000000; + const price = paysXAH / getsValue; + + console.log("Cuenta:", offer.Account); + console.log(" Ofrece:", getsValue, "USD"); + console.log(" Pide:", paysXAH, "XAH"); + console.log(" Precio:", price.toFixed(4), "XAH por USD"); + console.log(); + } + + await client.disconnect(); +} + +getOrderBook();`, + }, + { + title: { + es: "Top 5 mejores ofertas de compra y venta", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function showTopOfBook() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const issuer = "rIssuerAddressHere"; + const currency = "USD"; + + // Lado de compra: ofertas que venden USD por XAH + // (taker paga XAH, recibe USD) + const buyBook = await client.request({ + command: "book_offers", + taker_pays: { currency: "XAH" }, + taker_gets: { currency, issuer }, + limit: 5, + ledger_index: "validated", + }); + + // Lado de venta: ofertas que compran USD con XAH + // (taker paga USD, recibe XAH) + const sellBook = await client.request({ + command: "book_offers", + taker_pays: { currency, issuer }, + taker_gets: { currency: "XAH" }, + limit: 5, + ledger_index: "validated", + }); + + // Función para calcular precio + function calcPrice(pays, gets) { + const paysVal = typeof pays === "string" + ? Number(pays) / 1000000 + : Number(pays.value); + const getsVal = typeof gets === "string" + ? Number(gets) / 1000000 + : Number(gets.value); + return { paysVal, getsVal }; + } + + console.log("=== TOP 5 OFERTAS DE COMPRA (Buy USD) ==="); + for (let i = 0; i < buyBook.result.offers.length; i++) { + const o = buyBook.result.offers[i]; + const { paysVal, getsVal } = calcPrice(o.TakerPays, o.TakerGets); + console.log( + \` #\${i + 1} | \${getsVal.toFixed(2)} USD a \${(paysVal / getsVal).toFixed(4)} XAH/USD\` + ); + } + + console.log(); + console.log("=== TOP 5 OFERTAS DE VENTA (Sell USD) ==="); + for (let i = 0; i < sellBook.result.offers.length; i++) { + const o = sellBook.result.offers[i]; + const { paysVal, getsVal } = calcPrice(o.TakerPays, o.TakerGets); + console.log( + \` #\${i + 1} | \${paysVal.toFixed(2)} USD a \${(getsVal / paysVal).toFixed(4)} XAH/USD\` + ); + } + + const bestBuy = buyBook.result.offers[0]; + const bestSell = sellBook.result.offers[0]; + + if (bestBuy && bestSell) { + const buy = calcPrice(bestBuy.TakerPays, bestBuy.TakerGets); + const sell = calcPrice(bestSell.TakerPays, bestSell.TakerGets); + const buyPrice = buy.paysVal / buy.getsVal; + const sellPrice = sell.getsVal / sell.paysVal; + const spread = ((buyPrice - sellPrice) / sellPrice * 100).toFixed(2); + console.log(\`\\nSpread: \${spread}%\`); + } + + await client.disconnect(); +} + +showTopOfBook();`, + }, + ], + slides: [ + { + title: { es: "Consultar el libro de órdenes", en: "", jp: "" }, + content: { + es: "Comando: book_offers\n\n• taker_pays → Lo que ofreces\n• taker_gets → Lo que quieres\n• Resultado: lista de ofertas ordenadas por precio\n• La primera oferta = mejor precio", + en: "", + jp: "", + }, + visual: "🔍", + }, + { + title: { es: "Anatomía de una oferta", en: "", jp: "" }, + content: { + es: "Cada oferta contiene:\n\n• TakerPays → Lo que el maker quiere recibir\n• TakerGets → Lo que el maker ofrece\n• quality → Precio (TakerPays / TakerGets)\n• Account → Creador de la oferta\n• Sequence → Identificador de la oferta", + en: "", + jp: "", + }, + visual: "📄", + }, + { + title: { es: "Precio, top of book y spread", en: "", jp: "" }, + content: { + es: "Calcular el precio:\n• precio = TakerPays / TakerGets\n• XAH en drops (1 XAH = 1,000,000 drops)\n\nTop of book:\n• Primera oferta = mejor precio disponible\n• Compra: precio más bajo\n• Venta: precio más alto\n\nBid/Ask spread = diferencia entre mejor compra y mejor venta", + en: "", + jp: "", + }, + visual: "💹", + }, + ], + }, + { + id: "m9l3", + title: { + es: "Crear y gestionar ofertas", + en: "", + jp: "", + }, + theory: { + es: `Para operar en el DEX de Xahau, creas ofertas usando la transacción **OfferCreate**. Estas ofertas se publican en el libro de órdenes y pueden ser llenadas por otros participantes. + +### OfferCreate — Crear una oferta + +La transacción \`OfferCreate\` tiene dos campos principales: + +- **TakerPays**: lo que quieres recibir (tu lado de compra) +- **TakerGets**: lo que ofreces dar (tu lado de venta) + +Si quieres **comprar 100 USD pagando con XAH a un precio de 2 XAH por USD**: +- \`TakerPays\` = 100 USD (lo que quieres) +- \`TakerGets\` = 200 XAH (lo que ofreces) + +### Matching automático + +Cuando creas una oferta, el protocolo busca automáticamente ofertas existentes que coincidan: +- Si hay ofertas al precio que pides (o mejor), tu oferta se **llena inmediatamente** +- Si no hay coincidencias, tu oferta queda en el libro esperando +- Las ofertas pueden llenarse **parcialmente**: si pides 100 USD pero solo hay 50 disponibles, recibes 50 y el resto queda como oferta abierta + +### Flags importantes + +| Flag | Efecto | +|---|---| +| **tfImmediateOrCancel** | Si no se llena inmediatamente (total o parcial), se cancela el resto | +| **tfFillOrKill** | Si no se puede llenar completamente de inmediato, se cancela toda la oferta | +| **tfPassive** | No consume ofertas existentes; solo se publica en el libro | +| **tfSell** | Trata TakerGets como la cantidad exacta a vender (el resto puede variar) | + +### OfferCancel — Cancelar una oferta + +Para cancelar una oferta abierta, usas \`OfferCancel\` con el \`OfferSequence\` (número de secuencia) de la oferta que quieres cancelar. + +### Consultar tus ofertas abiertas + +Usa el comando \`account_offers\` para ver todas las ofertas abiertas de una cuenta. + +### Expiración automática + +Puedes agregar un campo **Expiration** a tu oferta con un timestamp. Cuando el ledger supere ese tiempo, la oferta se elimina automáticamente. El timestamp es en formato "Ripple epoch" (segundos desde el 1 de enero del 2000).`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Crear una oferta de compra (comprar USD con XAH)", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function createBuyOffer() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const wallet = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + + // Crear oferta: comprar 50 USD a 2 XAH por USD + // TakerPays = lo que quiero (50 USD) + // TakerGets = lo que ofrezco (100 XAH = 100000000 drops) + const offerCreate = { + TransactionType: "OfferCreate", + Account: wallet.address, + TakerPays: { + currency: "USD", + issuer: "rIssuerAddressHere", + value: "50", + }, + TakerGets: "100000000", // 100 XAH en drops + }; + + const prepared = await client.autofill(offerCreate); + const signed = wallet.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + // Verificar si la oferta se llenó o quedó abierta + const affectedNodes = result.result.meta.AffectedNodes; + const createdOffer = affectedNodes.find( + (n) => n.CreatedNode && n.CreatedNode.LedgerEntryType === "Offer" + ); + + if (createdOffer) { + console.log("Oferta publicada en el libro de órdenes."); + console.log("Sequence:", result.result.Sequence); + } else { + console.log("¡Oferta llenada inmediatamente!"); + } + } + + await client.disconnect(); +} + +createBuyOffer();`, + }, + { + title: { + es: "Listar todas las ofertas abiertas de tu cuenta", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function listMyOffers(address) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const response = await client.request({ + command: "account_offers", + account: address, + ledger_index: "validated", + }); + + const offers = response.result.offers; + console.log(\`=== Ofertas abiertas de \${address} ===\`); + console.log(\`Total: \${offers.length}\\n\`); + + for (const offer of offers) { + // Parsear TakerPays + let paysStr; + if (typeof offer.taker_pays === "string") { + paysStr = (Number(offer.taker_pays) / 1000000).toFixed(2) + " XAH"; + } else { + paysStr = offer.taker_pays.value + " " + offer.taker_pays.currency; + } + + // Parsear TakerGets + let getsStr; + if (typeof offer.taker_gets === "string") { + getsStr = (Number(offer.taker_gets) / 1000000).toFixed(2) + " XAH"; + } else { + getsStr = offer.taker_gets.value + " " + offer.taker_gets.currency; + } + + console.log(\`Oferta #\${offer.seq}:\`); + console.log(\` Quiero: \${paysStr}\`); + console.log(\` Ofrezco: \${getsStr}\`); + if (offer.expiration) { + const expDate = new Date((offer.expiration + 946684800) * 1000); + console.log(\` Expira: \${expDate.toISOString()}\`); + } + console.log(); + } + + await client.disconnect(); +} + +listMyOffers("rTuDireccionAqui");`, + }, + { + title: { + es: "Cancelar una oferta por su número de secuencia", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function cancelOffer(offerSequence) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const wallet = Wallet.fromSeed("sEdVxxxTuSeedDeTestnet", {algorithm: 'secp256k1'}); + + const offerCancel = { + TransactionType: "OfferCancel", + Account: wallet.address, + OfferSequence: offerSequence, + }; + + const prepared = await client.autofill(offerCancel); + const signed = wallet.sign(prepared); + const result = await client.submitAndWait(signed.tx_blob); + + console.log("Resultado:", result.result.meta.TransactionResult); + + if (result.result.meta.TransactionResult === "tesSUCCESS") { + console.log(\`Oferta #\${offerSequence} cancelada con éxito.\`); + } else { + console.log("Error al cancelar la oferta."); + } + + await client.disconnect(); +} + +// Cancelar la oferta con sequence 12345 +cancelOffer(12345);`, + }, + ], + slides: [ + { + title: { es: "OfferCreate", en: "", jp: "" }, + content: { + es: "Crear ofertas en el DEX\n\n• TakerPays → Lo que quieres recibir\n• TakerGets → Lo que ofreces dar\n• Matching automático si hay coincidencia\n• Las ofertas pueden llenarse parcialmente", + en: "", + jp: "", + }, + visual: "📝", + }, + { + title: { es: "Flags de ofertas", en: "", jp: "" }, + content: { + es: "• tfImmediateOrCancel → Llena o cancela\n• tfFillOrKill → Todo o nada\n• tfPassive → Solo publica, no consume\n• tfSell → Cantidad exacta de venta\n\nExpiration → Cancelación automática por tiempo", + en: "", + jp: "", + }, + visual: "🚩", + }, + { + title: { es: "Gestionar ofertas", en: "", jp: "" }, + content: { + es: "• account_offers → Ver tus ofertas abiertas\n• OfferCancel → Cancelar por OfferSequence\n• Expiration → Auto-cancelación por tiempo\n• Cada oferta abierta aumenta tu reserva", + en: "", + jp: "", + }, + visual: "🗂️", + }, + ], + }, + { + id: "m9l4", + title: { + es: "Estrategias de trading y auto-bridging", + en: "", + jp: "", + }, + theory: { + es: `En esta lección veremos cómo funciona el **auto-bridging** en detalle, y las mejores prácticas para operar en el DEX de Xahau. + +### Auto-bridging en detalle + +Cuando operas con un par de tokens que no tiene liquidez directa, Xahau busca una ruta a través de **XAH**: + +**Ejemplo**: Quieres vender EUR por USD +1. El DEX busca ofertas directas EUR/USD +2. Si no hay suficiente liquidez, busca EUR→XAH y XAH→USD +3. Combina ambas rutas para darte el mejor precio posible +4. Todo esto ocurre en una sola transacción + +El auto-bridging **aumenta significativamente la liquidez** del DEX porque todos los pares de tokens se benefician de la liquidez XAH. + +### Órdenes de mercado vs órdenes límite + +- **Orden de mercado**: Quieres ejecutar inmediatamente al mejor precio disponible + - Usa el flag \`tfImmediateOrCancel\` + - La oferta se llena con las mejores ofertas del libro y el resto se cancela + +- **Orden límite**: Quieres un precio específico y estás dispuesto a esperar + - Crea una oferta sin flags especiales + - La oferta permanece en el libro hasta que alguien la llene + +### Spread y slippage + +- **Spread**: La diferencia entre el mejor precio de compra y el mejor precio de venta + - Un spread bajo indica buena liquidez + - Un spread alto indica poca liquidez o volatilidad + +- **Slippage**: La diferencia entre el precio esperado y el precio real de ejecución + - Ocurre cuando tu orden es grande relativa a la liquidez disponible + - Para órdenes grandes, puedes consultar el libro primero para estimar el slippage + +### Mejores prácticas + +1. **Consulta el libro antes de operar**: Usa \`book_offers\` para ver los precios actuales y estimar el slippage + +2. **Usa tfImmediateOrCancel para órdenes de mercado**: Así evitas que una oferta parcialmente llenada quede abierta indefinidamente + +3. **Monitorea tus ofertas abiertas**: Las ofertas que dejas en el libro pueden ejecutarse en cualquier momento. Usa \`account_offers\` regularmente + +4. **Reservas de cuenta**: Cada oferta abierta en el DEX aumenta la **reserva** requerida de tu cuenta. Si tienes muchas ofertas abiertas, necesitarás más XAH en tu cuenta + - Reserva base de cuenta: 1 XAH + - Reserva por objeto (incluyendo ofertas): 0.2 XAH adicionales por oferta + +5. **Expiración como protección**: Para ofertas límite, usa el campo \`Expiration\` para evitar que ofertas viejas se ejecuten a precios desactualizados + +6. **Cuidado con el auto-bridging en tokens ilíquidos**: Si el par XAH intermedio también tiene poca liquidez, el precio final puede ser desfavorable`, + en: "", + jp: "", + }, + codeBlocks: [], + slides: [ + { + title: { es: "Auto-bridging en detalle", en: "", jp: "" }, + content: { + es: "Ruta automática a través de XAH\n\n• EUR → XAH → USD (automático)\n• Una sola transacción\n• Combina liquidez de ambos pares\n• Aumenta la liquidez efectiva del DEX", + en: "", + jp: "", + }, + visual: "🌉", + }, + { + title: { es: "Tipos de órdenes", en: "", jp: "" }, + content: { + es: "Orden de mercado:\n• tfImmediateOrCancel\n• Ejecución inmediata al mejor precio\n\nOrden límite:\n• Sin flags especiales\n• Espera en el libro al precio deseado\n• Usa Expiration como protección", + en: "", + jp: "", + }, + visual: "⚖️", + }, + { + title: { es: "Mejores prácticas", en: "", jp: "" }, + content: { + es: "• Consulta book_offers antes de operar\n• Monitorea tus ofertas abiertas\n• Cada oferta abierta = +0.2 XAH de reserva\n• Usa Expiration en ofertas límite\n• Cuidado con tokens ilíquidos", + en: "", + jp: "", + }, + visual: "✅", + }, + ], + }, + ], +} diff --git a/src/data/modules/m10-herramientas.js b/src/data/modules/m10-herramientas.js new file mode 100644 index 0000000..9ecfe72 --- /dev/null +++ b/src/data/modules/m10-herramientas.js @@ -0,0 +1,549 @@ +export default { + id: "m10", + icon: "🧰", + title: { + es: "Herramientas del ecosistema Xahau", + en: "", + jp: "", + }, + lessons: [ + { + id: "m10l1", + title: { + es: "Xaman: la wallet principal de Xahau", + en: "", + jp: "", + }, + theory: { + es: `**Xaman** (anteriormente conocida como XUMM) es la wallet principal del ecosistema XRPL y Xahau. Es una aplicación móvil que te permite gestionar tus cuentas, firmar transacciones y conectarte con aplicaciones descentralizadas. + +### ¿Qué es Xaman? + +Xaman es una wallet no custodial, lo que significa que **tú controlas tus claves privadas**. Nadie más tiene acceso a tus fondos. Es la puerta de entrada al ecosistema Xahau para usuarios y desarrolladores. + +### Instalación + +- **iOS**: Busca "Xaman" en la [App Store](https://apps.apple.com/app/xaman-wallet-formerly-xumm/id1492302343). +- **Android**: Busca "Xaman" en [Google Play Store](https://play.google.com/store/apps/details?id=com.xrpllabs.xumm). +- La app es de descarga gratuita y está disponible en múltiples idiomas. + +### Crear tu primera cuenta + +1. Abre Xaman y selecciona "Crear nueva cuenta" +2. La app generará un par de claves (pública/privada) +3. **IMPORTANTE**: Anota tu secreto (family seed) en papel y guárdalo en un lugar seguro +4. Confirma que has guardado el secreto completando la verificación +5. Tu cuenta está creada, pero necesita ser activada con un depósito mínimo + +### Importar una cuenta existente + +Si ya tienes una cuenta de Xahau (por ejemplo, creada con código): +1. Ve a "Ajustes" → "Cuentas" → "Añadir cuenta" +2. Selecciona "Importar cuenta existente" +3. Introduce tu **family seed** (sEd...) o **mnemónico** +4. La app importará la cuenta con acceso completo + +### Modo desarrollador (Testnet) + +Para trabajar con testnet en Xaman: +1. Ve a "Ajustes" → "Avanzado" → "Nodo" +2. Cambia el nodo a \`wss://xahau-test.net\` +3. Ahora puedes usar tu cuenta de testnet en Xaman +4. Las transacciones de testnet no tienen valor real + +### Firmar transacciones con Xaman + +Xaman actúa como un **firmador seguro** de transacciones: +- Las dApps envían una solicitud de firma a Xaman +- Tú revisas los detalles de la transacción en la app +- Autorizas con biometría (huella/Face ID) o PIN +- La transacción firmada se envía al ledger + +Las **xApps** son mini-aplicaciones que se ejecutan dentro de Xaman, proporcionando funcionalidad adicional directamente en la wallet. + +### Seguridad + +- **Bloqueo biométrico**: Face ID, Touch ID o huella dactilar +- **Cifrado**: Las claves privadas se cifran en el dispositivo +- **Firma local**: Las claves nunca salen del dispositivo +- **PIN de respaldo**: Por si falla la biometría +- **Modo de solo lectura**: Puedes añadir cuentas sin importar la clave privada + +### Conexión con dApps + +Las dApps se conectan a Xaman de dos formas: +- **Códigos QR**: Escaneas un QR que contiene la solicitud de transacción +- **Deep links**: Un enlace que abre directamente Xaman con la transacción pre-rellenada +- **xApps SDK**: Para desarrolladores que quieren integrar Xaman en sus aplicaciones`, + en: "", + jp: "", + }, + codeBlocks: [], + slides: [ + { + title: { + es: "¿Qué es Xaman?", + en: "", + jp: "", + }, + content: { + es: "Xaman (ex-XUMM) es la wallet principal de Xahau\n\n• Wallet no custodial — tú controlas tus claves\n• Disponible en iOS y Android\n• Firma segura con biometría\n• Conexión con dApps via QR / deep links", + en: "", + jp: "", + }, + visual: "📱", + }, + { + title: { + es: "Configurar Xaman para desarrollo", + en: "", + jp: "", + }, + content: { + es: "Para usar Xaman con testnet:\n\n1. Ajustes → Avanzado → Nodo\n2. Cambiar a wss://xahau-test.net\n3. Importar o crear cuenta de testnet\n4. ¡Las transacciones de test no cuestan nada real!", + en: "", + jp: "", + }, + visual: "🔧", + }, + { + title: { + es: "Seguridad en Xaman", + en: "", + jp: "", + }, + content: { + es: "Xaman protege tus fondos:\n\n• Claves cifradas en el dispositivo\n• Firma local — claves nunca salen del móvil\n• Bloqueo biométrico (Face ID / huella)\n• Modo solo lectura para monitoreo", + en: "", + jp: "", + }, + visual: "🔐", + }, + ], + }, + { + id: "m10l2", + title: { + es: "Exploradores de bloques", + en: "", + jp: "", + }, + theory: { + es: `Un **explorador de bloques** (block explorer) es una herramienta web que te permite navegar y buscar información en el ledger de Xahau de forma visual. Es como un "buscador" para la blockchain. + +### ¿Por qué usar un explorador? + +- Verificar que una transacción se ejecutó correctamente +- Inspeccionar el estado de una cuenta (balance, objetos, historial) +- Debuggear transacciones fallidas +- Entender qué pasó "bajo el capó" de una transacción + +### Xahau Explorer + +El explorador oficial de Xahau permite buscar: +- **Cuentas**: balance de XAH, tokens, objetos del ledger, historial de transacciones +- **Transacciones**: tipo, estado (éxito/fallo), detalles, metadata +- **Ledgers**: número, hash, timestamp, transacciones incluidas + +### Buscar una cuenta + +Al buscar una dirección (ej: \`rXXXXXX...\`) puedes ver: +- **Balance**: Cantidad de XAH disponible y reservado +- **Objetos**: Trust lines, ofertas DEX, URITokens, Hooks instalados +- **Historial**: Todas las transacciones enviadas y recibidas +- **Reserves**: XAH bloqueado por objetos en el ledger + +### Buscar una transacción + +Al buscar un hash de transacción puedes ver: +- **Tipo**: Payment, TrustSet, URITokenMint, SetHook, etc. +- **Estado**: \`tesSUCCESS\` (éxito) o código de error +- **Detalles**: Origen, destino, cantidad, memos, flags +- **Metadata**: Los nodos del ledger que fueron afectados (AffectedNodes) + +### Buscar un ledger + +Al buscar un número de ledger puedes ver: +- **Hash del ledger**: Identificador único +- **Timestamp**: Momento de cierre +- **Transacciones**: Lista de todas las transacciones incluidas +- **Número de transacciones**: Cuántas transacciones se procesaron + +### Bithomp Explorer + +Bithomp es otro explorador popular que soporta XRPL y Xahau: +- Interfaz limpia y fácil de usar +- Información detallada de cuentas y transacciones +- Herramientas adicionales como decodificador de transacciones + +### Entender AffectedNodes + +La metadata de cada transacción incluye \`AffectedNodes\`, que describe exactamente qué cambió en el ledger: +- **CreatedNode**: Se creó un nuevo objeto (ej: nueva trust line) +- **ModifiedNode**: Se modificó un objeto existente (ej: balance actualizado) +- **DeletedNode**: Se eliminó un objeto (ej: oferta completada) + +Cada nodo afectado muestra el estado anterior (\`PreviousFields\`) y el nuevo estado (\`FinalFields\`). + +### Debuggear transacciones fallidas + +Cuando una transacción falla, el explorador te muestra: +1. El **código de error** (ej: \`tecUNFUNDED_PAYMENT\`, \`tecNO_LINE\`) +2. El **significado** del error +3. Los **campos de la transacción** para identificar el problema + +### API endpoints de exploradores + +Algunos exploradores ofrecen APIs públicas para consultar datos programáticamente, además de la interfaz web.`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "Obtener y mostrar información de una transacción (como un explorador)", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client } = require("xahau"); + +async function explorarTransaccion(txHash) { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + // Obtener la transacción con metadata + const response = await client.request({ + command: "tx", + transaction: txHash, + binary: false, + }); + + const tx = response.result; + + console.log("=== EXPLORADOR DE TRANSACCION ===\\n"); + console.log("Hash:", txHash); + console.log("Tipo:", tx.TransactionType); + console.log("Estado:", tx.meta.TransactionResult); + console.log("Ledger:", tx.ledger_index); + console.log("Fecha:", new Date((tx.date + 946684800) * 1000).toISOString()); + console.log("Cuenta origen:", tx.Account); + + if (tx.Destination) { + console.log("Cuenta destino:", tx.Destination); + } + + if (tx.Amount) { + if (typeof tx.Amount === "string") { + console.log("Cantidad:", Number(tx.Amount) / 1000000, "XAH"); + } else { + console.log("Cantidad:", tx.Amount.value, tx.Amount.currency); + } + } + + console.log("Fee:", Number(tx.Fee) / 1000000, "XAH"); + + // Mostrar nodos afectados + console.log("\\n=== NODOS AFECTADOS ===\\n"); + for (const node of tx.meta.AffectedNodes) { + if (node.CreatedNode) { + console.log("+ CREADO:", node.CreatedNode.LedgerEntryType); + } else if (node.ModifiedNode) { + console.log("~ MODIFICADO:", node.ModifiedNode.LedgerEntryType); + if (node.ModifiedNode.PreviousFields) { + console.log(" Antes:", JSON.stringify(node.ModifiedNode.PreviousFields)); + } + if (node.ModifiedNode.FinalFields) { + console.log(" Despues:", JSON.stringify(node.ModifiedNode.FinalFields)); + } + } else if (node.DeletedNode) { + console.log("- ELIMINADO:", node.DeletedNode.LedgerEntryType); + } + } + + await client.disconnect(); +} + +// Uso: reemplaza con un hash de transaccion real de testnet +explorarTransaccion("TU_HASH_DE_TRANSACCION_AQUI");`, + }, + ], + slides: [ + { + title: { + es: "¿Qué es un explorador de bloques?", + en: "", + jp: "", + }, + content: { + es: "Un explorador es un buscador para la blockchain\n\n• Buscar cuentas: balance, objetos, historial\n• Buscar transacciones: tipo, estado, metadata\n• Buscar ledgers: hash, transacciones incluidas\n• Herramienta esencial para desarrollo y debug", + en: "", + jp: "", + }, + visual: "🔍", + }, + { + title: { + es: "AffectedNodes: qué cambió en el ledger", + en: "", + jp: "", + }, + content: { + es: "Cada transacción modifica el ledger:\n\n• CreatedNode — nuevo objeto creado\n• ModifiedNode — objeto existente modificado\n• DeletedNode — objeto eliminado\n\nCada nodo muestra PreviousFields y FinalFields", + en: "", + jp: "", + }, + visual: "📋", + }, + { + title: { + es: "Debuggear con el explorador", + en: "", + jp: "", + }, + content: { + es: "Cuando una transacción falla:\n\n1. Busca el hash en el explorador\n2. Revisa el código de error (ej: tecUNFUNDED_PAYMENT)\n3. Inspecciona los campos de la transacción\n4. Compara con la documentación del error", + en: "", + jp: "", + }, + visual: "🐛", + }, + ], + }, + { + id: "m10l3", + title: { + es: "Hooks Builder: IDE online para smart contracts", + en: "", + jp: "", + }, + theory: { + es: `**Hooks Builder** es un entorno de desarrollo integrado (IDE) online que te permite escribir, compilar, desplegar y probar Hooks de Xahau directamente desde tu navegador. + +### ¿Qué es Hooks Builder? + +Hooks Builder está disponible en **hooks-builder.xrpl.org** y es la forma más rápida de empezar a desarrollar smart contracts para Xahau sin instalar nada en tu máquina. + +### Características principales + +- **Editor de código**: Editor con resaltado de sintaxis para C +- **Compilador**: Compila C a WebAssembly directamente en el navegador +- **Desplegador**: Despliega tu Hook en testnet con un clic +- **Debugger**: Lee las trazas de ejecución del Hook +- **Templates**: Biblioteca de Hooks pre-construidos para aprender + +### Crear tu primer Hook paso a paso + +1. Ve a **hooks-builder.xrpl.org** +2. Haz clic en "New Hook" o selecciona un template +3. Escribe tu código C en el editor +4. Haz clic en "Compile" para compilar a WebAssembly +5. Si la compilación es exitosa, haz clic en "Deploy" +6. Selecciona tu cuenta de testnet (o crea una nueva) +7. Confirma el despliegue y espera la confirmación + +### La biblioteca de templates + +Hooks Builder incluye varios ejemplos listos para usar: +- **Starter**: Hook mínimo que acepta todas las transacciones +- **Firewall**: Hook que bloquea transacciones de ciertas cuentas +- **Carbon**: Hook que cobra una "tasa de carbono" en cada pago +- **Notifier**: Hook que emite un dato cada vez que se ejecuta + +Estos templates son excelentes para aprender los patrones comunes de desarrollo de Hooks. + +### Compilación: C a WebAssembly + +El proceso de compilación ocurre **en tu navegador**: +1. Tu código C se envía al compilador WASM integrado +2. Se verifica que usas las guard() correctamente +3. Se genera el archivo \`.wasm\` (WebAssembly) +4. Si hay errores, se muestran en la consola del IDE + +### Testing: desplegar en testnet + +Una vez compilado, puedes desplegar directamente en testnet: +1. El IDE genera la transacción \`SetHook\` automáticamente +2. Conecta con una cuenta de testnet (el IDE puede crear una) +3. La transacción se firma y envía +4. El Hook queda activo en tu cuenta de testnet + +### Debugging: trazas de ejecución + +Cuando tu Hook se ejecuta, puedes ver las trazas: +- Mensajes de \`trace()\` que hayas puesto en tu código +- El resultado del Hook (aceptar/rechazar) +- Errores de ejecución si los hay +- Estado del Hook (emisiones, cambios de estado) + +### Limitaciones + +- **Prototipado**: Ideal para experimentar y aprender +- **No para producción**: Para proyectos serios, usa un entorno local +- **Sin control de versiones**: No tiene git integrado +- **Compilador limitado**: Algunas optimizaciones avanzadas no están disponibles + +### ¿Cuándo pasar a desarrollo local? + +Considera migrar a un entorno local cuando: +- Tu Hook crece en complejidad +- Necesitas control de versiones (git) +- Quieres automatizar tests +- Vas a desplegar en mainnet +- Trabajas en equipo`, + en: "", + jp: "", + }, + codeBlocks: [], + slides: [ + { + title: { + es: "Hooks Builder: IDE online", + en: "", + jp: "", + }, + content: { + es: "hooks-builder.xrpl.org\n\n• Editor de código C con resaltado\n• Compilador C → WebAssembly en el navegador\n• Despliegue directo a testnet\n• Biblioteca de templates para aprender", + en: "", + jp: "", + }, + visual: "💻", + }, + { + title: { + es: "Flujo de trabajo en Hooks Builder", + en: "", + jp: "", + }, + content: { + es: "1. Escribe tu código C (o usa un template)\n2. Compila → se genera el .wasm\n3. Despliega → SetHook en testnet\n4. Prueba → envía transacciones al Hook\n5. Debuggea → lee las trazas de ejecución", + en: "", + jp: "", + }, + visual: "🔄", + }, + { + title: { + es: "¿Prototipo o producción?", + en: "", + jp: "", + }, + content: { + es: "Hooks Builder es ideal para:\n• Aprender y experimentar\n• Prototipos rápidos\n• Probar ideas\n\nPasa a desarrollo local cuando:\n• El Hook crece en complejidad\n• Necesitas git y CI/CD\n• Vas a desplegar en mainnet", + en: "", + jp: "", + }, + visual: "⚖️", + }, + ], + }, + { + id: "m10l4", + title: { + es: "Recursos para desarrolladores", + en: "", + jp: "", + }, + theory: { + es: `Como desarrollador de Xahau, tienes acceso a un ecosistema creciente de documentación, herramientas y comunidad. Aquí tienes los recursos más importantes. + +### Documentación oficial + +- **docs.xahau.network**: Documentación completa de Xahau, incluyendo transacciones, objetos del ledger, Hooks API y guías +- **xrpl.org/docs**: Gran parte de la documentación de XRPL aplica a Xahau (transacciones base, formato de datos, criptografía) + +### Repositorios en GitHub + +La organización de Xahau en GitHub contiene: +- **xahaud**: El servidor/nodo de Xahau (fork de rippled) +- **hooks-api**: Documentación y headers de la API de Hooks +- **Hooks examples**: Ejemplos de Hooks en C +- **xahau-py, xahau-js**: Librerías cliente + +### Comunidad + +- **Discord**: El canal principal de comunicación entre desarrolladores +- **Twitter/X**: Sigue las cuentas oficiales para anuncios y actualizaciones +- **GitHub Discussions**: Para preguntas técnicas y propuestas + +### Xahau Foundation + +La Xahau Foundation supervisa el desarrollo y gobernanza de la red: +- Coordina actualizaciones del protocolo +- Gestiona los fondos de desarrollo +- Organiza grants para desarrolladores + +### Librerías útiles + +Estas son las librerías que más usarás como desarrollador: + +- **xahau** (JavaScript/TypeScript): La librería principal que usamos en este curso. Permite conectar con el ledger, crear wallets, firmar y enviar transacciones. Es un fork de xrpl.js adaptado para Xahau. + +- **xrpl-client**: Cliente WebSocket ligero para conectar con nodos xahaud. Más simple que xahau.js, ideal para aplicaciones que solo necesitan leer datos. + +- **xrpl-accountlib**: Librería para derivar cuentas, generar claves y firmar transacciones offline. Útil para gestión avanzada de claves. + +- **xrpl-codec / xrpl-binary-codec**: Codificación y decodificación del formato binario del ledger. Necesario si trabajas con datos raw del ledger. + +### Herramientas de testing + +- **Testnet faucet**: Obtén XAH de prueba gratis en el faucet de testnet +- **Hooks Builder**: IDE online para prototipar Hooks (lo vimos en la lección anterior) +- **Xahau Explorer**: Explorador de bloques para verificar transacciones en testnet + +### Mantenerte actualizado + +El ecosistema evoluciona rápidamente. Para estar al día: +- Sigue **@XahauNetwork** y **@XRPLLabs** en Twitter/X +- Únete al **Discord** oficial +- Revisa los **releases** en GitHub para nuevas versiones +- Lee los **amendments** propuestos para entender hacia dónde va el protocolo +- Participa en las discusiones de gobernanza`, + en: "", + jp: "", + }, + codeBlocks: [], + slides: [ + { + title: { + es: "Documentación y repositorios", + en: "", + jp: "", + }, + content: { + es: "Recursos oficiales:\n\n• docs.xahau.network — documentación de Xahau\n• xrpl.org/docs — documentación XRPL (compatible)\n• GitHub Xahau — código fuente y ejemplos\n• Discord — comunidad de desarrolladores", + en: "", + jp: "", + }, + visual: "📚", + }, + { + title: { + es: "Librerías del ecosistema", + en: "", + jp: "", + }, + content: { + es: "Herramientas para desarrolladores:\n\n• xahau (JS/TS) — librería principal\n• xrpl-client — WebSocket ligero\n• xrpl-accountlib — derivación de cuentas\n• xrpl-codec — codificación binaria\n• Testnet faucet + Hooks Builder", + en: "", + jp: "", + }, + visual: "🛠️", + }, + { + title: { + es: "Comunidad y crecimiento", + en: "", + jp: "", + }, + content: { + es: "Mantente conectado:\n\n• Discord — canal principal de desarrolladores\n• Twitter/X — @XahauNetwork, @XRPLLabs\n• Xahau Foundation — grants para proyectos\n• GitHub — contribuye a repos open source\n• Amendments — sigue la evolución del protocolo", + en: "", + jp: "", + }, + visual: "🌐", + }, + ], + }, + ], +} diff --git a/src/data/modules/m11-proyecto-final.js b/src/data/modules/m11-proyecto-final.js new file mode 100644 index 0000000..1bb2d7c --- /dev/null +++ b/src/data/modules/m11-proyecto-final.js @@ -0,0 +1,846 @@ +export default { + id: "m11", + icon: "🎓", + title: { + es: "Proyecto final", + en: "", + jp: "", + }, + lessons: [ + { + id: "m11l1", + title: { + es: "Diseño del proyecto", + en: "", + jp: "", + }, + theory: { + es: `En este módulo final vamos a construir un **sistema de pagos completo** en Xahau testnet que demuestra todo lo aprendido durante el curso. + +### ¿Qué vamos a construir? + +Un conjunto de scripts que, ejecutados en orden, demuestran las capacidades fundamentales de Xahau: +1. Crear y financiar wallets +2. Enviar pagos en XAH +3. Emitir tokens personalizados +4. Mintear y transferir NFTs (URITokens) +5. Operar en el DEX + +### Arquitectura del proyecto + +El proyecto consiste en **5 scripts independientes** que se ejecutan secuencialmente: + +\`\`\` +proyecto-final/ +├── 01-setup.js → Crear y financiar dos wallets (A y B) +├── 02-payment.js → Enviar XAH de A a B +├── 03-token.js → Emitir token "CURSO" de A a B +├── 04-nft.js → Mintear URIToken en A, transferir a B +└── 05-dex.js → Colocar orden en el DEX +\`\`\` + +### Flujo del proyecto + +1. **01-setup.js**: Crea dos wallets y las financia con el faucet de testnet. Guarda los seeds para los siguientes scripts. + +2. **02-payment.js**: Wallet A envía XAH a Wallet B. Verificamos que el balance de B aumentó. + +3. **03-token.js**: Wallet A se configura como emisor de tokens. Wallet B crea una trust line. A emite 1000 tokens "CURSO" a B. + +4. **04-nft.js**: Wallet A mintea un URIToken. Lo pone a la venta. Wallet B lo compra. Verificamos que B es el nuevo propietario. + +5. **05-dex.js**: Wallet B coloca una oferta en el DEX para vender tokens CURSO por XAH. Consultamos el order book. + +### Prerequisitos + +- Node.js instalado +- Librería \`xahau\` instalada (\`npm install xahau\`) +- Conexión a internet (para conectar con testnet) +- Todo lo aprendido en los módulos 0-10 + +### Nota importante + +Todo el proyecto se ejecuta en **testnet**. Los tokens y NFTs no tienen valor real. Es un entorno seguro para experimentar sin riesgos.`, + en: "", + jp: "", + }, + codeBlocks: [], + slides: [ + { + title: { + es: "Proyecto final: sistema de pagos", + en: "", + jp: "", + }, + content: { + es: "Vamos a construir un sistema completo que demuestra:\n\n• Creación de wallets\n• Pagos en XAH\n• Emisión de tokens\n• NFTs (URITokens)\n• Trading en el DEX", + en: "", + jp: "", + }, + visual: "🏗️", + }, + { + title: { + es: "Arquitectura: 5 scripts", + en: "", + jp: "", + }, + content: { + es: "01-setup.js → Crear wallets A y B\n02-payment.js → Enviar XAH\n03-token.js → Emitir token CURSO\n04-nft.js → Mintear y transferir NFT\n05-dex.js → Operar en el DEX\n\nCada script construye sobre el anterior", + en: "", + jp: "", + }, + visual: "📋", + }, + { + title: { + es: "Todo en testnet", + en: "", + jp: "", + }, + content: { + es: "El proyecto completo se ejecuta en testnet\n\n• Sin riesgo — tokens sin valor real\n• Faucet gratuito para financiar wallets\n• Entorno seguro para experimentar\n• Mismo código que mainnet, distinto servidor", + en: "", + jp: "", + }, + visual: "🧪", + }, + ], + }, + { + id: "m11l2", + title: { + es: "Paso 1: Crear y financiar wallets", + en: "", + jp: "", + }, + theory: { + es: `El primer paso de nuestro proyecto es crear dos wallets y financiarlas con XAH de testnet. + +### Crear wallets programáticamente + +Usaremos \`Wallet.generate()\` para crear dos wallets nuevas. Cada wallet tiene: +- **Dirección pública** (rXXXXX...): para recibir fondos +- **Seed/secreto** (sEdXXX...): para firmar transacciones + +### Financiar con el faucet + +El faucet de testnet nos da XAH gratis para probar. La librería \`xahau\` incluye un método \`fundWallet()\` que: +1. Genera o usa una wallet existente +2. Solicita fondos al faucet +3. Espera a que la cuenta se active en el ledger +4. Devuelve la wallet financiada + +### Guardar la configuración + +Los seeds de las wallets se necesitan en los siguientes scripts. En un proyecto real usarías variables de entorno o un archivo de configuración seguro. Para este ejercicio, simplemente mostramos los seeds en consola para copiarlos. + +### Verificar las cuentas + +Después de crear y financiar las wallets, verificamos que existen en el ledger consultando su información con \`account_info\`.`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "01-setup.js: Crear y financiar dos wallets", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +async function setup() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + console.log("Conectado a Xahau Testnet\\n"); + + // --- Crear Wallet A --- + console.log("Creando Wallet A..."); + const walletA = Wallet.generate(); + console.log("Wallet A generada:"); + console.log(" Direccion:", walletA.address); + console.log(" Seed:", walletA.seed); + + // Financiar Wallet A con el faucet + console.log("Financiando Wallet A con el faucet..."); + await client.fundWallet(walletA); + console.log("Wallet A financiada\\n"); + + // --- Crear Wallet B --- + console.log("Creando Wallet B..."); + const walletB = Wallet.generate(); + console.log("Wallet B generada:"); + console.log(" Direccion:", walletB.address); + console.log(" Seed:", walletB.seed); + + // Financiar Wallet B con el faucet + console.log("Financiando Wallet B con el faucet..."); + await client.fundWallet(walletB); + console.log("Wallet B financiada\\n"); + + // --- Verificar ambas cuentas --- + console.log("=== VERIFICACION ===\\n"); + + const infoA = await client.request({ + command: "account_info", + account: walletA.address, + }); + console.log("Wallet A - Balance:", Number(infoA.result.account_data.Balance) / 1000000, "XAH"); + + const infoB = await client.request({ + command: "account_info", + account: walletB.address, + }); + console.log("Wallet B - Balance:", Number(infoB.result.account_data.Balance) / 1000000, "XAH"); + + // --- Mostrar configuracion para los siguientes scripts --- + console.log("\\n=== CONFIGURACION (copia para los siguientes scripts) ===\\n"); + console.log("const CONFIG = {"); + console.log(' walletA_seed: "' + walletA.seed + '",'); + console.log(' walletA_address: "' + walletA.address + '",'); + console.log(' walletB_seed: "' + walletB.seed + '",'); + console.log(' walletB_address: "' + walletB.address + '",'); + console.log("};"); + + await client.disconnect(); + console.log("\\nDesconectado. Guarda la configuracion para los siguientes pasos."); +} + +setup().catch(console.error);`, + }, + ], + slides: [ + { + title: { + es: "Crear wallets programáticamente", + en: "", + jp: "", + }, + content: { + es: "Wallet.generate() crea un par de claves:\n\n• Dirección pública (rXXX...) → para recibir\n• Seed/secreto (sEdXXX...) → para firmar\n\nfundWallet() financia con XAH de testnet", + en: "", + jp: "", + }, + visual: "👛", + }, + { + title: { + es: "Verificar y guardar", + en: "", + jp: "", + }, + content: { + es: "Después de crear las wallets:\n\n1. Verificar con account_info que existen\n2. Comprobar que tienen balance\n3. Guardar los seeds para los siguientes scripts\n4. ¡Nunca compartir seeds en producción!", + en: "", + jp: "", + }, + visual: "✅", + }, + { + title: { + es: "Seguridad al manejar seeds", + en: "", + jp: "", + }, + content: { + es: "Buenas prácticas con claves privadas:\n\n• Nunca hacer console.log del seed en producción\n• Usar variables de entorno (.env)\n• Nunca subir seeds a repositorios (git)\n• Testnet: puedes ser flexible\n• Mainnet: máxima precaución, fondos reales en riesgo", + en: "", + jp: "", + }, + visual: "🔐", + }, + ], + }, + { + id: "m11l3", + title: { + es: "Paso 2: Enviar pagos y emitir tokens", + en: "", + jp: "", + }, + theory: { + es: `En este paso combinamos dos operaciones fundamentales: enviar un pago en XAH y emitir un token personalizado llamado "CURSO". + +### Enviar XAH de Wallet A a Wallet B + +Un pago en XAH es la transacción más básica: +1. Creamos una transacción \`Payment\` +2. Especificamos origen, destino y cantidad +3. Firmamos con la wallet de origen +4. Enviamos y esperamos validación +5. Verificamos que el balance de B aumentó + +### Configurar Wallet A como emisor de tokens + +Para emitir tokens, Wallet A necesita activar el flag **DefaultRipple**: +- \`DefaultRipple\` permite que los tokens emitidos por A puedan ser transferidos entre cuentas +- Se activa con una transacción \`AccountSet\` +- Sin este flag, los tokens quedarían "atrapados" y no podrían circular + +### Crear trust line de B hacia A + +Antes de recibir tokens, Wallet B debe crear una **trust line** hacia Wallet A: +- La trust line indica que B confía en A como emisor del token "CURSO" +- Especifica un límite máximo de tokens que B acepta +- Se crea con una transacción \`TrustSet\` + +### Emitir tokens CURSO + +Con la trust line creada, Wallet A puede emitir tokens: +1. A envía un \`Payment\` con el token "CURSO" a B +2. La cantidad se especifica como un objeto con \`currency\`, \`value\` e \`issuer\` +3. Los tokens aparecen en el balance de B + +### Verificar el resultado + +Al final verificamos: +- El balance de XAH de ambas wallets +- El balance de tokens CURSO de Wallet B +- Que la trust line existe y tiene el límite correcto`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "02-payment.js + 03-token.js: Pagos XAH y emisión de tokens", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +// PEGA AQUI los seeds del script 01-setup.js +const CONFIG = { + walletA_seed: "sEdXXXXXXXXXXXXX", // <-- tu seed de Wallet A + walletB_seed: "sEdYYYYYYYYYYYYY", // <-- tu seed de Wallet B +}; + +async function pagosYTokens() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const walletA = Wallet.fromSeed(CONFIG.walletA_seed, {algorithm: 'secp256k1'}); + const walletB = Wallet.fromSeed(CONFIG.walletB_seed, {algorithm: 'secp256k1'}); + + console.log("Wallet A:", walletA.address); + console.log("Wallet B:", walletB.address); + + // ============================================= + // PASO 1: Enviar 100 XAH de A a B + // ============================================= + console.log("\\n=== PASO 1: Enviar 100 XAH de A a B ===\\n"); + + const payment = { + TransactionType: "Payment", + Account: walletA.address, + Destination: walletB.address, + Amount: "100000000", // 100 XAH en drops + }; + + const payResult = await client.submitAndWait(payment, { wallet: walletA }); + console.log("Pago XAH:", payResult.result.meta.TransactionResult); + + // Verificar balance de B + const infoB = await client.request({ + command: "account_info", + account: walletB.address, + }); + console.log("Balance de B:", Number(infoB.result.account_data.Balance) / 1000000, "XAH"); + + // ============================================= + // PASO 2: Configurar A como emisor (DefaultRipple) + // ============================================= + console.log("\\n=== PASO 2: Configurar A como emisor de tokens ===\\n"); + + const accountSet = { + TransactionType: "AccountSet", + Account: walletA.address, + SetFlag: 8, // asfDefaultRipple + }; + + const setResult = await client.submitAndWait(accountSet, { wallet: walletA }); + console.log("DefaultRipple activado:", setResult.result.meta.TransactionResult); + + // ============================================= + // PASO 3: Crear trust line de B para token CURSO + // ============================================= + console.log("\\n=== PASO 3: Crear trust line B -> A para CURSO ===\\n"); + + const trustSet = { + TransactionType: "TrustSet", + Account: walletB.address, + LimitAmount: { + currency: "CURSO", + value: "10000", + issuer: walletA.address, + }, + }; + + const trustResult = await client.submitAndWait(trustSet, { wallet: walletB }); + console.log("Trust line creada:", trustResult.result.meta.TransactionResult); + + // ============================================= + // PASO 4: Emitir 1000 CURSO de A a B + // ============================================= + console.log("\\n=== PASO 4: Emitir 1000 CURSO de A a B ===\\n"); + + const issueToken = { + TransactionType: "Payment", + Account: walletA.address, + Destination: walletB.address, + Amount: { + currency: "CURSO", + value: "1000", + issuer: walletA.address, + }, + }; + + const issueResult = await client.submitAndWait(issueToken, { wallet: walletA }); + console.log("Tokens emitidos:", issueResult.result.meta.TransactionResult); + + // ============================================= + // VERIFICACION FINAL + // ============================================= + console.log("\\n=== VERIFICACION FINAL ===\\n"); + + const lines = await client.request({ + command: "account_lines", + account: walletB.address, + }); + + for (const line of lines.result.lines) { + if (line.currency === "CURSO") { + console.log("Token CURSO en Wallet B:"); + console.log(" Balance:", line.balance); + console.log(" Limite:", line.limit); + console.log(" Emisor:", line.account); + } + } + + await client.disconnect(); + console.log("\\nCompletado. Wallet B tiene XAH y tokens CURSO."); +} + +pagosYTokens().catch(console.error);`, + }, + ], + slides: [ + { + title: { + es: "Pagos y tokens en un solo script", + en: "", + jp: "", + }, + content: { + es: "Este script combina:\n\n1. Payment de XAH (A → B)\n2. AccountSet: activar DefaultRipple en A\n3. TrustSet: B confía en A para CURSO\n4. Payment de tokens CURSO (A → B)\n\nCada paso construye sobre el anterior", + en: "", + jp: "", + }, + visual: "💸", + }, + { + title: { + es: "Verificar el resultado", + en: "", + jp: "", + }, + content: { + es: "Después de ejecutar:\n\n• account_info → verificar balance XAH\n• account_lines → verificar tokens CURSO\n• Wallet B tiene 1000 CURSO\n• Trust line con límite de 10000", + en: "", + jp: "", + }, + visual: "🔎", + }, + { + title: { + es: "4 transacciones clave", + en: "", + jp: "", + }, + content: { + es: "Tipos de transacción usados:\n\n• Payment (XAH) → transferencia nativa\n• AccountSet → activar DefaultRipple en emisor\n• TrustSet → B autoriza recibir token CURSO\n• Payment (token) → A emite CURSO a B\n\nOrden obligatorio: AccountSet → TrustSet → Payment token", + en: "", + jp: "", + }, + visual: "🔗", + }, + ], + }, + { + id: "m11l4", + title: { + es: "Paso 3: NFTs y trading", + en: "", + jp: "", + }, + theory: { + es: `En este paso final del proyecto, combinamos NFTs (URITokens) y el DEX para demostrar las capacidades avanzadas de Xahau. + +### Mintear un URIToken + +Wallet A va a crear un NFT que representa un "certificado de curso": +- La URI apunta a los metadatos del NFT +- Se marca como \`tfBurnable\` para que el emisor pueda quemarlo si es necesario + +### Transferir el URIToken a Wallet B + +La transferencia de URITokens en Xahau funciona así: +1. El propietario (A) crea una **oferta de venta** con \`URITokenCreateSellOffer\` +2. Puede especificar un precio (en XAH o tokens) o precio 0 para transferencia gratuita +3. Puede especificar un destinatario específico +4. El comprador (B) acepta con \`URITokenBuy\` +5. La propiedad cambia de A a B + +### Operar en el DEX + +El DEX (Decentralized Exchange) de Xahau permite intercambiar cualquier par de tokens: +- Wallet B va a crear una oferta para vender tokens CURSO por XAH +- Usamos \`OfferCreate\` para colocar la orden +- Consultamos el \`book_offers\` para ver el order book +- Finalmente, cancelamos la oferta con \`OfferCancel\` + +### Verificación final + +Al terminar verificamos: +- Que el URIToken ahora pertenece a Wallet B +- Que la oferta del DEX se creó correctamente +- Que pudimos cancelar la oferta limpiamente`, + en: "", + jp: "", + }, + codeBlocks: [ + { + title: { + es: "04-nft.js + 05-dex.js: NFTs y trading en el DEX", + en: "", + jp: "", + }, + language: "javascript", + code: `const { Client, Wallet } = require("xahau"); + +// PEGA AQUI los seeds del script 01-setup.js +const CONFIG = { + walletA_seed: "sEdXXXXXXXXXXXXX", // <-- tu seed de Wallet A + walletB_seed: "sEdYYYYYYYYYYYYY", // <-- tu seed de Wallet B +}; + +function toHex(str) { + return Buffer.from(str, "utf8").toString("hex").toUpperCase(); +} + +async function nftsYDex() { + const client = new Client("wss://xahau-test.net"); + await client.connect(); + + const walletA = Wallet.fromSeed(CONFIG.walletA_seed, {algorithm: 'secp256k1'}); + const walletB = Wallet.fromSeed(CONFIG.walletB_seed, {algorithm: 'secp256k1'}); + + console.log("Wallet A:", walletA.address); + console.log("Wallet B:", walletB.address); + + // ============================================= + // PASO 1: Mintear un URIToken desde Wallet A + // ============================================= + console.log("\\n=== PASO 1: Mintear URIToken (NFT) ===\\n"); + + const mintTx = { + TransactionType: "URITokenMint", + Account: walletA.address, + URI: toHex("https://xahau-course.example/certificate/final-project.json"), + Flags: 1, // tfBurnable + }; + + const mintResult = await client.submitAndWait(mintTx, { wallet: walletA }); + console.log("Mint:", mintResult.result.meta.TransactionResult); + + // Obtener el ID del URIToken creado + const uriTokenID = mintResult.result.meta.AffectedNodes + .filter(n => n.CreatedNode && n.CreatedNode.LedgerEntryType === "URIToken") + .map(n => n.CreatedNode.LedgerIndex)[0]; + console.log("URIToken ID:", uriTokenID); + + // ============================================= + // PASO 2: Crear oferta de venta (gratis, solo para B) + // ============================================= + console.log("\\n=== PASO 2: Crear oferta de venta ===\\n"); + + const sellOffer = { + TransactionType: "URITokenCreateSellOffer", + Account: walletA.address, + URITokenID: uriTokenID, + Amount: "0", // Transferencia gratuita + Destination: walletB.address, // Solo B puede comprar + }; + + const sellResult = await client.submitAndWait(sellOffer, { wallet: walletA }); + console.log("Oferta de venta:", sellResult.result.meta.TransactionResult); + + // ============================================= + // PASO 3: Wallet B compra el URIToken + // ============================================= + console.log("\\n=== PASO 3: Wallet B compra el URIToken ===\\n"); + + const buyTx = { + TransactionType: "URITokenBuy", + Account: walletB.address, + URITokenID: uriTokenID, + Amount: "0", + }; + + const buyResult = await client.submitAndWait(buyTx, { wallet: walletB }); + console.log("Compra:", buyResult.result.meta.TransactionResult); + + // Verificar que B es el propietario + const tokensB = await client.request({ + command: "account_objects", + account: walletB.address, + type: "uri_token", + }); + + const owned = tokensB.result.account_objects.find(obj => obj.index === uriTokenID); + console.log("B es propietario:", owned ? "SI" : "NO"); + + // ============================================= + // PASO 4: Crear oferta en el DEX (vender CURSO por XAH) + // ============================================= + console.log("\\n=== PASO 4: Crear oferta en el DEX ===\\n"); + + const offerCreate = { + TransactionType: "OfferCreate", + Account: walletB.address, + TakerPays: "50000000", // Quiero recibir 50 XAH + TakerGets: { + currency: "CURSO", + value: "100", + issuer: walletA.address, + }, // Ofrezco 100 CURSO + }; + + const offerResult = await client.submitAndWait(offerCreate, { wallet: walletB }); + console.log("Oferta DEX:", offerResult.result.meta.TransactionResult); + + // Obtener el Sequence de la oferta para poder cancelarla + const offerSequence = offerResult.result.Sequence; + + // ============================================= + // PASO 5: Consultar el order book + // ============================================= + console.log("\\n=== PASO 5: Order book CURSO/XAH ===\\n"); + + const book = await client.request({ + command: "book_offers", + taker_pays: { currency: "XAH" }, + taker_gets: { + currency: "CURSO", + issuer: walletA.address, + }, + limit: 10, + }); + + console.log("Ofertas en el order book:", book.result.offers.length); + for (const offer of book.result.offers) { + const gets = typeof offer.TakerGets === "string" + ? Number(offer.TakerGets) / 1000000 + " XAH" + : offer.TakerGets.value + " " + offer.TakerGets.currency; + const pays = typeof offer.TakerPays === "string" + ? Number(offer.TakerPays) / 1000000 + " XAH" + : offer.TakerPays.value + " " + offer.TakerPays.currency; + console.log(" Oferta: vende", gets, "por", pays); + } + + // ============================================= + // PASO 6: Cancelar la oferta (limpiar) + // ============================================= + console.log("\\n=== PASO 6: Cancelar oferta del DEX ===\\n"); + + const offerCancel = { + TransactionType: "OfferCancel", + Account: walletB.address, + OfferSequence: offerSequence, + }; + + const cancelResult = await client.submitAndWait(offerCancel, { wallet: walletB }); + console.log("Oferta cancelada:", cancelResult.result.meta.TransactionResult); + + await client.disconnect(); + console.log("\\nProyecto final completado!"); +} + +nftsYDex().catch(console.error);`, + }, + ], + slides: [ + { + title: { + es: "NFTs: mintear y transferir", + en: "", + jp: "", + }, + content: { + es: "Flujo de URITokens:\n\n1. A mintea el URIToken (URITokenMint)\n2. A crea oferta de venta (URITokenCreateSellOffer)\n3. B compra el URIToken (URITokenBuy)\n4. Verificar: B es el nuevo propietario", + en: "", + jp: "", + }, + visual: "🎨", + }, + { + title: { + es: "DEX: trading descentralizado", + en: "", + jp: "", + }, + content: { + es: "Operar en el DEX de Xahau:\n\n1. OfferCreate: vender 100 CURSO por 50 XAH\n2. book_offers: consultar el order book\n3. OfferCancel: cancelar la oferta\n\nTodo on-chain, sin intermediarios", + en: "", + jp: "", + }, + visual: "📊", + }, + { + title: { + es: "Flujo completo del DEX", + en: "", + jp: "", + }, + content: { + es: "Ciclo de vida de una oferta:\n\n• OfferCreate → publicar en el order book\n• book_offers → verificar que aparece\n• OfferCancel → retirar la oferta\n\nLimpiar ofertas importa:\n• Cada oferta abierta = +0.2 XAH de reserva\n• Ofertas huérfanas bloquean fondos innecesariamente", + en: "", + jp: "", + }, + visual: "♻️", + }, + ], + }, + { + id: "m11l5", + title: { + es: "Resumen y próximos pasos", + en: "", + jp: "", + }, + theory: { + es: `Has completado el curso de desarrollo en Xahau. Repasemos todo lo que has aprendido y exploremos los próximos pasos. + +### Resumen del curso + +A lo largo de 12 módulos has aprendido: + +- **Módulo 0 - Setup**: Configurar tu entorno de desarrollo con Node.js y la librería xahau + +- **Módulo 1 - Blockchain**: La arquitectura de Xahau, diferencias con blockchains EVM, el XRP Ledger como base + +- **Módulo 2 - Consenso**: El protocolo de consenso federado, UNLs, validadores, y cómo se cierran los ledgers + +- **Módulo 3 - Wallets**: Crear wallets, pares de claves, family seeds, activación de cuentas y reservas + +- **Módulo 4 - Consulta de datos**: Conectar con el ledger, consultar cuentas, transacciones, objetos y suscribirse a eventos + +- **Módulo 5 - Pagos**: Enviar pagos en XAH, destination tags, memos, y manejo de errores + +- **Módulo 6 - Tokens**: Emitir tokens personalizados, trust lines, DefaultRipple, y gestión de tokens + +- **Módulo 7 - NFTs**: URITokens nativos, mintear, transferir y quemar NFTs en Xahau + +- **Módulo 8 - Smart Contracts**: Hooks en C, compilación a WebAssembly, despliegue con SetHook, y la API de Hooks + +- **Módulo 9 - DEX**: El exchange descentralizado nativo, crear y cancelar ofertas, order books, auto-bridging + +- **Módulo 10 - Herramientas**: Xaman wallet, exploradores de bloques, Hooks Builder, y recursos del ecosistema + +- **Módulo 11 - Proyecto final**: Sistema completo que integra wallets, pagos, tokens, NFTs y DEX + +### Próximos pasos + +Ahora que dominas los fundamentos, aquí tienes ideas para seguir aprendiendo: + +#### 1. Escribe tu propio Hook en C +Profundiza en los smart contracts de Xahau: +- Aprende la API de Hooks en detalle +- Experimenta con \`state()\` para almacenar datos +- Crea un Hook que implemente lógica de negocio real +- Optimiza el uso de gas (instrucciones WASM) + +#### 2. Construye una dApp con Xaman +Crea una aplicación web que: +- Se conecte a Xahau via WebSocket +- Use el SDK de Xaman para firma de transacciones +- Tenga una interfaz de usuario amigable +- Implemente una funcionalidad útil (marketplace, votación, etc.) + +#### 3. Participa en la comunidad +- Únete al Discord de Xahau +- Contribuye a discusiones técnicas +- Ayuda a otros desarrolladores que están empezando +- Propón mejoras al protocolo + +#### 4. Contribuye a proyectos open source +- Revisa los repositorios de Xahau en GitHub +- Reporta bugs o sugiere mejoras +- Contribuye código a las librerías del ecosistema +- Crea herramientas que ayuden a otros desarrolladores + +#### 5. Explora mainnet (con precaución) +Cuando estés listo para mainnet: +- Recuerda que las transacciones tienen valor real +- Empieza con cantidades pequeñas +- Verifica todo en testnet antes de ir a mainnet +- Asegura tus claves privadas con máxima seguridad + +### Felicitaciones + +Has recorrido un largo camino desde configurar Node.js hasta construir un sistema de pagos completo en Xahau. Tienes las herramientas y conocimientos para construir aplicaciones reales en esta blockchain. + +**Xahau es una blockchain joven y en crecimiento** — hay enormes oportunidades para desarrolladores que entienden su tecnología. Lo que has aprendido aquí te da una base sólida para ser parte de ese futuro. + +¡Bienvenido al ecosistema Xahau!`, + en: "", + jp: "", + }, + codeBlocks: [], + slides: [ + { + title: { + es: "Lo que has aprendido", + en: "", + jp: "", + }, + content: { + es: "12 módulos completados:\n\n• Setup, Blockchain, Consenso\n• Wallets, Consultas, Pagos\n• Tokens, NFTs, Smart Contracts\n• DEX, Herramientas, Proyecto Final\n\nDe cero a desarrollador Xahau", + en: "", + jp: "", + }, + visual: "📚", + }, + { + title: { + es: "Próximos pasos", + en: "", + jp: "", + }, + content: { + es: "Sigue creciendo como desarrollador:\n\n• Escribe Hooks en C más complejos\n• Construye una dApp con Xaman SDK\n• Participa en la comunidad (Discord, GitHub)\n• Contribuye a proyectos open source\n• Explora mainnet cuando estés listo", + en: "", + jp: "", + }, + visual: "🚀", + }, + { + title: { + es: "¡Felicitaciones!", + en: "", + jp: "", + }, + content: { + es: "Has completado el curso de Xahau Academy\n\n• Tienes las herramientas para construir en Xahau\n• El ecosistema está creciendo y necesita desarrolladores\n• Lo que aprendiste es una base sólida\n\n¡Bienvenido al ecosistema Xahau!", + en: "", + jp: "", + }, + visual: "🎓", + }, + ], + }, + ], +} diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..8013a90 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './styles/index.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + +) diff --git a/src/styles/index.css b/src/styles/index.css new file mode 100644 index 0000000..ca8fabd --- /dev/null +++ b/src/styles/index.css @@ -0,0 +1,92 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + /* Dark theme (default) */ + --color-bg: #080818; + --color-surface: #0e0e24; + --color-surface-alt: #121230; + --color-border: #1e1e3a; + --color-border-light: #1a1a30; + --color-text: #c8d6e5; + --color-text-heading: #f0f0f5; + --color-text-secondary: #aab; + --color-text-muted: #667; + --color-text-dim: #555; + --color-text-faint: #333; + --color-accent: #c8ff00; + --color-icon-bg: #1a1a3a; + --color-button-bg: #1e1e3a; + --color-button-disabled-bg: #0a0a1a; + --color-code-bg: #0d0d1a; + --color-code-header: #13132a; + --color-code-text: #c8d6e5; + --color-scrollbar-track: #080818; + --color-scrollbar-thumb: #2a2a4a; + --color-overlay: rgba(0,0,0,0.4); + --color-header-gradient: rgba(200,255,0,0.06); + --color-done-bg: rgba(200,255,0,0.05); + --color-done-border: rgba(200,255,0,0.2); + --color-hover-bg: rgba(255,255,255,0.02); + --color-complete-bg: rgba(200,255,0,0.1); + --color-complete-border: rgba(200,255,0,0.3); + --color-copy-active-bg: #1a3a1a; + --color-inline-code-bg: #080818; +} + +[data-theme="light"] { + --color-bg: #f4f5f7; + --color-surface: #ffffff; + --color-surface-alt: #f8f9fc; + --color-border: #d8dae0; + --color-border-light: #e0e2e8; + --color-text: #2d3748; + --color-text-heading: #1a202c; + --color-text-secondary: #4a5568; + --color-text-muted: #718096; + --color-text-dim: #a0aec0; + --color-text-faint: #cbd5e0; + --color-accent: #d4a80e; + --color-icon-bg: #e8eaf0; + --color-button-bg: #e2e4ea; + --color-button-disabled-bg: #edf0f4; + --color-code-bg: #f1f3f7; + --color-code-header: #e6e8ee; + --color-code-text: #2d3748; + --color-scrollbar-track: #f4f5f7; + --color-scrollbar-thumb: #c0c4cc; + --color-overlay: rgba(0,0,0,0.15); + --color-header-gradient: rgba(212,168,14,0.06); + --color-done-bg: rgba(212,168,14,0.08); + --color-done-border: rgba(212,168,14,0.25); + --color-hover-bg: rgba(0,0,0,0.02); + --color-complete-bg: rgba(212,168,14,0.12); + --color-complete-border: rgba(212,168,14,0.3); + --color-copy-active-bg: #faf3d8; + --color-inline-code-bg: #e8eaf0; +} + +body { + font-family: 'Outfit', sans-serif; + background: var(--color-bg); + color: var(--color-text); + margin: 0; + transition: background 0.3s, color 0.3s; +} + +::-webkit-scrollbar { + width: 6px; +} +::-webkit-scrollbar-track { + background: var(--color-scrollbar-track); +} +::-webkit-scrollbar-thumb { + background: var(--color-scrollbar-thumb); + border-radius: 3px; +} + +/* Code block styling */ +pre code { + font-family: 'Fira Code', monospace; +} diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..7dc48f6 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,25 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,jsx}", + ], + theme: { + extend: { + colors: { + xahau: { + accent: '#c8ff00', + bg: '#080818', + surface: '#0e0e24', + border: '#1e1e3a', + muted: '#667', + } + }, + fontFamily: { + display: ['Outfit', 'sans-serif'], + mono: ['Fira Code', 'monospace'], + } + }, + }, + plugins: [], +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..a3d2d83 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + server: { + port: 3000, + open: true + }, + build: { + outDir: 'dist' + } +})