This commit is contained in:
Ekiserrepé
2026-02-18 20:16:39 +01:00
parent 8fcbbf73f4
commit e68bd2b14f
37 changed files with 16243 additions and 0 deletions

115
CLAUDE.md Normal file
View File

@@ -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

21
LICENSE Normal file
View File

@@ -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.

70
README.md Normal file
View File

@@ -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).

86
docs/ADDING_MODULES.md Normal file
View File

@@ -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

16
index.html Normal file
View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Xahau Academy — Learn Xahau Development</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&family=Outfit:wght@300;400;600;700;900&display=swap" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

2712
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View File

@@ -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"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

4
public/favicon.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polygon points="50,5 93,27.5 93,72.5 50,95 7,72.5 7,27.5" fill="#080818" stroke="#c8ff00" stroke-width="4"/>
<text x="50" y="62" text-anchor="middle" font-family="monospace" font-weight="bold" font-size="32" fill="#c8ff00">X</text>
</svg>

After

Width:  |  Height:  |  Size: 307 B

130
src/App.jsx Normal file
View File

@@ -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 (
<SlideViewer
slides={currentLesson.slides}
lang={lang}
labels={t}
onExit={() => setShowSlides(false)}
theme={theme}
/>
)
}
// Overview
if (view === 'overview') {
return (
<div className="min-h-screen" style={{ background: 'var(--color-bg)' }}>
<Header
lang={lang}
setLang={setLang}
labels={t}
completedCount={completedCount}
totalLessons={totalLessons}
theme={theme}
onToggleTheme={toggleTheme}
/>
<Overview
courseData={COURSE_DATA}
lang={lang}
labels={t}
completedLessons={completedLessons}
onOpenLesson={openLesson}
/>
</div>
)
}
// Lesson
return (
<LessonView
module={currentModule}
moduleIdx={activeModuleIdx}
lesson={currentLesson}
lessonIdx={activeLessonIdx}
lang={lang}
labels={t}
isCompleted={!!completedLessons[currentLesson.id]}
onToggleComplete={() => 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}
/>
)
}

View File

@@ -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 (
<div
className="rounded-xl overflow-hidden mb-5"
style={{ background: 'var(--color-code-bg)', border: '1px solid var(--color-border)' }}
>
<div
className="flex items-center justify-between px-4 py-2.5"
style={{ background: 'var(--color-code-header)', borderBottom: '1px solid var(--color-border)' }}
>
<div className="flex items-center gap-3">
<span
className="text-xs font-bold uppercase px-2 py-0.5 rounded"
style={{ background: LANG_COLORS[block.language] || '#666', color: '#000' }}
>
{block.language}
</span>
<span className="text-sm" style={{ color: 'var(--color-text-muted)' }}>{block.title[lang]}</span>
</div>
<button
onClick={handleCopy}
className="text-xs px-3 py-1.5 rounded-lg font-medium transition-all"
style={{
background: copied ? 'var(--color-copy-active-bg)' : 'var(--color-button-bg)',
color: copied ? '#4eaa25' : 'var(--color-text-muted)',
border: `1px solid ${copied ? '#4eaa25' : 'var(--color-border)'}`,
}}
>
{copied ? `${labels.copied}` : labels.copy}
</button>
</div>
<pre className="p-4 overflow-x-auto text-sm leading-relaxed">
<code className="font-mono" style={{ color: 'var(--color-code-text)' }}>{block.code}</code>
</pre>
</div>
)
}

65
src/components/Header.jsx Normal file
View File

@@ -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 (
<header
className="px-6 py-5 border-b"
style={{
background: `linear-gradient(180deg, var(--color-header-gradient) 0%, transparent 100%)`,
borderColor: 'var(--color-border-light)',
}}
>
<div className="max-w-5xl mx-auto">
<div className="flex items-center justify-between mb-4">
<div>
<h1 className="text-3xl font-black tracking-tight font-display" style={{ color: 'var(--color-text-heading)' }}>
<span style={{ color: 'var(--color-accent)' }}></span> {labels.title}
</h1>
<p className="text-sm mt-1" style={{ color: 'var(--color-text-muted)' }}>{labels.subtitle}</p>
</div>
<div className="flex items-center gap-3">
{/* Theme Toggle */}
<button
onClick={onToggleTheme}
className="w-9 h-9 rounded-lg flex items-center justify-center text-lg transition-all"
style={{
background: 'var(--color-button-bg)',
border: '1px solid var(--color-border)',
}}
title={theme === 'dark' ? 'Light mode' : 'Dark mode'}
>
{theme === 'dark' ? '☀️' : '🌙'}
</button>
{/* Language Switcher */}
<div className="flex rounded-lg overflow-hidden" style={{ border: '1px solid var(--color-border)' }}>
{['es', 'en', 'jp'].map((l) => (
<button
key={l}
onClick={() => setLang(l)}
className="px-3 py-1.5 text-sm font-bold uppercase transition-all"
style={{
background: lang === l ? 'var(--color-accent)' : 'transparent',
color: lang === l ? '#000' : 'var(--color-text-dim)',
}}
>
{l === 'jp' ? '日本語' : l.toUpperCase()}
</button>
))}
</div>
</div>
</div>
{/* Progress */}
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium" style={{ color: 'var(--color-text-muted)' }}>
{labels.progress}: {completedCount}/{totalLessons}
</span>
<span className="text-sm font-bold" style={{ color: 'var(--color-accent)' }}>{pct}%</span>
</div>
<ProgressBar completed={completedCount} total={totalLessons} />
</div>
</header>
)
}

View File

@@ -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 (
<div className="min-h-screen" style={{ background: 'var(--color-bg)', color: 'var(--color-text)' }}>
{/* Lesson header */}
<header
className="px-6 py-4 border-b"
style={{ background: 'var(--color-surface)', borderColor: 'var(--color-border-light)' }}
>
<div className="max-w-5xl mx-auto">
<div className="flex items-center justify-between mb-3">
<button onClick={onBack} className="flex items-center gap-2 text-sm font-medium" style={{ color: 'var(--color-text-muted)' }}>
{labels.allModules}
</button>
<div className="flex items-center gap-3">
{/* Theme toggle */}
<button
onClick={onToggleTheme}
className="w-8 h-8 rounded-lg flex items-center justify-center text-base transition-all"
style={{
background: 'var(--color-button-bg)',
border: '1px solid var(--color-border)',
}}
title={theme === 'dark' ? 'Light mode' : 'Dark mode'}
>
{theme === 'dark' ? '☀️' : '🌙'}
</button>
{/* Lesson position indicator */}
<span className="text-xs font-bold px-2.5 py-1 rounded-lg" style={{ background: 'var(--color-icon-bg)', color: 'var(--color-text-muted)' }}>
{labels.module} {moduleIdx}/{totalModules} {labels.theory} {lessonNumber}/{totalLessons}
</span>
</div>
</div>
<div className="flex items-center gap-3">
<span className="text-2xl">{mod.icon}</span>
<div>
<div className="text-xs font-bold uppercase tracking-wider" style={{ color: 'var(--color-text-muted)' }}>
{mod.title[lang]}
</div>
<h2 className="text-xl font-bold" style={{ color: 'var(--color-text-heading)' }}>{lesson.title[lang]}</h2>
</div>
</div>
{/* Lesson navigation pills */}
<div className="flex items-center gap-2 mt-3">
{mod.lessons.map((l, idx) => (
<button
key={l.id}
onClick={() => onGoToLesson(idx)}
className="text-xs px-3 py-1.5 rounded-full font-medium transition-all"
style={{
background: idx === lessonIdx ? 'var(--color-accent)' : 'var(--color-button-bg)',
color: idx === lessonIdx ? '#000' : 'var(--color-text-muted)',
border: `1px solid ${idx === lessonIdx ? 'var(--color-accent)' : 'var(--color-border)'}`,
}}
>
{idx + 1}. {l.title[lang]}
</button>
))}
</div>
{/* Tabs */}
<div className="flex gap-1 mt-4">
{tabs.map((tab) => {
const isActive = activeTab === tab.key && tab.key !== 'slides'
return (
<button
key={tab.key}
onClick={() => {
if (tab.key === 'slides' && lesson.slides?.length) {
onShowSlides()
} else if (!tab.disabled) {
setActiveTab(tab.key)
}
}}
disabled={tab.disabled}
className="px-4 py-2 rounded-t-lg text-sm font-medium transition-all flex items-center gap-2"
style={{
background: isActive ? 'var(--color-surface-alt)' : 'transparent',
color: tab.disabled ? 'var(--color-text-faint)' : isActive ? 'var(--color-accent)' : 'var(--color-text-muted)',
borderBottom: isActive ? '2px solid var(--color-accent)' : '2px solid transparent',
opacity: tab.disabled ? 0.4 : 1,
}}
>
{tab.icon} {tab.label}
{tab.key === 'slides' && ' ↗'}
</button>
)
})}
</div>
</div>
</header>
{/* Content */}
<div className="max-w-5xl mx-auto px-6 py-8">
{activeTab === 'theory' && (
<div
className="rounded-2xl p-6 md:p-8"
style={{
background: `linear-gradient(135deg, var(--color-surface) 0%, var(--color-surface-alt) 100%)`,
border: '1px solid var(--color-border)',
}}
>
<Markdown text={lesson.theory[lang]} />
</div>
)}
{activeTab === 'code' && lesson.codeBlocks && (
<div>
{lesson.codeBlocks.map((block, idx) => (
<CodeBlock key={idx} block={block} lang={lang} labels={labels} />
))}
</div>
)}
{/* Bottom actions */}
<div className="flex items-center justify-between mt-8 pt-6 border-t" style={{ borderColor: 'var(--color-border-light)' }}>
<button
onClick={onPrev}
disabled={!hasPrev}
className="px-5 py-2.5 rounded-xl text-sm font-medium transition-all"
style={{
background: !hasPrev ? 'var(--color-button-disabled-bg)' : 'var(--color-button-bg)',
color: !hasPrev ? 'var(--color-text-faint)' : 'var(--color-text-secondary)',
border: '1px solid var(--color-border)',
opacity: !hasPrev ? 0.4 : 1,
}}
>
{labels.prev}
</button>
<button
onClick={onToggleComplete}
className="px-6 py-2.5 rounded-xl text-sm font-bold transition-all"
style={{
background: isCompleted ? 'var(--color-complete-bg)' : 'var(--color-accent)',
color: isCompleted ? 'var(--color-accent)' : '#000',
border: isCompleted ? '1px solid var(--color-complete-border)' : '1px solid transparent',
}}
>
{isCompleted ? labels.lessonCompleted : labels.markComplete}
</button>
<button
onClick={onNext}
disabled={!hasNext}
className="px-5 py-2.5 rounded-xl text-sm font-medium transition-all"
style={{
background: !hasNext ? 'var(--color-button-disabled-bg)' : 'var(--color-accent)',
color: !hasNext ? 'var(--color-text-faint)' : '#000',
opacity: !hasNext ? 0.4 : 1,
}}
>
{labels.next}
</button>
</div>
</div>
</div>
)
}

168
src/components/Markdown.jsx Normal file
View File

@@ -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 <strong key={i} className="font-bold" style={{ color: 'var(--color-text-heading)' }}>{part.slice(2, -2)}</strong>
}
if (part.startsWith('`') && part.endsWith('`')) {
return (
<code key={i} className="px-1.5 py-0.5 rounded text-sm font-mono" style={{ background: 'var(--color-inline-code-bg)', color: 'var(--color-accent)' }}>
{part.slice(1, -1)}
</code>
)
}
const linkMatch = part.match(/\[([^\]]+)\]\(([^)]+)\)/)
if (linkMatch) {
return (
<a key={i} href={linkMatch[2]} target="_blank" rel="noopener noreferrer" className="underline text-blue-400">
{linkMatch[1]}
</a>
)
}
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(
<pre
key={`code-${i}`}
className="rounded-xl p-4 my-3 overflow-x-auto text-sm leading-relaxed font-mono"
style={{
background: 'var(--color-code-bg)',
border: '1px solid var(--color-border)',
color: 'var(--color-code-text)',
}}
>
<code>{codeLines.join('\n')}</code>
</pre>
)
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(
<div key={`table-${i}`} className="overflow-x-auto my-3">
<table className="w-full text-sm border-collapse" style={{ borderColor: 'var(--color-border)' }}>
<thead>
<tr>
{headerRow.map((cell, ci) => (
<th
key={ci}
className="text-left px-3 py-2 font-bold text-xs uppercase tracking-wider"
style={{
background: 'var(--color-code-header)',
borderBottom: '2px solid var(--color-border)',
color: 'var(--color-text-heading)',
}}
>
{renderInline(cell)}
</th>
))}
</tr>
</thead>
<tbody>
{bodyRows.map((row, ri) => (
<tr key={ri}>
{row.map((cell, ci) => (
<td
key={ci}
className="px-3 py-2"
style={{
borderBottom: '1px solid var(--color-border)',
color: 'var(--color-text)',
}}
>
{renderInline(cell)}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
continue
}
if (line.startsWith('### ')) {
elements.push(
<h3 key={i} className="text-lg font-bold mt-5 mb-2" style={{ color: 'var(--color-accent)' }}>
{line.slice(4)}
</h3>
)
} else if (line.startsWith('## ')) {
elements.push(
<h2 key={i} className="text-xl font-bold mt-6 mb-3" style={{ color: 'var(--color-accent)' }}>
{line.slice(3)}
</h2>
)
} else if (line.startsWith('- ')) {
elements.push(
<div key={i} className="flex gap-2 ml-4 mb-1">
<span style={{ color: 'var(--color-accent)' }}></span>
<span>{renderInline(line.slice(2))}</span>
</div>
)
} else if (/^\d+\.\s/.test(line)) {
const match = line.match(/^(\d+)\.\s(.*)/)
elements.push(
<div key={i} className="flex gap-2 ml-4 mb-1">
<span className="font-bold" style={{ color: 'var(--color-accent)' }}>{match[1]}.</span>
<span>{renderInline(match[2])}</span>
</div>
)
} else if (line.trim() === '') {
elements.push(<div key={i} className="h-3" />)
} else {
elements.push(
<p key={i} className="mb-2 leading-relaxed">
{renderInline(line)}
</p>
)
}
i++
}
return <div>{elements}</div>
}

View File

@@ -0,0 +1,61 @@
import React from 'react'
export default function Overview({ courseData, lang, labels, completedLessons, onOpenLesson }) {
return (
<div className="max-w-5xl mx-auto px-6 py-8">
<div className="grid gap-4">
{courseData.map((mod, mIdx) => (
<div
key={mod.id}
className="rounded-2xl overflow-hidden"
style={{
background: `linear-gradient(135deg, var(--color-surface) 0%, var(--color-surface-alt) 100%)`,
border: '1px solid var(--color-border)',
}}
>
<div className="p-5">
<div className="flex items-center gap-4 mb-4">
<div
className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl"
style={{ background: 'var(--color-icon-bg)' }}
>
{mod.icon}
</div>
<div>
<div className="text-xs font-bold uppercase tracking-widest" style={{ color: 'var(--color-text-muted)' }}>
{labels.module} {mIdx}
</div>
<h3 className="text-lg font-bold" style={{ color: 'var(--color-text-heading)' }}>{mod.title[lang]}</h3>
</div>
</div>
<div className="grid gap-2 ml-16">
{mod.lessons.map((lesson, lIdx) => {
const done = completedLessons[lesson.id]
return (
<button
key={lesson.id}
onClick={() => onOpenLesson(mIdx, lIdx)}
className="flex items-center justify-between p-3 rounded-xl text-left transition-all group"
style={{
background: done ? 'var(--color-done-bg)' : 'var(--color-hover-bg)',
border: `1px solid ${done ? 'var(--color-done-border)' : 'var(--color-border)'}`,
}}
>
<span
className="text-sm font-medium group-hover:translate-x-1 transition-transform"
style={{ color: done ? 'var(--color-accent)' : 'var(--color-text-secondary)' }}
>
{done ? '✓ ' : ''}{lesson.title[lang]}
</span>
<span className="text-xs" style={{ color: 'var(--color-text-muted)' }}></span>
</button>
)
})}
</div>
</div>
</div>
))}
</div>
</div>
)
}

View File

@@ -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 (
<div className="w-full rounded-full h-2" style={{ background: 'var(--color-border)' }}>
<div
className="h-2 rounded-full transition-all duration-500"
style={{
width: `${pct}%`,
background: 'linear-gradient(90deg, var(--color-accent), #7eb8ff)',
}}
/>
</div>
)
}

View File

@@ -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 (
<div
className="fixed inset-0 z-50 flex flex-col"
style={{
background: isLight
? 'linear-gradient(135deg, #f0f1f5 0%, #e8eaf0 50%, #f0f1f5 100%)'
: 'linear-gradient(135deg, #080818 0%, #0d0d2b 50%, #0a0a1f 100%)',
}}
>
{/* Top bar */}
<div className="flex items-center justify-between px-6 py-3" style={{ background: 'var(--color-overlay)' }}>
<span className="text-sm font-mono" style={{ color: 'var(--color-text-muted)' }}>
{current + 1} {labels.slideOf} {slides.length}
</span>
<button
onClick={onExit}
className="px-4 py-1.5 rounded-lg text-sm font-medium"
style={{
background: 'var(--color-button-bg)',
color: 'var(--color-text-muted)',
border: '1px solid var(--color-border)',
}}
>
{labels.exitSlides} (Esc)
</button>
</div>
{/* Content */}
<div className="flex-1 flex items-center justify-center px-8">
<div className="text-center max-w-4xl w-full">
<div className="text-7xl mb-8">{slide.visual}</div>
<h2
className="text-4xl font-black mb-8 tracking-tight font-mono"
style={{ color: 'var(--color-text-heading)' }}
>
{slide.title[lang]}
</h2>
<div
className="text-xl leading-relaxed whitespace-pre-line"
style={{ color: 'var(--color-text-secondary)' }}
>
{slide.content[lang]}
</div>
</div>
</div>
{/* Navigation */}
<div className="flex items-center justify-center gap-4 pb-6">
<button
onClick={() => setCurrent((c) => Math.max(c - 1, 0))}
disabled={current === 0}
className="px-6 py-2.5 rounded-lg font-medium transition-all"
style={{
background: current === 0 ? 'var(--color-button-disabled-bg)' : 'var(--color-button-bg)',
color: current === 0 ? 'var(--color-text-faint)' : 'var(--color-text-secondary)',
border: '1px solid var(--color-border)',
}}
>
{labels.prev}
</button>
<div className="flex gap-2">
{slides.map((_, idx) => (
<button
key={idx}
onClick={() => setCurrent(idx)}
className="w-3 h-3 rounded-full transition-all"
style={{
background: idx === current ? 'var(--color-accent)' : 'var(--color-border)',
transform: idx === current ? 'scale(1.3)' : 'scale(1)',
}}
/>
))}
</div>
<button
onClick={() => setCurrent((c) => Math.min(c + 1, slides.length - 1))}
disabled={current === slides.length - 1}
className="px-6 py-2.5 rounded-lg font-medium transition-all"
style={{
background: current === slides.length - 1 ? 'var(--color-button-disabled-bg)' : 'var(--color-accent)',
color: current === slides.length - 1 ? 'var(--color-text-faint)' : '#000',
}}
>
{labels.next}
</button>
</div>
</div>
)
}

39
src/data/courses.js Normal file
View File

@@ -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,
]

92
src/data/i18n.js Normal file
View File

@@ -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: "プレゼンテーションモード",
},
}

View File

@@ -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
},
],
},
],
}

File diff suppressed because it is too large Load Diff

View File

@@ -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: "🌐",
},
],
},
],
}

View File

@@ -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: "📊",
},
],
},
],
}

File diff suppressed because it is too large Load Diff

View File

@@ -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: "🔎",
},
],
},
],
}

View File

@@ -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: "🔑",
},
],
},
],
}

File diff suppressed because it is too large Load Diff

View File

@@ -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: "🔐",
},
],
},
],
}

View File

@@ -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: "🔄",
},
],
},
],
}

File diff suppressed because it is too large Load Diff

647
src/data/modules/m09-dex.js Normal file
View File

@@ -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: "✅",
},
],
},
],
}

View File

@@ -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: "🌐",
},
],
},
],
}

View File

@@ -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: "🎓",
},
],
},
],
}

10
src/main.jsx Normal file
View File

@@ -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(
<React.StrictMode>
<App />
</React.StrictMode>
)

92
src/styles/index.css Normal file
View File

@@ -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;
}

25
tailwind.config.js Normal file
View File

@@ -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: [],
}

13
vite.config.js Normal file
View File

@@ -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'
}
})