CH part 2

This commit is contained in:
Ekiserrepé
2026-04-15 12:01:41 +02:00
parent f5368b40e2
commit 63c25a3db6
8 changed files with 4606 additions and 104 deletions

View File

@@ -463,32 +463,35 @@ getAccountInfo("rYourAddressHere");`,
],
slides: [
{
title: { es: "Conexión a Xahau", en: "Connecting to Xahau", jp: "Xahauへの接続", ko: "Xahau 연결" },
title: { es: "Conexión a Xahau", en: "Connecting to Xahau", jp: "Xahauへの接続", ko: "Xahau 연결", zh: "连接到 Xahau" },
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: "パブリックードへのWebSocket接続\n\n🌐 メインネットwss://xahau.network\n🧪 テストネットwss://xahau-test.net\n\nすべての照会にJSON-RPC API",
ko: "공용 노드에 WebSocket으로 연결\n\n🌐 Mainnet: wss://xahau.network\n🧪 Testnet: wss://xahau-test.net\n\n모든 조회에 사용하는 JSON-RPC API",
zh: "通过 WebSocket 连接到公共节点\n\n🌐 主网: wss://xahau.network\n🧪 测试网: wss://xahau-test.net\n\n所有查询都通过 JSON-RPC API 完成",
},
visual: "🔌",
},
{
title: { es: "Comandos principales", en: "Main commands", jp: "主要なコマンド", ko: "주요 명령" },
title: { es: "Comandos principales", en: "Main commands", jp: "主要なコマンド", ko: "주요 명령", zh: "主要命令" },
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: "• server_info → ノードの状態\n• account_info → アカウントデータ\n• account_lines → TrustLine\n• account_objects → アカウントオブジェクト\n• account_tx → トランザクション履歴\n• ledger → レジャー情報",
ko: "• server_info → 노드 상태\n• account_info → 계정 데이터\n• account_lines → TrustLine\n• account_objects → 계정 객체\n• account_tx → 트랜잭션 기록\n• ledger → Ledger 정보",
zh: "• server_info → 节点状态\n• account_info → 账户数据\n• account_lines → TrustLines\n• account_objects → 账户对象\n• account_tx → 交易历史\n• ledger → 账本信息",
},
visual: "📡",
},
{
title: { es: "Buenas prácticas de conexión", en: "Connection best practices", jp: "接続のベストプラクティス", ko: "연결 모범 사례" },
title: { es: "Buenas prácticas de conexión", en: "Connection best practices", jp: "接続のベストプラクティス", ko: "연결 모범 사례", zh: "连接最佳实践" },
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: "• try/catchで接続をラップする\n• 自動再接続を実装する\n• 'disconnected'イベントをリッスンする\n• 開発にはTestnet、本番にはMainnet\n• 適切なタイムアウトを設定する\n• 処理前にレスポンスを検証する",
ko: "• 연결 로직을 try/catch로 감싸기\n• 자동 재연결 구현\n• 'disconnected' 이벤트 감지\n• 개발은 Testnet, 운영은 Mainnet 사용\n• 적절한 timeout 설정\n• 처리 전에 응답 검증",
zh: "• 用 try/catch 包裹连接逻辑\n• 实现自动重连\n• 监听 'disconnected' 事件\n• 开发用 Testnet生产用 Mainnet\n• 设置合理的 timeout\n• 处理前先验证响应",
},
visual: "🛡️",
},
@@ -501,6 +504,7 @@ getAccountInfo("rYourAddressHere");`,
en: "Advanced queries and subscriptions",
jp: "高度な照会とサブスクリプション",
ko: "고급 조회와 구독",
zh: "高级查询与订阅",
},
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.
@@ -603,6 +607,31 @@ You can query the details of a specific transaction using its **hash** with the
### 개별 트랜잭션 조회
\`tx\` 명령을 사용하면 **해시**로 특정 트랜잭션의 상세 정보를 조회할 수 있습니다.`,
zh: `除了基础查询外Xahau 还允许你查询特定账本对象、某个账户的交易历史,并订阅实时事件。
### 交易历史
\`account_tx\` 命令会返回与某个账户相关的交易。你可以使用 \`marker\` 字段对结果进行分页。
### 账户对象
\`account_objects\` 命令会返回与某个账户关联的所有账本对象:
- TrustLines
- OffersDEX 订单)
- URITokensNFT
- 已安装的 Hooks
- Hook 状态
### 实时订阅
通过 \`subscribe\` 命令,你可以在事件发生时收到通知:
- **ledger**:每次有新账本关闭时通知
- **transactions**:网络中的所有交易
- **accounts**:影响特定账户的交易
### 查询单笔交易
你可以使用 \`tx\` 命令,通过交易的 **hash** 查询某一笔交易的详细信息。`,
},
codeBlocks: [
{
@@ -611,6 +640,7 @@ You can query the details of a specific transaction using its **hash** with the
en: "Query an account's transaction history",
jp: "アカウントのトランザクション履歴を照会する",
ko: "계정 트랜잭션 기록 조회",
zh: "查询账户交易历史",
},
language: "javascript",
code: {
@@ -745,6 +775,39 @@ async function getAccountTransactions(address) {
await client.disconnect();
}
//예시 주소: rDADDYfnLvVY9FBnS8zFXhwYFHPuU5q2Sk
getAccountTransactions("rYourAddressHere");`,
zh: `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("=== 最近交易 ===");
for (const item of response.result.transactions) {
const tx = item.tx;
console.log(\`类型: \${tx.TransactionType}\`);
console.log(\` Hash: \${item.tx.hash}\`);
console.log(\` 日期: \${new Date((tx.date + 946684800) * 1000).toISOString()}\`);
console.log(\` 结果: \${item.meta.TransactionResult}\`);
if (tx.TransactionType === "Payment") {
console.log(\` 从: \${tx.Account}\`);
console.log(\` 到: \${tx.Destination}\`);
console.log(\` 金额: \${Number(tx.Amount) / 1_000_000} XAH\`);
}
}
await client.disconnect();
}
//示例地址: rDADDYfnLvVY9FBnS8zFXhwYFHPuU5q2Sk
getAccountTransactions("rYourAddressHere");`,
},
},
@@ -754,6 +817,7 @@ getAccountTransactions("rYourAddressHere");`,
en: "Query account objects and subscribe to events",
jp: "アカウントオブジェクトの照会とイベントのサブスクリプション",
ko: "계정 객체 조회와 이벤트 구독",
zh: "查询账户对象并订阅事件",
},
language: "javascript",
code: {
@@ -928,38 +992,84 @@ async function getAccountObjects(address) {
setTimeout(() => client.disconnect(), 60000);
}
//예시 주소: rDADDYfnLvVY9FBnS8zFXhwYFHPuU5q2Sk
getAccountObjects("rYourAddressHere");`,
zh: `const { Client } = require("xahau");
async function getAccountObjects(address) {
const client = new Client("wss://xahau-test.net");
await client.connect();
// 查询该账户的所有对象
const response = await client.request({
command: "account_objects",
account: address,
ledger_index: "validated",
});
console.log("=== 账户对象 ===");
for (const obj of response.result.account_objects) {
console.log(\`类型: \${obj.LedgerEntryType}\`);
if (obj.LedgerEntryType === "RippleState") {
console.log(\` 代币: \${obj.Balance.currency}\`);
console.log(\` 余额: \${obj.Balance.value}\`);
} else if (obj.LedgerEntryType === "URIToken") {
console.log(\` URI: \${obj.URI}\`);
}
}
// 订阅该账户的交易
console.log("已订阅该账户的交易...");
await client.request({
command: "subscribe",
accounts: [address]
});
client.on("transaction", (tx) => {
console.log("检测到新交易!");
console.log("类型:", tx.transaction.TransactionType);
console.log("结果:", tx.meta.TransactionResult);
});
// 保持连接 60 秒
setTimeout(() => client.disconnect(), 60000);
}
//示例地址: rDADDYfnLvVY9FBnS8zFXhwYFHPuU5q2Sk
getAccountObjects("rYourAddressHere");`,
},
},
],
slides: [
{
title: { es: "Historial de transacciones", en: "Transaction history", jp: "トランザクション履歴", ko: "트랜잭션 기록" },
title: { es: "Historial de transacciones", en: "Transaction history", jp: "トランザクション履歴", ko: "트랜잭션 기록", zh: "交易历史" },
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: "account_tx → アカウントの履歴\n\n• markerでページング\n• トランザクションタイプでフィルタリング\n• 結果を確認(成功/失敗)\n• 詳細なメタデータを照会",
ko: "account_tx → 계정 기록\n\n• marker로 페이지네이션\n• 트랜잭션 유형별 필터링\n• 결과 확인(성공/실패)\n• 상세 메타데이터 조회",
zh: "account_tx → 账户历史\n\n• 使用 marker 分页\n• 按交易类型过滤\n• 查看结果(成功/失败)\n• 查询详细 metadata",
},
visual: "📜",
},
{
title: { es: "Tiempo real", en: "Real time", jp: "リアルタイム", ko: "실시간" },
title: { es: "Tiempo real", en: "Real time", jp: "リアルタイム", ko: "실시간", zh: "实时" },
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: "subscribe → リアルタイムイベント\n\n• ledger → レジャーのクローズ\n• transactions → すべてのtx\n• accounts → 特定アカウントのtx\n\nアクティビティの監視に最適",
ko: "subscribe → 실시간 이벤트\n\n• ledger → ledger 닫힘 알림\n• transactions → 모든 tx\n• accounts → 특정 계정 tx\n\n활동 모니터링에 적합",
zh: "subscribe → 实时事件\n\n• ledger → 账本关闭通知\n• transactions → 所有交易\n• accounts → 特定账户的交易\n\n非常适合监控活动",
},
visual: "⚡",
},
{
title: { es: "Suscripciones en detalle", en: "Subscriptions in detail", jp: "サブスクリプションの詳細", ko: "구독 상세" },
title: { es: "Suscripciones en detalle", en: "Subscriptions in detail", jp: "サブスクリプションの詳細", ko: "구독 상세", zh: "订阅详解" },
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: "リアルタイムイベントのsubscribeコマンド\n\n• ledgerイベント → 新しいレジャーがクローズ\n• transactionイベント → txが確認済み\n• client.on('transaction')でリッスン\n• unsubscribeでリッスン停止\n• WebSocket接続を開いたままにする",
ko: "실시간 이벤트용 subscribe 명령:\n\n• ledger 이벤트 → 새 ledger 닫힘\n• transaction 이벤트 → tx 확인\n• client.on('transaction')로 수신\n• unsubscribe로 중지\n• WebSocket 연결을 계속 유지",
zh: "用于实时事件的 subscribe 命令:\n\n• ledger 事件 → 新账本关闭\n• transaction 事件 → 交易已确认\n• 用 client.on('transaction') 监听\n• 使用 unsubscribe 停止监听\n• 保持 WebSocket 连接处于打开状态",
},
visual: "📡",
},
@@ -972,6 +1082,7 @@ getAccountObjects("rYourAddressHere");`,
en: "Pagination and error handling",
jp: "ページネーションとエラー処理",
ko: "페이지네이션과 오류 처리",
zh: "分页与错误处理",
},
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.
@@ -1094,6 +1205,36 @@ Many API commands return paginated results. When there is more data than fits in
- **연결 끊김 처리**: 클라이언트의 \`disconnected\` 이벤트를 감지하고 자동 재연결합니다
- **Rate limiting 고려**: 공용 노드는 요청을 제한할 수 있으므로 대량 요청 사이에 간격을 둡니다
- **Timeout 설정**: 애플리케이션이 멈추지 않도록 적절한 timeout을 설정합니다`,
zh: `在使用 Xahau API 时,有两个方面尤其重要:处理大结果集时的**分页**,以及构建稳健应用所需的**错误处理**。
### marker 系统
很多 API 命令返回的是分页结果。当数据量超过单次响应可容纳的范围时API 会在结果中包含一个 \`marker\` 字段。要获取下一页,你必须发送同一个命令,并带上这个 \`marker\`
- \`limit\` 字段控制每页返回多少结果(最大值视命令而定,通常为 200 到 400
- 如果响应中包含 \`marker\`,说明后面还有更多页面
- 如果响应中没有 \`marker\`,说明已经到最后一页
- \`marker\` 的值是不透明的:不要修改它,直接原样传回即可
### 常见 API 错误
| Error | 含义 |
|---|---|
| \`actNotFound\` | 查询的账户在账本中不存在 |
| \`lgrNotFound\` | 请求的账本未找到 |
| \`invalidParams\` | 请求参数不正确 |
| \`noCurrent\` | 服务器当前没有可用账本 |
| \`noNetwork\` | 服务器未连接到网络 |
| \`tooBusy\` | 服务器负载过高 |
### 最佳实践
- **始终用 try/catch 包裹请求**网络错误、timeout 和 API 错误都必须处理
- **实现重试机制**:对于 \`tooBusy\` 或 timeout 这类瞬时错误,使用指数退避重试
- **验证响应内容**:在处理数据前先确认 \`result.status === "success"\`
- **处理断线情况**:监听客户端的 \`disconnected\` 事件并自动重连
- **考虑速率限制**:公共节点可能会限制请求频率,批量请求之间应适当暂停
- **设置合理 timeout**:防止应用程序长时间卡住`,
},
codeBlocks: [
{
@@ -1102,6 +1243,7 @@ Many API commands return paginated results. When there is more data than fits in
en: "Paginate all account objects using marker",
jp: "markerを使用してアカウントのすべてのオブジェクトをページングする",
ko: "marker로 계정 객체 전체 페이지네이션",
zh: "使用 marker 分页获取账户全部对象",
},
language: "javascript",
code: {
@@ -1340,38 +1482,100 @@ async function getAllAccountObjects(address) {
await client.disconnect();
}
//예시 계정: rHh1YJN4kwRdw4Y29Xu1EY9qW8u36vAYLc
getAllAccountObjects("rYourAddressHere");`,
zh: `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("=== 正在获取", address, "的全部对象 ===");
do {
const request = {
command: "account_objects",
account: address,
ledger_index: "validated",
limit: 100,
};
// 只有 marker 存在时才加入(第一次请求不加)
if (marker) {
request.marker = marker;
}
const response = await client.request(request);
const objects = response.result.account_objects;
allObjects = allObjects.concat(objects);
console.log(\`\${page} 页: 收到 \${objects.length} 个对象\`);
// 更新下一页所需的 marker
marker = response.result.marker;
page++;
// 短暂停顿,避免压垮节点
if (marker) {
await new Promise((resolve) => setTimeout(resolve, 200));
}
} while (marker);
console.log(\`总共获取对象数: \${allObjects.length}\`);
// 按类型分组
const byType = {};
for (const obj of allObjects) {
const type = obj.LedgerEntryType;
byType[type] = (byType[type] || 0) + 1;
}
console.log("按类型汇总:");
for (const [type, count] of Object.entries(byType)) {
console.log(\` \${type}: \${count}\`);
}
await client.disconnect();
}
//示例账户: rHh1YJN4kwRdw4Y29Xu1EY9qW8u36vAYLc
getAllAccountObjects("rYourAddressHere");`,
},
},
],
slides: [
{
title: { es: "Paginación con marker", en: "Pagination with marker", jp: "markerによるページネーション", ko: "marker 페이지네이션" },
title: { es: "Paginación con marker", en: "Pagination with marker", jp: "markerによるページネーション", ko: "marker 페이지네이션", zh: "使用 marker 分页" },
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: "多くの結果がある場合、APIはページングします\n\n1. limitをつけてクエリを送信\n2. レスポンスにmarkerがあればデータが続く\n3. markerを含めてクエリを再送信\n4. markerがなくなるまで繰り返す\n\nmarkerの値を絶対に変更しない",
ko: "결과가 많으면 API가 페이지를 나눕니다:\n\n1. limit와 함께 요청 전송\n2. 응답에 marker가 있으면 다음 데이터 존재\n3. marker를 포함해 다시 요청\n4. marker가 없어질 때까지 반복\n\nmarker 값은 절대 수정하지 마세요",
zh: "当结果很多时API 会分页:\n\n1. 带上 limit 发送请求\n2. 如果响应里有 marker说明还有更多数据\n3. 带着 marker 再发一次请求\n4. 重复直到 marker 消失\n\n永远不要修改 marker 的值",
},
visual: "📄",
},
{
title: { es: "Errores comunes", en: "Common errors", jp: "よくあるエラー", ko: "흔한 오류" },
title: { es: "Errores comunes", en: "Common errors", jp: "よくあるエラー", ko: "흔한 오류", zh: "常见错误" },
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: "• actNotFound → アカウントが存在しない\n• lgrNotFound → レジャーが見つからない\n• invalidParams → パラメータが正しくない\n• noCurrent → 現在のレジャーなし\n• noNetwork → ネットワーク接続なし\n• tooBusy → サーバーが過負荷",
ko: "• actNotFound → 계정이 존재하지 않음\n• lgrNotFound → ledger를 찾지 못함\n• invalidParams → 잘못된 파라미터\n• noCurrent → 현재 ledger 없음\n• noNetwork → 네트워크 연결 없음\n• tooBusy → 서버 과부하",
zh: "• actNotFound → 账户不存在\n• lgrNotFound → 未找到账本\n• invalidParams → 参数错误\n• noCurrent → 当前没有账本\n• noNetwork → 未连接到网络\n• tooBusy → 服务器过载",
},
visual: "⚠️",
},
{
title: { es: "Buenas prácticas", en: "Best practices", jp: "ベストプラクティス", ko: "모범 사례" },
title: { es: "Buenas prácticas", en: "Best practices", jp: "ベストプラクティス", ko: "모범 사례", zh: "最佳实践" },
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: "• リクエストには常にtry/catchを使用\n• 指数バックオフでリトライする\n• result.status === 'success'を検証する\n• 'disconnected'イベントをリッスンする\n• 大量リクエスト間に間隔を設ける\n• 適切なタイムアウトを設定する",
ko: "• 요청은 항상 try/catch 사용\n• 지수 백오프로 재시도\n• result.status === 'success' 검증\n• 'disconnected' 이벤트 감지\n• 대량 요청 사이에 잠시 대기\n• 적절한 timeout 설정",
zh: "• 请求始终放在 try/catch 中\n• 用指数退避重试\n• 验证 result.status === 'success'\n• 监听 'disconnected' 事件\n• 批量请求之间稍作暂停\n• 设置合理 timeout",
},
visual: "🛡️",
},
@@ -1384,6 +1588,7 @@ getAllAccountObjects("rYourAddressHere");`,
en: "Working with ledger objects",
jp: "レジャーオブジェクトの操作",
ko: "레저 객체 다루기",
zh: "处理账本对象",
},
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.
@@ -1514,6 +1719,38 @@ ledger의 각 객체는 식별 데이터에서 계산되는 SHA-512Half hash 기
- RippleState 인덱스는 두 계정과 통화 정보에서 계산됩니다
이 인덱스는 결정적이므로 입력 데이터를 알고 있으면 언제든지 다시 계산할 수 있습니다.`,
zh: `Xahau 的账本会把所有信息存储为**对象**ledger entries。每个对象都有类型、唯一索引hash以及专属字段。在这一课里我们会学习如何直接查询和处理这些对象。
### ledger_entry 命令
使用 \`ledger_entry\`,你可以通过对象的**索引**64 个十六进制字符的 hash查询账本中的某个特定对象。当你已经知道对象的精确标识符时这个命令非常有用。
### 可查询的对象类型
| 类型 | 说明 |
|---|---|
| \`AccountRoot\` | 账户的主要数据 |
| \`RippleState\` | 两个账户之间的 trust line |
| \`Offer\` | DEX 上的活跃订单 |
| \`URIToken\` | 非同质化代币Xahau NFT |
| \`Hook\` | 已安装 Hook 的定义 |
| \`HookState\` | Hook 存储的状态 |
### 带类型过滤的 account_objects 命令
\`account_objects\` 命令接受 \`type\` 参数,用来只筛选某一类对象。有效值包括:
- \`"state"\` → RippleStatetrust lines
- \`"offer"\` → OffersDEX 订单)
- \`"uri_token"\` → URITokens
- \`"hook"\` → 已安装 Hooks
### 理解 ledger 索引
账本中的每个对象都拥有一个**唯一索引**,它是根据对象的标识数据通过 SHA-512Half 哈希计算出来的。例如:
- AccountRoot 的索引由账户地址计算得到
- RippleState 的索引由两个账户和货币共同计算得到
这些索引是确定性的:只要你知道输入数据,就可以随时重新计算出来。`,
},
codeBlocks: [
@@ -1523,6 +1760,7 @@ ledger의 각 객체는 식별 데이터에서 계산되는 SHA-512Half hash 기
en: "Query account_objects filtered by type",
jp: "タイプでフィルタリングされたaccount_objectsを照会する",
ko: "type으로 필터링한 account_objects 조회",
zh: "按类型过滤查询 account_objects",
},
language: "javascript",
code: {
@@ -1809,28 +2047,101 @@ getObjectsByType("rDk1xiArDMjDqnrR2yWypwQAKg4mKnQYvs", "state");
// getObjectsByType("rfmPQz4eSmisCVnWJkKj82hHKQdrUPv3Px", "offer");
// URIToken 보기
// getObjectsByType("rfPMnDQEzb5StPXj3Dkd34oKY4BVAJCwsn", "uri_token");`,
zh: `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(\`总数: \${allObjects.length}\`);
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} (对手方: \${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} → 获得 \${gets}\`);
break;
case "uri_token":
const uri = Buffer.from(obj.URI || "", "hex").toString("utf8");
console.log(\` URIToken: \${uri}\`);
console.log(\` 索引: \${obj.index}\`);
break;
default:
console.log(\` \${obj.LedgerEntryType}: \${obj.index}\`);
}
}
await client.disconnect();
}
// 使用示例:
// 查看 trust lines
getObjectsByType("rDk1xiArDMjDqnrR2yWypwQAKg4mKnQYvs", "state");
// 查看 DEX 订单
// getObjectsByType("rfmPQz4eSmisCVnWJkKj82hHKQdrUPv3Px", "offer");
// 查看 URITokens
// getObjectsByType("rfPMnDQEzb5StPXj3Dkd34oKY4BVAJCwsn", "uri_token");`,
},
},
],
slides: [
{
title: { es: "Objetos del ledger", en: "Ledger objects", jp: "レジャーオブジェクト", ko: "레저 객체" },
title: { es: "Objetos del ledger", en: "Ledger objects", jp: "レジャーオブジェクト", ko: "레저 객체", zh: "账本对象" },
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: "Xahauのすべてはオブジェクトとして保存される\n\n• AccountRoot → アカウントデータ\n• RippleState → トラストライン\n• Offer → DEXの注文\n• URIToken → NFT\n• Hook → インストールされたHook\n\n各オブジェクトは一意のインデックスハッシュを持つ",
ko: "Xahau의 모든 것은 객체로 저장됩니다:\n\n• AccountRoot → 계정 데이터\n• RippleState → Trust line\n• Offer → DEX 주문\n• URIToken → NFT\n• Hook → 설치된 Hook\n\n각 객체는 고유 인덱스(hash)를 가집니다",
zh: "Xahau 中的一切都以对象形式存储:\n\n• AccountRoot → 账户数据\n• RippleState → Trust lines\n• Offer → DEX 订单\n• URIToken → NFT\n• Hook → 已安装 Hooks\n\n每个对象都有唯一索引hash",
},
visual: "🗂️",
},
{
title: { es: "Consultas por tipo", en: "Queries by type", jp: "タイプ別照会", ko: "유형별 조회" },
title: { es: "Consultas por tipo", en: "Queries by type", jp: "タイプ別照会", ko: "유형별 조회", zh: "按类型查询" },
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: "account_objects + type = 効率的なフィルタリング\n\n• type: 'state' → トラストライン\n• type: 'offer' → DEXの注文\n• type: 'uri_token' → NFT\n• type: 'hook' → Hook\n\nmarkerと組み合わせてページングする",
ko: "account_objects + type = 효율적인 필터링\n\n• type: 'state' → Trust line\n• type: 'offer' → DEX 주문\n• type: 'uri_token' → NFT\n• type: 'hook' → Hook\n\nmarker와 함께 사용해 페이지네이션",
zh: "account_objects + type = 高效过滤\n\n• type: 'state' → Trust lines\n• type: 'offer' → DEX 订单\n• type: 'uri_token' → NFT\n• type: 'hook' → Hooks\n\n可与 marker 结合进行分页",
},
visual: "🔎",
},

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@ export default {
en: "Creating and using payments",
jp: "支払いの作成と使用",
ko: "결제 생성 및 사용",
zh: "支付的创建与使用",
},
lessons: [
{
@@ -15,6 +16,7 @@ export default {
en: "Anatomy of a payment transaction",
jp: "支払いトランザクションの解剖",
ko: "결제 트랜잭션의 구조",
zh: "支付交易剖析",
},
theory: {
es: `El **Payment** es la transacción más fundamental de Xahau. Permite enviar XAH (o tokens) de una cuenta a otra.
@@ -251,6 +253,63 @@ Payment 트랜잭션에는 여기서 다루는 것보다 훨씬 더 많은 선
- 사용 가능한 플래그 (tfPartialPayment, tfLimitQuality 등)
- 오류 코드의 전체 목록 및 원인
- 특수 사례 및 고급 동작`,
zh: `**Payment** 是 Xahau 中最基础的一类交易。它允许你把 XAH或代币从一个账户发送到另一个账户。
### Payment 交易的字段
| 字段 | 说明 |
|---|---|
| \`TransactionType\` | 永远是 \`"Payment"\` |
| \`Account\` | 发送方地址(付款人) |
| \`Destination\` | 接收方地址 |
| \`Amount\` | 要发送的金额(原生 XAH 以 drops 表示) |
| \`Fee\` | 交易费用单位drops |
| \`Sequence\` | 发送账户的序列号 |
| \`NetworkID\` | 网络标识符Xahau 必需) |
### Drops 与 XAH
原生 XAH 的金额使用 **drops** 表示:
- 1 XAH = **1,000,000 drops**
- 原生 XAH 的 \`Amount\` 字段是一个表示 drops 数量的**字符串**
- 例如:\`"10000000"\` = 10 XAH
### Fees交易费用
Xahau 上的费用非常低且可预测:
- 一笔典型支付只需 **12 drops**0.000012 XAH
- 手续费会被**销毁**,不会给任何验证者
- \`xahau\` 库可以通过 \`autofill()\` 自动计算费用
### 发送 IOU代币而不是原生 XAH
发送原生 XAH 时,\`Amount\` 字段是一个表示 drops 数量的**字符串**。但发送 **IOU**(由某个账户发行的代币,例如 USD、EUR 等)时,\`Amount\` 会变成一个包含三个字段的**对象**
\`\`\`
{
"currency": "USD", // 货币代码3 个字符或 40 字节十六进制)
"issuer": "rIssuerAddress", // 发行该代币的账户
"value": "100" // 字符串形式的金额
}
\`\`\`
**发送 IOU 的前提条件:**
- **发送方必须持有该 IOU 余额**:你的账户必须拥有这种 IOU。你可以通过之前的支付、DEX 交易或直接从代币发行方获得
- **接收方必须有 TrustLine**:目标账户必须事先为该 IOU 以及相同发行方创建好 TrustLine\`TrustSet\`)。没有 TrustLine 时,支付会因 \`tecPATH_DRY\`\`tecNO_LINE\` 失败
### 为什么 XAH 之外的 IOU 或代币需要这些字段?
可能会有多个实体发行同一种 IOU。例如不同银行都可能发行自己的 EUR 或 USD 代币。如果它们共享相同的代币名称,唯一的区分方式就是指定发行方。
### 关于 Payment 的更多信息
Payment 交易还有很多这里未涉及的可选字段、flags 和潜在错误。完整参考请查看[官方文档](https://xahau.network/docs/protocol-reference/transactions/transaction-types/payment/)
在文档中你可以找到:
- 所有可选字段SendMax、DeliverMin、InvoiceID 等)
- 可用 flagstfPartialPayment、tfLimitQuality 等)
- 完整的错误代码及其原因
- 特殊情况与高级行为`,
},
codeBlocks: [
{
@@ -259,6 +318,7 @@ Payment 트랜잭션에는 여기서 다루는 것보다 훨씬 더 많은 선
en: "Send an XAH payment between two accounts",
jp: "2つのアカウント間でXAH支払いを送信",
ko: "두 계정 간 XAH 결제 전송",
zh: "在两个账户之间发送 XAH 支付",
},
language: "javascript",
code: {
@@ -421,6 +481,46 @@ async function sendPayment() {
await client.disconnect();
}
sendPayment();`,
zh: `require("dotenv").config();
const { Client, Wallet, xahToDrops } = require("xahau");
async function sendPayment() {
const client = new Client("wss://xahau-test.net");
await client.connect();
// 发送方钱包(使用你的测试网 seed如果你的 seed 不是 secp256k1请删除 ", {algorithm: 'secp256k1'}" 这一部分
const sender = Wallet.fromSeed(process.env.WALLET_SEED, {algorithm: 'secp256k1'});
// 构建支付交易
const payment = {
TransactionType: "Payment",
Account: sender.address,
Destination: "rf1NrYAsv92UPDd8nyCG4A3bez7dhYE61r",
Amount: xahToDrops(10), // 10 XAH
};
// Autofill 会自动补上 Fee、Sequence、NetworkID
const prepared = await client.autofill(payment);
console.log("已准备交易:", prepared);
// 对交易签名
const signed = sender.sign(prepared);
console.log("交易哈希:", signed.hash);
// 提交并等待验证
const result = await client.submitAndWait(signed.tx_blob);
console.log("结果:", result.result.meta.TransactionResult);
if (result.result.meta.TransactionResult === "tesSUCCESS") {
console.log("支付发送成功!");
} else {
console.log("支付出错");
}
await client.disconnect();
}
sendPayment();`,
},
},
@@ -430,6 +530,7 @@ sendPayment();`,
en: "Send an IOU (token) payment between two accounts",
jp: "2つのアカウント間でIOUトークン支払いを送信",
ko: "두 계정 간 IOU(토큰) 결제 전송",
zh: "在两个账户之间发送 IOU代币支付",
},
language: "javascript",
code: {
@@ -616,38 +717,87 @@ async function sendIOUPayment() {
await client.disconnect();
}
sendIOUPayment();`,
zh: `require("dotenv").config();
const { Client, Wallet } = require("xahau");
// 这段代码只有在你持有该 IOU且目标账户拥有有效 TrustLine 时才能工作。请根据你的测试网配置修改字段。
async function sendIOUPayment() {
const client = new Client("wss://xahau-test.net");
await client.connect();
// 发送方钱包(使用你的测试网 seed如果你的 seed 不是 secp256k1请删除 ", {algorithm: 'secp256k1'}" 这一部分
const sender = Wallet.fromSeed(process.env.WALLET_SEED, {algorithm: 'secp256k1'});
// 发送 IOU 时Amount 是一个包含 currency、issuer 和 value 的对象
// 前提:
// 1. 发送方必须持有这种 IOU 的余额
// 2. 目标账户必须拥有该 IOU 的 TrustLine
const payment = {
TransactionType: "Payment",
Account: sender.address,
Destination: "rRecipientAddress",
// 根据你想发送的代币修改 currency、issuer 和 value
Amount: {
currency: "USD",
issuer: "rTokenIssuerAddress",
value: "50", // 50 USD
},
};
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("结果:", txResult);
if (txResult === "tesSUCCESS") {
console.log("IOU 支付发送成功!");
} else if (txResult === "tecPATH_DRY") {
console.log("错误:没有支付路径。目标账户有 TrustLine 吗?");
} else if (txResult === "tecUNFUNDED_PAYMENT") {
console.log("错误:你的 IOU 余额不足。");
}
await client.disconnect();
}
sendIOUPayment();`,
},
},
],
slides: [
{
title: { es: "Transacción Payment", en: "Payment Transaction", jp: "Paymentトランザクション", ko: "Payment 트랜잭션" },
title: { es: "Transacción Payment", en: "Payment Transaction", jp: "Paymentトランザクション", ko: "Payment 트랜잭션", zh: "Payment 交易" },
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 para XAH)\n• 1 XAH = 1,000,000 drops",
en: "The most basic transaction on Xahau\n\n• Account → The sender\n• Destination → The receiver\n• Amount → Quantity (in drops for XAH)\n• 1 XAH = 1,000,000 drops",
jp: "Xahauで最も基本的なトランザクション\n\n• Account → 送信者\n• Destination → 受信者\n• Amount → 金額XAHの場合はdrops単位\n• 1 XAH = 1,000,000 drops",
ko: "Xahau에서 가장 기본적인 트랜잭션\n\n• Account → 발신자\n• Destination → 수신자\n• Amount → 금액 (XAH의 경우 drops 단위)\n• 1 XAH = 1,000,000 drops",
zh: "Xahau 中最基础的交易类型\n\n• Account → 发送方\n• Destination → 接收方\n• Amount → 金额XAH 以 drops 表示)\n• 1 XAH = 1,000,000 drops",
},
visual: "💸",
},
{
title: { es: "Envío de IOUs (tokens)", en: "Sending IOUs (tokens)", jp: "IOUトークンの送金", ko: "IOU(토큰) 전송" },
title: { es: "Envío de IOUs (tokens)", en: "Sending IOUs (tokens)", jp: "IOUトークンの送金", ko: "IOU(토큰) 전송", zh: "发送 IOU代币" },
content: {
es: "Amount pasa a ser un objeto:\n\n• currency → Código del token (USD, EUR...)\n• issuer → Cuenta emisora del token\n• value → Cantidad como string\n\nRequisitos:\n• Tener saldo del IOU\n• Destino con TrustLine activa",
en: "Amount becomes an object:\n\n• currency → Token code (USD, EUR...)\n• issuer → Token issuer account\n• value → Amount as a string\n\nRequirements:\n• Hold a balance of the IOU\n• Destination with an active TrustLine",
jp: "Amountがオブジェクトになります\n\n• currency → トークンコードUSD、EURなど\n• issuer → トークン発行アカウント\n• value → 文字列としての金額\n\n要件\n• IOUの残高を保持\n• アクティブなTrustLineを持つ宛先",
ko: "Amount가 객체가 됩니다:\n\n• currency → 토큰 코드 (USD, EUR...)\n• issuer → 토큰 발행 계정\n• value → 문자열로 된 금액\n\n요건:\n• IOU 잔액 보유\n• 활성 TrustLine이 있는 목적지",
zh: "Amount 会变成一个对象:\n\n• currency → 代币代码USD、EUR...\n• issuer → 代币发行账户\n• value → 字符串形式的金额\n\n前提条件\n• 持有该 IOU 余额\n• 目标账户拥有有效 TrustLine",
},
visual: "🪙",
},
{
title: { es: "Documentación oficial", en: "Official documentation", jp: "公式ドキュメント", ko: "공식 문서" },
title: { es: "Documentación oficial", en: "Official documentation", jp: "公式ドキュメント", ko: "공식 문서", zh: "官方文档" },
content: {
es: "Referencia completa de Payment:\ https://xahau.network/docs/technical/protocol-reference/transactions/transaction-types/payment\n\n• Campos opcionales (SendMax, DeliverMin...)\n• Flags (tfPartialPayment, tfLimitQuality...)\n• Códigos de error completos\n• Casos especiales y avanzados",
en: "Complete Payment reference:\ https://xahau.network/docs/technical/protocol-reference/transactions/transaction-types/payment\n\n• Optional fields (SendMax, DeliverMin...)\n• Flags (tfPartialPayment, tfLimitQuality...)\n• Complete error codes\n• Special cases and advanced behaviors",
jp: "Paymentの完全リファレンス\nhttps://xahau.network/docs/...\n\n• オプションフィールドSendMax、DeliverMinなど\n• フラグtfPartialPayment、tfLimitQualityなど\n• 完全なエラーコード\n• 特殊ケースと高度な動作",
ko: "Payment 전체 참조:\nhttps://xahau.network/docs/technical/protocol-reference/transactions/transaction-types/payment\n\n• 선택적 필드 (SendMax, DeliverMin...)\n• 플래그 (tfPartialPayment, tfLimitQuality...)\n• 전체 오류 코드\n• 특수 사례 및 고급 동작",
zh: "Payment 完整参考:\nhttps://xahau.network/docs/technical/protocol-reference/transactions/transaction-types/payment\n\n• 可选字段SendMax、DeliverMin...\n• FlagstfPartialPayment、tfLimitQuality...\n• 完整错误代码\n• 特殊情况与高级行为",
},
visual: "📖",
},
@@ -660,6 +810,7 @@ sendIOUPayment();`,
en: "Payments with Destination Tag and memos",
jp: "Destination TagとMemoを使った支払い",
ko: "Destination Tag와 메모를 사용한 결제",
zh: "带 Destination Tag 和 memos 的支付",
},
theory: {
es: `Además del pago básico, Xahau soporta campos adicionales que permiten añadir contexto y funcionalidad a los pagos.
@@ -778,6 +929,35 @@ Each transaction returns a result code:
- \`tecNO_DST\`: 목적지 계정이 존재하지 않음
- \`tecDST_TAG_NEEDED\`: Destination Tag 필요
- \`tecNO_DST_INSUF_XAH\`: 목적지에 예비금을 위한 XAH가 부족`,
zh: `除了基础支付之外Xahau 还支持一些额外字段,让支付能够携带更多上下文和功能。
### Destination Tag
**Destination Tag** 是一个整数,用来让接收方识别单独的付款。它特别适用于:
- **交易所**:识别一笔充值属于哪个用户
- **服务平台**:把一笔支付与订单或发票关联起来
- 如果某个账户启用了 \`RequireDestTag\` 标志,**你就不能在没有 tag 的情况下向它付款**
系统允许 Destination Tag 使用最多 32 位,也就是说你可以使用最大到 4,294,967,295 的整数。在发送支付之前,务必先和接收方确认正确的 Destination Tag因为如果向要求 tag 的账户发送无 tag 或错误 tag 的付款,可能会导致资金丢失。
另外还有 **Source Tag**它对发送方起到类似作用。不过在实际使用中Destination Tag 更常见,也更广泛。
### Memos
**Memos** 允许你在交易中附加任意数据:
- \`MemoType\`memo 类型(例如 "text/plain"、"application/json"
- \`MemoData\`memo 内容
- memos 会被编码为**十六进制**
- 它们是公开的,账本上的任何人都能看到
### 交易结果
每笔交易都会返回一个结果代码:
- \`tesSUCCESS\`:交易成功
- \`tecUNFUNDED_PAYMENT\`:余额不足
- \`tecNO_DST\`:目标账户不存在
- \`tecDST_TAG_NEEDED\`:必须提供 Destination Tag
- \`tecNO_DST_INSUF_XAH\`:目标账户没有足够的 XAH 用于 reserve`,
},
codeBlocks: [
{
@@ -786,6 +966,7 @@ Each transaction returns a result code:
en: "Payment with Source Tag, Destination Tag, and Memos",
jp: "Source Tag、Destination Tag、Memoを使った支払い",
ko: "Source Tag, Destination Tag 및 메모를 사용한 결제",
zh: "带 Source Tag、Destination Tag 和 Memos 的支付",
},
language: "javascript",
code: {
@@ -1084,6 +1265,80 @@ async function sendPaymentWithMemo() {
await client.disconnect();
}
sendPaymentWithMemo();`,
zh: `require("dotenv").config();
const { Client, Wallet, xahToDrops } = require("xahau");
// 把文本转成十六进制的辅助函数
function toHex(str) {
return Buffer.from(str, "utf8").toString("hex").toUpperCase();
}
function hexToString(hex) {
if (!hex) return null;
return Buffer.from(hex, "hex").toString("utf8");
}
async function sendPaymentWithMemo() {
const client = new Client("wss://xahau-test.net");
await client.connect();
// 发送方钱包(使用你的测试网 seed如果你的 seed 不是 secp256k1请删除 ", {algorithm: 'secp256k1'}" 这一部分
const sender = Wallet.fromSeed(process.env.WALLET_SEED, {
algorithm: "secp256k1",
});
const payment = {
TransactionType: "Payment",
Account: sender.address,
Destination: "rf1NrYAsv92UPDd8nyCG4A3bez7dhYE61r",
Amount: xahToDrops(5), // 5 XAH
SourceTag: 1, // 用于标识付款的发送方标签
DestinationTag: 12345, // 用于标识付款的目标标签
Memos: [
{
Memo: {
MemoType: toHex("text/plain"),
MemoData: toHex("Xahau 课程付款"),
},
},
],
};
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("结果:", txResult);
if (txResult === "tesSUCCESS") {
console.log("带 memo 的支付已发送!");
console.log("Hash:", signed.hash);
const lookup = await client.request({
command: "tx",
transaction: signed.hash,
});
const tx = lookup.result.tx_json ?? lookup.result;
console.log("Source Tag:", tx.SourceTag);
console.log("Destination Tag:", tx.DestinationTag);
if (tx.Memos) {
tx.Memos.forEach((memoWrapper, index) => {
const memo = memoWrapper.Memo;
const memoType = hexToString(memo.MemoType);
const memoData = hexToString(memo.MemoData);
console.log("MemoType:", memoType);
console.log("MemoData:", memoData);
});
}
}
await client.disconnect();
}
sendPaymentWithMemo();`,
},
},
@@ -1093,6 +1348,7 @@ sendPaymentWithMemo();`,
en: "Verify a received payment",
jp: "受信した支払いを確認",
ko: "수신된 결제 확인",
zh: "验证一笔收到的支付",
},
language: "javascript",
code: {
@@ -1243,38 +1499,78 @@ async function verifyPayment(txHash) {
await client.disconnect();
}
// 트랜잭션 해시 예시: "4B56BD61E7E7F59FF191A779FC0C9ACF68DC25C174930FCB906AC06EB812F38C"
verifyPayment("YOUR_TRANSACTION_HASH_HERE");`,
zh: `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("=== 支付详情 ===");
console.log("类型:", tx.TransactionType);
console.log("从:", tx.Account);
console.log("到:", tx.Destination);
console.log("金额:", Number(tx.Amount) / 1_000_000, "XAH");
console.log("Fee:", Number(tx.Fee) / 1_000_000, "XAH");
console.log("结果:", 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();
}
// 示例交易哈希: "4B56BD61E7E7F59FF191A779FC0C9ACF68DC25C174930FCB906AC06EB812F38C"
verifyPayment("YOUR_TRANSACTION_HASH_HERE");`,
},
},
],
slides: [
{
title: { es: "Destination Tag", en: "Destination Tag", jp: "Destination Tag", ko: "Destination Tag" },
title: { es: "Destination Tag", en: "Destination Tag", jp: "Destination Tag", ko: "Destination Tag", zh: "Destination Tag" },
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: "A number to identify individual payments\n\n• Used by exchanges and services\n• Associates payments with users/orders\n• Some accounts require it\n• It is an integer (uint32)",
jp: "個別の支払いを識別する番号\n\n• 取引所やサービスで使用\n• 支払いをユーザー/注文と関連付け\n• 一部のアカウントでは必須\n• 整数uint32",
ko: "개별 결제를 식별하는 번호\n\n• 거래소 및 서비스에서 사용\n• 결제를 사용자/주문과 연결\n• 일부 계정에서는 필수\n• 정수 (uint32)",
zh: "用于识别单笔付款的编号\n\n• 交易所和服务平台常用\n• 可把付款与用户/订单关联起来\n• 某些账户强制要求它\n• 它是一个整数uint32",
},
visual: "🏷️",
},
{
title: { es: "Memos", en: "Memos", jp: "Memo", ko: "메모" },
title: { es: "Memos", en: "Memos", jp: "Memo", ko: "메모", zh: "Memos" },
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: "Data attached to a transaction\n\n• MemoType → Type (text/plain, etc.)\n• MemoData → Content\n• Encoded in hexadecimal\n• Public on the ledger",
jp: "トランザクションに添付するデータ\n\n• MemoType → タイプtext/plainなど\n• MemoData → コンテンツ\n• 16進数でエンコード\n• レジャー上でパブリック",
ko: "트랜잭션에 첨부된 데이터\n\n• MemoType → 유형 (text/plain 등)\n• MemoData → 내용\n• 16진수로 인코딩\n• 레저에서 공개",
zh: "附加在交易上的数据\n\n• MemoType → 类型text/plain 等)\n• MemoData → 内容\n• 以十六进制编码\n• 在账本上公开可见",
},
visual: "📝",
},
{
title: { es: "Seguridad del DestinationTag", en: "Destination Tag security", jp: "Destination Tagのセキュリティ", ko: "Destination Tag 보안" },
title: { es: "Seguridad del DestinationTag", en: "Destination Tag security", jp: "Destination Tagのセキュリティ", ko: "Destination Tag 보안", zh: "Destination Tag 安全" },
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: "• RequireDestTag flag on the destination account\n• No tag → error tecDST_TAG_NEEDED\n• Exchanges require a tag for deposits\n• Wrong or missing tag = lost funds\n• Always validate the tag before sending\n• Handle errors: tecNO_DST, tecUNFUNDED",
jp: "• 宛先アカウントのRequireDestTagフラグ\n• タグなし → tecDST_TAG_NEEDEDエラー\n• 取引所は入金にタグを要求\n• タグ誤りや欠落 = 資金消失\n• 送信前にタグを常に確認\n• エラーを処理tecNO_DST、tecUNFUNDED",
ko: "• 목적지 계정의 RequireDestTag 플래그\n• 태그 없음 → tecDST_TAG_NEEDED 오류\n• 거래소는 입금에 태그 필요\n• 잘못되거나 누락된 태그 = 자금 손실\n• 전송 전 항상 태그 확인\n• 오류 처리: tecNO_DST, tecUNFUNDED",
zh: "• 目标账户上的 RequireDestTag 标志\n• 没有 tag → 报错 tecDST_TAG_NEEDED\n• 交易所充值通常要求 tag\n• tag 错误或缺失 = 资金丢失\n• 发送前务必确认 tag\n• 处理错误tecNO_DST、tecUNFUNDED",
},
visual: "🔒",
},
@@ -1287,6 +1583,7 @@ verifyPayment("YOUR_TRANSACTION_HASH_HERE");`,
en: "Cross-currency payments and pathfinding",
jp: "クロスカレンシー支払いとパスファインディング",
ko: "크로스 커런시 결제 및 경로 탐색",
zh: "跨币种支付与路径查找",
},
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**.
@@ -1429,38 +1726,76 @@ The \`tfPartialPayment\` flag (value: \`0x00020000\`) allows a payment to delive
- 쿼리와 실행 사이에 유동성이 변할 수 있을 때 유용
- \`DeliverMin\`을 사용하여 허용 가능한 최소값 설정
- **중요**: 결제를 받을 때, \`Amount\` 필드가 아니라 메타데이터의 \`delivered_amount\`를 항상 확인하세요. 공격자가 높은 \`Amount\`를 표시하지만 훨씬 적게 전달하는 부분 결제를 보낼 수 있습니다`,
zh: `Xahau 不仅支持发送原生 XAH 或相同类型的代币,还支持 **跨币种支付**。也就是说,发送方可以发送一种货币,而接收方收到另一种货币。这依赖于**内置 DEX**和**路径查找pathfinding**系统。
### 跨币种支付
例如,跨币种支付可以让发送方支付 XAH而接收方收到 USD。Xahau 会自动通过 DEX 找到最优路径来完成货币转换。
### 路径查找系统
路径查找是用于寻找货币转换路线的机制:
- Xahau 会通过 trust lines 和 DEX 订单搜索**路径**
- 它可以串联多个中间转换步骤
- 它始终尝试找到当前可用的**最佳汇率**
### 跨币种支付中的关键字段
| 字段 | 说明 |
|---|---|
| \`Amount\` | 接收方应收到的金额(目标货币) |
| \`SendMax\` | 发送方愿意支付的最大金额(源货币) |
| \`DeliverMin\` | 接收方最少必须收到的金额(部分支付时) |
| \`Paths\` | 由路径查找发现的转换路径 |
### ripple_path_find 命令
在发送跨币种支付之前,可用 \`ripple_path_find\` 来:
- 检查两种货币之间是否存在路径
- 获取交易所需的 \`Paths\`
- 了解预估成本(\`source_amount\`
### 部分支付tfPartialPayment
\`tfPartialPayment\` 标志(值:\`0x00020000\`)允许支付实际交付**少于** \`Amount\` 中指定的金额:
- 当查询与执行之间流动性可能变化时非常有用
- 使用 \`DeliverMin\` 来设置可接受的最小值
- **重要**:接收支付时,必须检查 metadata 中的 \`delivered_amount\`,而**不是** \`Amount\` 字段。攻击者可能发起一笔显示较高 \`Amount\`,但实际交付远少于该值的部分支付`,
},
codeBlocks: [
],
slides: [
{
title: { es: "Pagos cross-currency", en: "Cross-currency payments", jp: "クロスカレンシー支払い", ko: "크로스 커런시 결제" },
title: { es: "Pagos cross-currency", en: "Cross-currency payments", jp: "クロスカレンシー支払い", ko: "크로스 커런시 결제", zh: "跨币种支付" },
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: "Send one currency, the receiver gets another\n\n• The built-in DEX converts automatically\n• Amount = what the receiver gets\n• SendMax = maximum the sender pays\n• Paths = conversion routes",
jp: "一つの通貨を送り、受信者は別の通貨を受け取る\n\n• 組み込みDEXが自動的に変換\n• Amount = 受信者が受け取る額\n• SendMax = 送信者が支払う最大額\n• Paths = 変換ルート",
ko: "한 통화를 보내면 수신자는 다른 통화를 받음\n\n• 내장 DEX가 자동으로 변환\n• Amount = 수신자가 받는 금액\n• SendMax = 발신자가 지불하는 최대 금액\n• Paths = 변환 경로",
zh: "发送一种货币,接收方得到另一种\n\n• 内置 DEX 会自动完成兑换\n• Amount = 接收方实际收到的金额\n• SendMax = 发送方最多支付的金额\n• Paths = 转换路径",
},
visual: "🔄",
},
{
title: { es: "Pathfinding", en: "Pathfinding", jp: "パスファインディング", ko: "경로 탐색" },
title: { es: "Pathfinding", en: "Pathfinding", jp: "パスファインディング", ko: "경로 탐색", zh: "路径查找" },
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: "ripple_path_find searches for conversion routes\n\n1. Specify source and destination accounts\n2. Specify the destination currency and amount\n3. Get alternatives with estimated cost\n4. Use paths_computed in your Payment",
jp: "ripple_path_findが変換ルートを検索\n\n1. 送信元と宛先アカウントを指定\n2. 宛先通貨と金額を指定\n3. 推定コストの代替案を取得\n4. PaymentでPaths computedを使用",
ko: "ripple_path_find가 변환 경로를 탐색\n\n1. 원본 및 목적지 계정 지정\n2. 목적지 통화 및 금액 지정\n3. 예상 비용과 함께 대안 획득\n4. Payment에서 paths_computed 사용",
zh: "ripple_path_find 用于搜索转换路径\n\n1. 指定源账户和目标账户\n2. 指定目标货币与金额\n3. 获取带预估成本的可选路径\n4. 在 Payment 中使用 paths_computed",
},
visual: "🗺️",
},
{
title: { es: "Pagos parciales", en: "Partial payments", jp: "部分支払い", ko: "부분 결제" },
title: { es: "Pagos parciales", en: "Partial payments", jp: "部分支払い", ko: "부분 결제", zh: "部分支付" },
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: "tfPartialPayment flag allows delivering less\n\n• Useful when liquidity varies\n• DeliverMin = acceptable minimum\n• ALWAYS verify delivered_amount\n• NEVER trust the Amount field\n\n⚠ Security risk if not verified",
jp: "tfPartialPaymentフラグで少なく配信可能\n\n• 流動性が変わる場合に便利\n• DeliverMin = 許容できる最小額\n• 常にdelivered_amountを確認\n• Amountフィールドは信頼しない\n\n⚠ 確認しないとセキュリティリスク",
ko: "tfPartialPayment 플래그로 적게 전달 가능\n\n• 유동성이 변할 때 유용\n• DeliverMin = 허용 가능한 최솟값\n• 항상 delivered_amount 확인\n• Amount 필드를 절대 신뢰하지 말 것\n\n⚠ 확인하지 않으면 보안 위험",
zh: "tfPartialPayment 标志允许少量交付\n\n• 在流动性变化时很有用\n• DeliverMin = 可接受的最低金额\n• 一定要检查 delivered_amount\n• 永远不要相信 Amount 字段\n\n⚠ 不检查会带来安全风险",
},
visual: "⚠️",
},

View File

@@ -6,6 +6,7 @@ export default {
en: "Creating and managing your own tokens",
jp: "独自トークンの作成と管理",
ko: "나만의 토큰 생성 및 관리",
zh: "创建和管理自定义代币",
},
lessons: [
{
@@ -15,6 +16,7 @@ export default {
en: "TrustLines and the token model in Xahau",
jp: "トラストラインとXahauのトークンモデル",
ko: "TrustLine과 Xahau의 토큰 모델",
zh: "TrustLine 与 Xahau 的代币模型",
},
theory: {
es: `En Xahau, 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).
@@ -165,6 +167,16 @@ Xahauのトークンシステムの利点の一つは、発行アカウントが
- 발행자는 직접 토큰을 “민팅”하기보다 잔액 관계를 생성합니다
Xahau 토큰 모델을 이해하려면 “토큰 컨트랙트”가 아니라 “계정 간 관계”라는 관점이 중요합니다.`,
zh: `Xahau 的发行型代币与 Ethereum 的 ERC-20 不同。用户在接收代币之前,必须先建立 **TrustLine**,它表示发行方与接收方之间的信任关系。
### 核心概念
- 代币由发行账户定义
- 接收方先通过 \`TrustSet\` 创建 TrustLine
- TrustLine 包含额度、状态和标志
- 发行方不是直接“铸造”代币,而是建立余额关系
理解 Xahau 的代币模型时,重点不是“代币合约”,而是“账户之间的关系”。`,
},
codeBlocks: [
{
@@ -173,6 +185,7 @@ Xahau 토큰 모델을 이해하려면 “토큰 컨트랙트”가 아니라
en: "Create a TrustLine toward a token issuer",
jp: "トークン発行者へのTrustLineを作成する",
ko: "토큰 발행자에 대한 TrustLine 생성",
zh: "为代币发行方创建 TrustLine",
},
language: "javascript",
code: {
@@ -319,6 +332,42 @@ async function createTrustLine() {
await client.disconnect();
}
createTrustLine();`,
zh: `require("dotenv").config();
const { Client, Wallet } = require("xahau");
async function createTrustLine() {
const client = new Client("wss://xahau-test.net");
await client.connect();
// 想要接收代币的接收者钱包
const receiver = Wallet.fromSeed(process.env.WALLET_SEED, {algorithm: 'secp256k1'});
// 创建 TrustLine: “我最多信任该发行方 1,000,000 单位”
const trustSet = {
TransactionType: "TrustSet",
Account: receiver.address,
LimitAmount: {
currency: "YourTokenName",
issuer: "YourIssuerAddress",
value: "1000000", // 我愿意接受的最大额度
},
};
const prepared = await client.autofill(trustSet);
const signed = receiver.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
console.log("结果:", result.result.meta.TransactionResult);
if (result.result.meta.TransactionResult === "tesSUCCESS") {
console.log("TrustLine 创建成功!");
console.log("现在你的账户 " + receiver.address + " 可以接收该发行方的代币了");
}
await client.disconnect();
}
createTrustLine();`,
},
},
@@ -328,6 +377,7 @@ createTrustLine();`,
en: "Issue (send) tokens to an account with a TrustLine",
jp: "TrustLineを持つアカウントへトークンを発行送信する",
ko: "TrustLine이 있는 계정에 토큰 발행(전송)",
zh: "向已建立 TrustLine 的账户发行代币",
},
language: "javascript",
code: {
@@ -478,38 +528,78 @@ async function issueTokens() {
await client.disconnect();
}
issueTokens();`,
zh: `// 如果你并不持有要发送的代币,这段代码会失败
require("dotenv").config();
const { Client, Wallet } = require("xahau");
async function issueTokens() {
const client = new Client("wss://xahau-test.net");
await client.connect();
// 代币发行方钱包
const issuer = Wallet.fromSeed(process.env.WALLET_SEED, {algorithm: 'secp256k1'});
// 向接收方发送 100 USD对方已拥有 TrustLine
const payment = {
TransactionType: "Payment",
Account: issuer.address,
Destination: "rRecipientAddress",
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("结果:", result.result.meta.TransactionResult);
if (result.result.meta.TransactionResult === "tesSUCCESS") {
console.log("代币发行成功!");
}
await client.disconnect();
}
issueTokens();`,
},
},
],
slides: [
{
title: { es: "Modelo de tokens en Xahau", en: "Token model in Xahau", jp: "Xahauのトークンモデル", ko: "Xahau의 토큰 모델" },
title: { es: "Modelo de tokens en Xahau", en: "Token model in Xahau", jp: "Xahauのトークンモデル", ko: "Xahau의 토큰 모델", zh: "Xahau 的代币模型" },
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: "No smart contracts needed to create tokens\n\n1⃣ Issuer: Any account\n2⃣ TrustLine: Recipient opts-in\n3⃣ Payment: Native transfer\n\nTokens = currency + issuer",
jp: "トークン作成にスマートコントラクト不要\n\n1⃣ 発行者:どのアカウントでも可\n2⃣ TrustLine受取人がオプトイン\n3⃣ Paymentネイティブ転送\n\nトークン = currency + issuer",
ko: "토큰 생성에 스마트 컨트랙트가 필요 없음\n\n1⃣ 발행자: 어떤 계정이든 가능\n2⃣ TrustLine: 수신자가 opt-in\n3⃣ Payment: 네이티브 전송\n\n토큰 = currency + issuer",
zh: "创建代币不需要智能合约\n\n1⃣ 发行方:任何账户都可以\n2⃣ TrustLine接收方主动选择接收\n3⃣ Payment原生转账\n\n代币 = currency + issuer",
},
visual: "🪙",
},
{
title: { es: "TrustLine = Opt-in", en: "TrustLine = Opt-in", jp: "TrustLine = オプトイン", ko: "TrustLine = Opt-in" },
title: { es: "TrustLine = Opt-in", en: "TrustLine = Opt-in", jp: "TrustLine = オプトイン", ko: "TrustLine = Opt-in", zh: "TrustLine = 主动加入" },
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: "The recipient CHOOSES to receive a token\n\n• Creates a TrustLine toward the issuer\n• Defines the maximum limit\n• Consumes owner reserve\n• Protects against token spam",
jp: "受取人がトークンを受け取ることを選択する\n\n• 発行者へのTrustLineを作成\n• 最大限度額を定義\n• オーナーリザーブを消費\n• トークンスパムから保護",
ko: "수신자가 토큰 수령을 직접 선택함\n\n• 발행자에 대한 TrustLine 생성\n• 최대 한도 정의\n• owner reserve 소비\n• 토큰 스팸 방지",
zh: "接收方主动选择是否接收代币\n\n• 向发行方创建 TrustLine\n• 定义最大信任额度\n• 会占用 owner reserve\n• 防止代币垃圾信息",
},
visual: "🤝",
},
{
title: { es: "Sistema de reservas", en: "Reserve system", jp: "リザーブシステム", ko: "Reserve 시스템" },
title: { es: "Sistema de reservas", en: "Reserve system", jp: "リザーブシステム", ko: "Reserve 시스템", zh: "储备机制" },
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: "Each TrustLine increases the account reserve\n\n• Base reserve + per-object reserve\n• More TrustLines = more XAH locked\n• Users must plan their TrustLines\n• Removing a TrustLine (balance 0) frees reserve\n• Direct impact on available XAH",
jp: "各TrustLineはアカウントリザーブを増やします\n\n• ベースリザーブ+オブジェクトごとのリザーブ\n• TrustLineが多いほどXAHがロックされる\n• ユーザーはTrustLineを計画的に\n• TrustLine削除残高0でリザーブが解放\n• 利用可能XAHへの直接影響",
ko: "각 TrustLine은 계정 reserve를 증가시킴\n\n• 기본 reserve + 객체당 reserve\n• TrustLine이 많을수록 더 많은 XAH가 잠김\n• 사용자는 TrustLine을 계획적으로 만들어야 함\n• TrustLine 제거(잔액 0) 시 reserve 해제\n• 사용 가능한 XAH에 직접 영향",
zh: "每条 TrustLine 都会增加账户储备\n\n• 基础储备 + 每个对象的额外储备\n• TrustLine 越多,锁定的 XAH 越多\n• 用户应规划好自己的 TrustLine\n• 删除 TrustLine余额为 0可释放储备\n• 会直接影响可用的 XAH",
},
visual: "💎",
},
@@ -522,6 +612,7 @@ issueTokens();`,
en: "Complete process: create and distribute your own token",
jp: "完全なプロセス:独自トークンの作成と配布",
ko: "전체 과정: 나만의 토큰 생성 및 배포",
zh: "完整流程:创建并分发你的代币",
},
theory: {
es: `Ahora que entiendes cómo funcionan las TrustLines, vamos a ver el proceso completo para crear tu propio token y distribuirlo. A diferencia de otras blockchains, en Xahau **no necesitas desplegar ningún contrato**. El proceso se realiza enteramente con transacciones nativas.
@@ -678,6 +769,19 @@ console.log(currencyToHex("EURZ"));
### 왜 두 개의 계정을 쓰기도 하나요?
발행자와 운영 계정을 분리하면 보안과 운영 관리가 쉬워집니다. 실무에서는 발행 계정을 더 엄격하게 보호하는 경우가 많습니다.`,
zh: `要创建并分发自己的代币,通常需要几个步骤。它不只是“发行代币”,还包括账户配置与接收方准备。
### 常见流程
1. 准备发行账户和运营账户
2. 设置必要标志(如 \`DefaultRipple\`\`RequireAuth\`
3. 接收方创建 TrustLine
4. 发行方向外发送代币
5. 用区块浏览器或 \`account_lines\` 检查状态
### 为什么经常使用两个账户?
把发行账户和运营账户分开,能让安全和日常运营更容易管理。实际项目里,发行账户通常会受到更严格的保护。`,
},
codeBlocks: [
{
@@ -686,6 +790,7 @@ console.log(currencyToHex("EURZ"));
en: "Complete process: configure issuer, create TrustLine, issue and distribute token",
jp: "完全なプロセス:発行者の設定、トラストラインの作成、トークンの発行と配布",
ko: "전체 과정: 발행자 설정, TrustLine 생성, 토큰 발행 및 배포",
zh: "完整流程:配置发行方、创建 TrustLine、发行并分发代币",
},
language: "javascript",
code: {
@@ -1288,38 +1393,176 @@ async function createAndDistributeToken() {
await client.disconnect();
}
createAndDistributeToken();`,
zh: `require("dotenv").config();
const { Client, Wallet, xahToDrops } = require("xahau");
// 你需要两个在测试网上有资金的钱包,并在 .env 中定义:
// ISSUER_SEED → 代币发行账户
// RESERVE_SEED → 储备/分发账户
// 你可以从水龙头获取测试资金https://xahau-test.net
// 如果 token_currency 超过 3 个字符,则转成 40 位十六进制
function normalizeCurrency(token_currency) {
if (typeof token_currency !== "string") return token_currency;
const cur = token_currency.trim();
if (cur.length <= 3) return cur;
const hex = Buffer.from(cur, "utf8").toString("hex").toUpperCase();
if (hex.length > 40) {
throw new Error(
\`token_currency 太长: "\${cur}" -> hex \${hex.length} (>40). UTF-8 最多约 20 bytes。\`
);
}
return hex.padEnd(40, "0");
}
async function createAndDistributeToken() {
const client = new Client("wss://xahau-test.net");
await client.connect();
// === 账户 ===
const issuer = Wallet.fromSeed(process.env.ISSUER_SEED, {algorithm: 'secp256k1'});
const reserve = Wallet.fromSeed(process.env.RESERVE_SEED, {algorithm: 'secp256k1'});
const TOKEN_CURRENCY_INPUT = "YourTokenName";
const TOTAL_SUPPLY = "1000000";
const TOKEN_CURRENCY = normalizeCurrency(TOKEN_CURRENCY_INPUT);
console.log("=== 创建代币 ===");
console.log("发行方:", issuer.address);
console.log("储备账户:", reserve.address);
console.log("代币:", TOKEN_CURRENCY);
console.log("总供应量:", TOTAL_SUPPLY);
// === 步骤 1为发行方开启 DefaultRipple ===
console.log("--- 步骤 1为发行方配置 DefaultRipple ---");
const accountSet = {
TransactionType: "AccountSet",
Account: issuer.address,
SetFlag: 8,
};
const prep1 = await client.autofill(accountSet);
const signed1 = issuer.sign(prep1);
const result1 = await client.submitAndWait(signed1.tx_blob);
console.log("DefaultRipple:", result1.result.meta.TransactionResult);
if (result1.result.meta.TransactionResult !== "tesSUCCESS") {
console.log("发行方配置失败,终止。");
await client.disconnect();
return;
}
// === 步骤 2储备账户为发行方创建 TrustLine ===
console.log("--- 步骤 2创建 TrustLinereserve → issuer---");
const trustSet = {
TransactionType: "TrustSet",
Account: reserve.address,
LimitAmount: {
currency: TOKEN_CURRENCY,
issuer: issuer.address,
value: TOTAL_SUPPLY,
},
};
const prep2 = await client.autofill(trustSet);
const signed2 = reserve.sign(prep2);
const result2 = await client.submitAndWait(signed2.tx_blob);
console.log("TrustLine:", result2.result.meta.TransactionResult);
if (result2.result.meta.TransactionResult !== "tesSUCCESS") {
console.log("创建 TrustLine 失败,终止。");
await client.disconnect();
return;
}
// === 步骤 3发行方向储备账户发送全部供应量 ===
console.log("--- 步骤 3发行代币issuer → reserve---");
const issuePayment = {
TransactionType: "Payment",
Account: issuer.address,
Destination: reserve.address,
Amount: {
currency: TOKEN_CURRENCY,
issuer: issuer.address,
value: TOTAL_SUPPLY,
},
};
const prep3 = await client.autofill(issuePayment);
const signed3 = issuer.sign(prep3);
const result3 = await client.submitAndWait(signed3.tx_blob);
console.log("发行结果:", result3.result.meta.TransactionResult);
if (result3.result.meta.TransactionResult !== "tesSUCCESS") {
console.log("代币发行失败,终止。");
await client.disconnect();
return;
}
console.log("代币已创建并分发到储备账户!");
console.log("总供应量:", TOTAL_SUPPLY, TOKEN_CURRENCY);
// === 验证 ===
console.log("--- 验证 ---");
const lines = await client.request({
command: "account_lines",
account: reserve.address,
ledger_index: "validated",
});
const tokenLine = lines.result.lines.find(
(l) => l.currency === TOKEN_CURRENCY && l.account === issuer.address
);
if (tokenLine) {
console.log("储备余额:", tokenLine.balance, TOKEN_CURRENCY);
console.log("发行方:", tokenLine.account);
console.log("额度:", tokenLine.limit, TOKEN_CURRENCY);
}
await client.disconnect();
}
createAndDistributeToken();`,
},
},
],
slides: [
{
title: { es: "Proceso de creación de un token", en: "Token creation process", jp: "トークン作成プロセス", ko: "토큰 생성 과정" },
title: { es: "Proceso de creación de un token", en: "Token creation process", jp: "トークン作成プロセス", ko: "토큰 생성 과정", zh: "代币创建流程" },
content: {
es: "No necesitas smart contracts\n\n1⃣ Configurar emisor (DefaultRipple)\n2⃣ Crear TrustLine desde cuenta reserva\n3⃣ Emitir supply (Payment del emisor)\n4⃣ Distribuir a usuarios finales\n\nTodo con transacciones nativas",
en: "No smart contracts needed\n\n1⃣ Configure issuer (DefaultRipple)\n2⃣ Create TrustLine from reserve account\n3⃣ Issue supply (Payment from issuer)\n4⃣ Distribute to end users\n\nAll with native transactions",
jp: "スマートコントラクト不要\n\n1⃣ 発行者を設定DefaultRipple\n2⃣ リザーブアカウントからTrustLineを作成\n3⃣ サプライを発行発行者からPayment\n4⃣ エンドユーザーに配布\n\nすべてネイティブトランザクションで",
ko: "스마트 컨트랙트가 필요 없음\n\n1⃣ 발행자 설정 (DefaultRipple)\n2⃣ reserve 계정에서 TrustLine 생성\n3⃣ 공급량 발행 (발행자의 Payment)\n4⃣ 최종 사용자에게 배포\n\n모두 네이티브 트랜잭션으로 처리",
zh: "不需要智能合约\n\n1⃣ 配置发行方DefaultRipple\n2⃣ 从储备账户创建 TrustLine\n3⃣ 发行供应量(发行方发起 Payment\n4⃣ 分发给最终用户\n\n全部使用原生交易完成",
},
visual: "🏭",
},
{
title: { es: "Dos cuentas: emisor + reserva", en: "Two accounts: issuer + reserve", jp: "2つのアカウント発行者 + リザーブ", ko: "두 개의 계정: 발행자 + reserve" },
title: { es: "Dos cuentas: emisor + reserva", en: "Two accounts: issuer + reserve", jp: "2つのアカウント発行者 + リザーブ", ko: "두 개의 계정: 발행자 + reserve", zh: "两个账户:发行方 + 储备账户" },
content: {
es: "Buena práctica: separar responsabilidades\n\n• Emisor: solo configura y emite\n → Proteger con multi-sign\n → Desactivar clave maestra\n\n• Reserva: opera día a día\n → Distribuye a usuarios\n → Vende en el DEX\n\nSi la reserva se compromete, el emisor puede congelar",
en: "Best practice: separate responsibilities\n\n• Issuer: only configures and issues\n -> Protect with multi-sign\n -> Disable master key\n\n• Reserve: day-to-day operations\n -> Distributes to users\n -> Sells on the DEX\n\nIf reserve is compromised, the issuer can freeze",
jp: "ベストプラクティス:責任を分離\n\n• 発行者:設定と発行のみ\n -> マルチサインで保護\n -> マスターキーを無効化\n\n• リザーブ:日常業務\n -> ユーザーへの配布\n -> DEXでの販売\n\nリザーブが侵害されたら発行者が凍結可能",
ko: "모범 사례: 역할 분리\n\n• 발행자: 설정과 발행만 담당\n → 멀티서명으로 보호\n → 마스터 키 비활성화 가능\n\n• Reserve: 일상 운영 담당\n → 사용자에게 배포\n → DEX에서 판매\n\nreserve가 침해되면 발행자가 동결 가능",
zh: "最佳实践:分离职责\n\n• 发行方:只负责配置与发行\n → 用多重签名保护\n → 可停用主密钥\n\n• 储备账户:负责日常运营\n → 向用户分发\n → 在 DEX 上出售\n\n如果储备账户被攻破发行方还能冻结代币",
},
visual: "🔐",
},
{
title: { es: "Resumen de transacciones", en: "Transaction summary", jp: "トランザクションまとめ", ko: "트랜잭션 요약" },
title: { es: "Resumen de transacciones", en: "Transaction summary", jp: "トランザクションまとめ", ko: "트랜잭션 요약", zh: "交易总结" },
content: {
es: "AccountSet → DefaultRipple en emisor\nTrustSet → Reserva confía en emisor\nPayment → Emisor envía supply a reserva\nPayment → Reserva distribuye a usuarios\n\nUsuarios finales necesitan TrustLine\nantes de poder recibir el token",
en: "AccountSet -> DefaultRipple on issuer\nTrustSet -> Reserve trusts issuer\nPayment -> Issuer sends supply to reserve\nPayment -> Reserve distributes to users\n\nEnd users need a TrustLine\nbefore they can receive the token",
jp: "AccountSet -> 発行者にDefaultRipple\nTrustSet -> リザーブが発行者を信頼\nPayment -> 発行者がリザーブにサプライを送信\nPayment -> リザーブがユーザーに配布\n\nエンドユーザーはトークンを\n受け取る前にTrustLineが必要",
ko: "AccountSet → 발행자에 DefaultRipple 설정\nTrustSet → reserve가 발행자를 신뢰\nPayment → 발행자가 reserve에 공급량 전송\nPayment → reserve가 사용자에게 배포\n\n최종 사용자는 토큰을 받기 전에\nTrustLine이 필요함",
zh: "AccountSet → 为发行方开启 DefaultRipple\nTrustSet → 储备账户信任发行方\nPayment → 发行方向储备账户发送供应量\nPayment → 储备账户向用户分发\n\n最终用户在接收代币前\n必须先建立 TrustLine",
},
visual: "📋",
},
@@ -1332,6 +1575,7 @@ createAndDistributeToken();`,
en: "Advanced token management",
jp: "高度なトークン管理",
ko: "고급 토큰 관리",
zh: "高级代币管理",
},
theory: {
es: `Una vez creado tu token, puedes gestionar diversos aspectos: consultar balances, configurar la cuenta emisora y transferir tokens entre usuarios.
@@ -1412,6 +1656,18 @@ For token names longer than 3 characters, a 40-character hexadecimal code is use
### 운영 관점의 포인트
토큰은 한 번 배포했다고 끝나지 않습니다. 정책, 보안, 규정, 사용자 경험을 고려해 발행자 계정을 지속적으로 관리해야 합니다.`,
zh: `当代币开始运行后,除了发行和分发,还需要处理各种 **管理工作**。这一阶段需要理解发行方权限以及 TrustLine 的状态。
### 常见管理内容
- 批准或拒绝某条 TrustLine
- 调整发行账户配置
- 检查持有额度与用户状态
- 监控代币流向
### 运营视角下的重点
代币并不是发出去就结束了。你需要持续管理发行账户,同时考虑策略、安全、合规和用户体验。`,
},
codeBlocks: [
{
@@ -1420,6 +1676,7 @@ For token names longer than 3 characters, a 40-character hexadecimal code is use
en: "Query the tokens (TrustLines) of an account",
jp: "アカウントのトークンTrustLineを照会する",
ko: "계정의 토큰(TrustLine) 조회",
zh: "查询账户的代币TrustLine",
},
language: "javascript",
code: {
@@ -1542,6 +1799,36 @@ async function getTokenBalances(address) {
await client.disconnect();
}
getTokenBalances("rYourAddressHere");`,
zh: `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("=== 账户代币 ===");
console.log("地址:", address);
if (response.result.lines.length === 0) {
console.log("没有找到 TrustLine代币。");
}
for (const line of response.result.lines) {
console.log(\`代币: \${line.currency}\`);
console.log(\` 发行方: \${line.account}\`);
console.log(\` 余额: \${line.balance}\`);
console.log(\` 限额: \${line.limit}\`);
}
await client.disconnect();
}
getTokenBalances("rYourAddressHere");`,
},
},
@@ -1549,32 +1836,35 @@ getTokenBalances("rYourAddressHere");`,
],
slides: [
{
title: { es: "Consultar tokens", en: "Query tokens", jp: "トークンの照会", ko: "토큰 조회" },
title: { es: "Consultar tokens", en: "Query tokens", jp: "トークンの照会", ko: "토큰 조회", zh: "查询代币" },
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: "account_lines -> TrustLines of an account\n\n• currency -> Token code\n• account -> Issuer\n• balance -> Current balance\n• limit -> Trust limit",
jp: "account_lines -> アカウントのTrustLine\n\n• currency -> トークンコード\n• account -> 発行者\n• balance -> 現在の残高\n• limit -> 信頼限度額",
ko: "account_lines → 계정의 TrustLine\n\n• currency → 토큰 코드\n• account → 발행자\n• balance → 현재 잔액\n• limit → 신뢰 한도",
zh: "account_lines → 账户的 TrustLine\n\n• currency → 代币代码\n• account → 发行方\n• balance → 当前余额\n• limit → 信任额度",
},
visual: "📊",
},
{
title: { es: "DefaultRipple", en: "DefaultRipple", jp: "DefaultRipple", ko: "DefaultRipple" },
title: { es: "DefaultRipple", en: "DefaultRipple", jp: "DefaultRipple", ko: "DefaultRipple", zh: "DefaultRipple" },
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: "Essential flag for token issuers\n\n• Without DefaultRipple -> Only back and forth to issuer\n• With DefaultRipple -> Transferable between third parties\n\nActivate it BEFORE issuing tokens",
jp: "トークン発行者に不可欠なフラグ\n\n• DefaultRippleなし -> 発行者との間でのみ\n• DefaultRippleあり -> 第三者間で転送可能\n\nトークン発行前に有効化すること",
ko: "토큰 발행자에게 필수적인 플래그\n\n• DefaultRipple 없음 → 발행자와의 왕복만 가능\n• DefaultRipple 있음 → 제3자 간 전송 가능\n\n토큰 발행 전에 활성화해야 함",
zh: "代币发行方的重要标志\n\n• 没有 DefaultRipple → 只能与发行方之间往返\n• 有 DefaultRipple → 可在第三方之间转移\n\n必须在发行代币前启用",
},
visual: "🔀",
},
{
title: { es: "Flags importantes para emisores", en: "Important flags for issuers", jp: "発行者の重要なフラグ", ko: "발행자를 위한 중요한 플래그" },
title: { es: "Flags importantes para emisores", en: "Important flags for issuers", jp: "発行者の重要なフラグ", ko: "발행자를 위한 중요한 플래그", zh: "发行方的重要标志" },
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: "RequireAuth (asfRequireAuth):\n• Issuer authorizes each TrustLine\n• Ideal for tokens with KYC\n\nDefaultRipple (asfDefaultRipple):\n• Allows transfer between third parties\n\nConfigure BEFORE issuing tokens\nUse AccountSet with SetFlag/ClearFlag",
jp: "RequireAuthasfRequireAuth\n• 発行者が各TrustLineを承認\n• KYCトークンに最適\n\nDefaultRippleasfDefaultRipple\n• 第三者間の転送を許可\n\nトークン発行前に設定する\nAccountSetにSetFlag/ClearFlagを使用",
ko: "RequireAuth (asfRequireAuth):\n• 발행자가 각 TrustLine을 승인\n• KYC가 필요한 토큰에 적합\n\nDefaultRipple (asfDefaultRipple):\n• 제3자 간 전송 허용\n\n토큰 발행 전 설정 필요\nAccountSet의 SetFlag/ClearFlag 사용",
zh: "RequireAuthasfRequireAuth\n• 发行方批准每一条 TrustLine\n• 适合需要 KYC 的代币\n\nDefaultRippleasfDefaultRipple\n• 允许第三方之间转移\n\n应在发行代币前完成配置\n使用带 SetFlag/ClearFlag 的 AccountSet",
},
visual: "🚩",
},
@@ -1587,6 +1877,7 @@ getTokenBalances("rYourAddressHere");`,
en: "Trading on the native DEX",
jp: "ネイティブDEXでのトレーディング",
ko: "네이티브 DEX에서 거래하기",
zh: "在原生 DEX 上交易",
},
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.
@@ -1738,6 +2029,20 @@ XahauのDEXはXAHを通じてマルチホップ取引を自動的にルーティ
- 가격과 수량 단위를 정확히 이해해야 합니다
- 부분 체결 가능성을 고려해야 합니다
- 유동성이 적으면 원하는 가격으로 체결되지 않을 수 있습니다`,
zh: `Xahau 内置原生 DEX因此无需额外的智能合约也能进行代币交易。交易基于订单簿和路径查找机制。
### 基本组成
- \`OfferCreate\`:创建买单或卖单
- \`OfferCancel\`:取消订单
- 订单簿:按资产对保存挂单
- 路径查找:计算经过多个中间资产的兑换路径
### 实务中的注意点
- 必须准确理解价格和数量单位
- 要考虑部分成交的可能性
- 如果流动性不足,可能无法按理想价格成交`,
},
codeBlocks: [
{
@@ -1746,6 +2051,7 @@ XahauのDEXはXAHを通じてマルチホップ取引を自動的にルーティ
en: "Query the order book for a token pair (USD/XAH)",
jp: "トークンペアの注文書を照会するEVR/XAH",
ko: "토큰 쌍의 오더북 조회 (EVR/XAH)",
zh: "查询代币对的订单簿EVR/XAH",
},
language: "javascript",
code: {
@@ -1916,6 +2222,48 @@ async function viewOrderBook() {
await client.disconnect();
}
viewOrderBook();`,
zh: `const { Client } = require("xahau");
async function viewOrderBook() {
// 此示例连接到 Xahau Mainnet因为那里的 DEX 更可能有活跃订单。
const client = new Client("wss://xahau.network");
await client.connect();
const issuerAddress = "rEvernodee8dJLaFsujS6q1EiXvZYmHXr8";
// 查询挂单:谁在用 EVR 换取 XAH
const response = await client.request({
command: "book_offers",
taker_pays: {
currency: "XAH",
},
taker_gets: {
currency: "EVR",
issuer: issuerAddress,
},
limit: 10,
});
console.log("=== 订单簿: EVR → XAH ===");
console.log(\`找到的挂单数: \${response.result.offers.length}\`);
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(\`账户: \${offer.Account}\`);
console.log(\` 卖出: \${getsUSD} EVR\`);
console.log(\` 想要: \${paysXAH} XAH\`);
console.log(\` Sequence: \${offer.Sequence}\`);
}
await client.disconnect();
}
viewOrderBook();`,
},
},
@@ -1925,6 +2273,7 @@ viewOrderBook();`,
en: "Create an offer on the DEX (sell 100 Tokens for XAH)",
jp: "DEXに注文を出す100トークンをXAHで売る",
ko: "DEX에 오퍼 생성 (100 토큰을 XAH로 판매)",
zh: "在 DEX 上创建订单(卖出 100 个代币换取 XAH",
},
language: "javascript",
code: {
@@ -2183,6 +2532,68 @@ async function createOffer() {
await client.disconnect();
}
createOffer();`,
zh: `require("dotenv").config();
const { Client, Wallet, xahToDrops } = require("xahau");
// 如果 token_currency 超过 3 个字符,则转成 40 位十六进制
function normalizeCurrency(token_currency) {
if (typeof token_currency !== "string") return token_currency;
const cur = token_currency.trim();
if (cur.length <= 3) return cur;
const hex = Buffer.from(cur, "utf8").toString("hex").toUpperCase();
if (hex.length > 40) {
throw new Error(
\`token_currency 太长: "\${cur}" -> hex \${hex.length} (>40). UTF-8 最多约 20 bytes。\`
);
}
return hex.padEnd(40, "0");
}
async function createOffer() {
const client = new Client("wss://xahau-test.net");
await client.connect();
const trader = Wallet.fromSeed(process.env.RESERVE_SEED, {algorithm: 'secp256k1'});
const issuerAddress = "rTokenIssuerAddress";
const tokenCurrencyInput = "YourTokenName";
const token_currency = normalizeCurrency(tokenCurrencyInput);
// 卖出 100 个代币,换取 50 XAH
const offer = {
TransactionType: "OfferCreate",
Account: trader.address,
// 我想收到的50 XAH
TakerPays: xahToDrops(50),
// 我愿意给出的100 个代币
TakerGets: {
currency: token_currency,
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("结果:", result.result.meta.TransactionResult);
if (result.result.meta.TransactionResult === "tesSUCCESS") {
console.log("DEX 订单已创建!");
console.log(\`卖出 100 个代币,换取 50 XAH0.5 XAH/代币)\`);
console.log(\`订单 Sequence: \${prepared.Sequence}\`);
}
await client.disconnect();
}
createOffer();`,
},
},
@@ -2192,6 +2603,7 @@ createOffer();`,
en: "Cancel an existing offer on the DEX",
jp: "DEXの既存注文をキャンセルする",
ko: "DEX의 기존 오퍼 취소",
zh: "取消 DEX 上已有的订单",
},
language: "javascript",
code: {
@@ -2320,38 +2732,72 @@ async function cancelOffer() {
await client.disconnect();
}
cancelOffer();`,
zh: `require("dotenv").config();
const { Client, Wallet } = require("xahau");
async function cancelOffer() {
const client = new Client("wss://xahau-test.net");
await client.connect();
const trader = Wallet.fromSeed(process.env.RESERVE_SEED, {algorithm: 'secp256k1'});
// 使用 OfferSequence 取消订单
const cancel = {
TransactionType: "OfferCancel",
Account: trader.address,
OfferSequence: 12345, // 要取消的订单 Sequence
};
const prepared = await client.autofill(cancel);
const signed = trader.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
console.log("结果:", result.result.meta.TransactionResult);
if (result.result.meta.TransactionResult === "tesSUCCESS") {
console.log("订单已成功取消!");
console.log("交易者地址:", trader.address);
}
await client.disconnect();
}
cancelOffer();`,
},
},
],
slides: [
{
title: { es: "DEX nativo de Xahau", en: "Xahau native DEX", jp: "XahauネイティブDEX", ko: "Xahau 네이티브 DEX" },
title: { es: "DEX nativo de Xahau", en: "Xahau native DEX", jp: "XahauネイティブDEX", ko: "Xahau 네이티브 DEX", zh: "Xahau 原生 DEX" },
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: "Decentralized exchange built into the protocol\n\n• No smart contracts\n• No external platforms\n• Atomic settlement\n• Auto-bridging through XAH\n\nAll with native transactions",
jp: "プロトコルに組み込まれた分散型取引所\n\n• スマートコントラクト不要\n• 外部プラットフォーム不要\n• アトミック決済\n• XAHを通じたオートブリッジング\n\nすべてネイティブトランザクションで",
ko: "프로토콜에 내장된 탈중앙화 거래소\n\n• 스마트 컨트랙트 불필요\n• 외부 플랫폼 불필요\n• 원자적 결제\n• XAH를 통한 자동 브리징\n\n모두 네이티브 트랜잭션으로 동작",
zh: "集成在协议中的去中心化交易所\n\n• 不需要智能合约\n• 不需要外部平台\n• 原子结算\n• 通过 XAH 自动桥接\n\n全部使用原生交易完成",
},
visual: "📈",
},
{
title: { es: "OfferCreate: anatomía de una orden", en: "OfferCreate: anatomy of an order", jp: "OfferCreate注文の構造", ko: "OfferCreate: 주문 구조" },
title: { es: "OfferCreate: anatomía de una orden", en: "OfferCreate: anatomy of an order", jp: "OfferCreate注文の構造", ko: "OfferCreate: 주문 구조", zh: "OfferCreate订单结构" },
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• tfFillOrKill → Ejecutar todo o nada\n• tfSell → Recibe tanto como la cantidad de TakerGets\n\nOfferCancel → Cancelar orden abierta",
en: "TakerPays -> What you want to RECEIVE\nTakerGets -> What you are willing to GIVE\n\nSpecial flags:\n• tfImmediateOrCancel -> Execute or cancel\n• tfPassive -> Only match existing\n• tfFillOrKill -> Execute all or nothing\n• tfSell -> Receive as much as TakerGets amount\n\nOfferCancel -> Cancel open order",
jp: "TakerPays -> 受け取りたいもの\nTakerGets -> 提供する意思があるもの\n\n特殊フラグ\n• tfImmediateOrCancel -> 実行またはキャンセル\n• tfPassive -> 既存注文にのみマッチ\n• tfFillOrKill -> 全量実行またはキャンセル\n• tfSell -> 可能な限り多くの金額を受け取る\n\nOfferCancel -> 未決注文をキャンセル",
ko: "TakerPays → 내가 받고 싶은 것\nTakerGets → 내가 내놓을 것\n\n특수 플래그:\n• tfImmediateOrCancel → 즉시 실행 아니면 취소\n• tfPassive → 기존 주문과만 매칭\n• tfFillOrKill → 전량 체결 아니면 취소\n• tfSell → TakerGets 전량 기준으로 매도\n\nOfferCancel → 열린 주문 취소",
zh: "TakerPays → 你想收到的东西\nTakerGets → 你愿意给出的东西\n\n特殊标志\n• tfImmediateOrCancel → 立即执行否则取消\n• tfPassive → 只与现有订单撮合\n• tfFillOrKill → 全部成交否则取消\n• tfSell → 以 TakerGets 全量为基准卖出\n\nOfferCancel → 取消未成交订单",
},
visual: "🔄",
},
{
title: { es: "Auto-bridging y order book", en: "Auto-bridging and order book", jp: "オートブリッジングと注文書", ko: "자동 브리징과 오더북" },
title: { es: "Auto-bridging y order book", en: "Auto-bridging and order book", jp: "オートブリッジングと注文書", ko: "자동 브리징과 오더북", zh: "自动桥接与订单簿" },
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: "The DEX routes multi-hop trades via XAH\n\nExample: USD -> XAH -> EUR\n\n• book_offers -> View the order book\n• Bids and Asks cross automatically\n• Partial or full execution\n• Shared liquidity across pairs",
jp: "DEXはXAHを経由してマルチホップ取引をルーティング\n\n例USD -> XAH -> EUR\n\n• book_offers -> 注文書を表示\n• BidsとAsksが自動的に交差\n• 部分または全量実行\n• ペア間で流動性を共有",
ko: "DEX는 XAH를 통해 멀티홉 거래를 라우팅함\n\n예: USD → XAH → EUR\n\n• book_offers → 오더북 보기\n• 매수/매도 주문 자동 매칭\n• 부분 또는 전량 체결\n• 거래쌍 간 유동성 공유",
zh: "DEX 会通过 XAH 路由多跳交易\n\n示例USD → XAH → EUR\n\n• book_offers → 查看订单簿\n• 买单和卖单会自动撮合\n• 可部分成交或完全成交\n• 不同交易对之间共享流动性",
},
visual: "🌐",
},
@@ -2364,6 +2810,7 @@ cancelOffer();`,
en: "Advanced token control: Freeze and Clawback",
jp: "高度なトークン制御FreezeとClawback",
ko: "고급 토큰 제어: Freeze와 Clawback",
zh: "高级代币控制Freeze 与 Clawback",
},
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).
@@ -2497,6 +2944,17 @@ The \`RequireAuth\` flag (asfRequireAuth) on the issuing account requires the is
### 왜 민감한가?
이 기능들은 강력하지만 사용자 신뢰와 직결됩니다. 발행자는 언제, 왜, 어떤 범위로 사용할지 명확한 정책을 가져야 하며, 사용자도 해당 토큰의 중앙화 수준을 이해해야 합니다.`,
zh: `有些发行方出于合规或运营原因需要更强的控制能力。Xahau 提供了 **Freeze** 和 **Clawback** 等高级控制功能。
### 主要功能
- **Freeze**:限制某条 TrustLine 或代币的使用
- **Global Freeze**:大范围限制整个已发行代币的流动
- **Clawback**:在特定条件下回收代币
### 为什么这很敏感?
这些功能非常强大,也直接影响用户信任。发行方需要明确说明何时、为何以及在多大范围内使用它们,用户也应了解该代币的中心化程度。`,
},
codeBlocks: [
{
@@ -2505,6 +2963,7 @@ The \`RequireAuth\` flag (asfRequireAuth) on the issuing account requires the is
en: "Create a TrustLine from a holder toward the issuer",
jp: "ホルダーから発行者へのTrustLineを作成する",
ko: "홀더에서 발행자로 TrustLine 생성",
zh: "从持有人到发行方创建 TrustLine",
},
language: "javascript",
code: {
@@ -2766,6 +3225,64 @@ async function createHolderTrustLine() {
await client.disconnect();
}
createHolderTrustLine();`,
zh: `require("dotenv").config();
const { Client, Wallet } = require("xahau");
// 这段代码会从一个账户(持有人)指向代币发行方创建 TrustLine。
// 这样发行方之后在有需要时就可以冻结这条 TrustLine。
function normalizeCurrency(token_currency) {
if (typeof token_currency !== "string") return token_currency;
const cur = token_currency.trim();
if (cur.length <= 3) return cur;
const hex = Buffer.from(cur, "utf8").toString("hex").toUpperCase();
if (hex.length > 40) {
throw new Error(
\`token_currency 太长: "\${cur}" -> hex \${hex.length} (>40). UTF-8 最多约 20 bytes。\`
);
}
return hex.padEnd(40, "0");
}
async function createHolderTrustLine() {
const client = new Client("wss://xahau-test.net");
await client.connect();
const holder = Wallet.fromSeed(process.env.FROZEN_SEED, {algorithm: 'secp256k1'});
const issuerAddress = "rIssuerAddress";
const tokenCurrencyInput = "YourTokenName";
const token_currency = normalizeCurrency(tokenCurrencyInput);
const trustSet = {
TransactionType: "TrustSet",
Account: holder.address,
LimitAmount: {
currency: token_currency,
issuer: issuerAddress,
value: "1000000",
},
};
const prepared = await client.autofill(trustSet);
const signed = holder.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
console.log("结果:", result.result.meta.TransactionResult);
if (result.result.meta.TransactionResult === "tesSUCCESS") {
console.log("TrustLine 已创建!");
console.log("持有人:", holder.address);
console.log("发行方:", issuerAddress);
console.log("\\n现在发行方可以向这个账户发送代币。");
console.log("如有需要,也可以冻结这条 TrustLine。");
}
await client.disconnect();
}
createHolderTrustLine();`,
},
},
@@ -2775,6 +3292,7 @@ createHolderTrustLine();`,
en: "Freeze a specific user's TrustLine",
jp: "特定ユーザーのTrustLineを凍結する",
ko: "특정 사용자의 TrustLine 동결",
zh: "冻结某个用户的 TrustLine",
},
language: "javascript",
code: {
@@ -3012,38 +3530,93 @@ async function freezeTrustLine() {
await client.disconnect();
}
freezeTrustLine();`,
zh: `require("dotenv").config();
const { Client, Wallet } = require("xahau");
function normalizeCurrency(token_currency) {
if (typeof token_currency !== "string") return token_currency;
const cur = token_currency.trim();
if (cur.length <= 3) return cur;
const hex = Buffer.from(cur, "utf8").toString("hex").toUpperCase();
if (hex.length > 40) {
throw new Error(
\`token_currency 太长: "\${cur}" -> hex \${hex.length} (>40). UTF-8 最多约 20 bytes。\`
);
}
return hex.padEnd(40, "0");
}
async function freezeTrustLine() {
const client = new Client("wss://xahau-test.net");
await client.connect();
const issuer = Wallet.fromSeed(process.env.ISSUER_SEED, {algorithm: 'secp256k1'});
const holderAddress = "rHolderAddress";
const tokenCurrencyInput = "YourTokenName";
const token_currency = normalizeCurrency(tokenCurrencyInput);
// 冻结该持有人的代币 TrustLine
const trustSet = {
TransactionType: "TrustSet",
Account: issuer.address,
LimitAmount: {
currency: token_currency,
issuer: holderAddress,
value: "0",
},
Flags: 1048576, // tfSetFreeze
};
const prepared = await client.autofill(trustSet);
const signed = issuer.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
console.log("结果:", result.result.meta.TransactionResult);
if (result.result.meta.TransactionResult === "tesSUCCESS") {
console.log(\`\${holderAddress} 的代币 TrustLine 已被冻结\`);
console.log("该持有人将无法发送或接收此代币");
}
await client.disconnect();
}
freezeTrustLine();`,
},
},
],
slides: [
{
title: { es: "Freeze: congelación de tokens", en: "Freeze: token freezing", jp: "Freezeトークンの凍結", ko: "Freeze: 토큰 동결" },
title: { es: "Freeze: congelación de tokens", en: "Freeze: token freezing", jp: "Freezeトークンの凍結", ko: "Freeze: 토큰 동결", zh: "Freeze代币冻结" },
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: "The issuer can freeze transfers\n\n• Individual Freeze -> A specific TrustLine\n• Global Freeze -> ALL TrustLines\n• NoFreeze -> Permanently renounce\n\nUse cases: regulation, security, disputes",
jp: "発行者は転送を凍結できます\n\n• 個別Freeze -> 特定のTrustLine\n• グローバルFreeze -> すべてのTrustLine\n• NoFreeze -> 恒久的に放棄\n\nユースケース規制、セキュリティ、紛争",
ko: "발행자는 전송을 동결할 수 있습니다\n\n• 개별 Freeze → 특정 TrustLine\n• Global Freeze → 모든 TrustLine\n• NoFreeze → 영구적으로 권한 포기\n\n사례: 규제, 보안, 분쟁",
zh: "发行方可以冻结转账\n\n• Individual Freeze → 某一条特定 TrustLine\n• Global Freeze → 所有 TrustLine\n• NoFreeze → 永久放弃冻结权\n\n使用场景合规、安全、争议处理",
},
visual: "🧊",
},
{
title: { es: "Clawback: recuperación forzada", en: "Clawback: forced recovery", jp: "Clawback強制回収", ko: "Clawback: 강제 회수" },
title: { es: "Clawback: recuperación forzada", en: "Clawback: forced recovery", jp: "Clawback強制回収", ko: "Clawback: 강제 회수", zh: "Clawback强制追回" },
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: "Reclaim tokens from any holder\n\n1⃣ Activate asfAllowTrustLineClawback\n2⃣ Use Clawback transaction\n\n⚠ Must be activated BEFORE issuing tokens\n⚠ Incompatible with NoFreeze",
jp: "任意のホルダーからトークンを回収\n\n1⃣ asfAllowTrustLineClawbackを有効化\n2⃣ Clawbackトランザクションを使用\n\n⚠ トークン発行前に有効化が必要\n⚠ NoFreezeとは非互換",
ko: "어떤 홀더에게서도 토큰 회수 가능\n\n1⃣ asfAllowTrustLineClawback 활성화\n2⃣ Clawback 트랜잭션 사용\n\n⚠ 토큰 발행 전에 활성화해야 함\n⚠ NoFreeze와 호환되지 않음",
zh: "可以从任何持有人处追回代币\n\n1⃣ 启用 asfAllowTrustLineClawback\n2⃣ 使用 Clawback 交易\n\n⚠ 必须在发行代币前启用\n⚠ 与 NoFreeze 不兼容",
},
visual: "🔙",
},
{
title: { es: "Transfer fees y RequireAuth", en: "Transfer fees and RequireAuth", jp: "Transfer feesとRequireAuth", ko: "Transfer fees와 RequireAuth" },
title: { es: "Transfer fees y RequireAuth", en: "Transfer fees and RequireAuth", jp: "Transfer feesとRequireAuth", ko: "Transfer fees와 RequireAuth", zh: "Transfer fees 与 RequireAuth" },
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: "Transfer fees:\n• TransferRate in AccountSet\n• Percentage on each transfer between third parties\n• Example: 0.1% -> 1001000000\n\nRequireAuth:\n• Issuer authorizes each TrustLine\n• Ideal for tokens with KYC",
jp: "Transfer fees\n• AccountSetのTransferRate\n• 第三者間転送ごとに割合を徴収\n• 例0.1% -> 1001000000\n\nRequireAuth\n• 発行者が各TrustLineを承認\n• KYCトークンに最適",
ko: "Transfer fees:\n• AccountSet의 TransferRate 사용\n• 제3자 간 전송마다 비율 적용\n• 예: 0.1% → 1001000000\n\nRequireAuth:\n• 발행자가 각 TrustLine 승인\n• KYC가 필요한 토큰에 적합",
zh: "Transfer fees\n• 在 AccountSet 中使用 TransferRate\n• 对第三方之间的每次转账收取比例费用\n• 示例0.1% → 1001000000\n\nRequireAuth\n• 发行方批准每一条 TrustLine\n• 适合需要 KYC 的代币",
},
visual: "🔐",
},

View File

@@ -6,6 +6,7 @@ export default {
en: "Creating and Using NFTs",
jp: "NFTの作成と使用",
ko: "NFT 생성 및 사용",
zh: "NFT 的创建与使用",
},
lessons: [
{
@@ -15,6 +16,7 @@ export default {
en: "URITokens: Native NFTs on Xahau",
jp: "URITokensXahauのネイティブNFT",
ko: "URITokens: Xahau의 네이티브 NFT",
zh: "URITokensXahau 的原生 NFT",
},
theory: {
es: `En Xahau, los NFTs se implementan como **URITokens**, objetos nativos del ledger que representan tokens no fungibles con una URI asociada.
@@ -149,6 +151,39 @@ URIToken은 레저 상의 **고유한** 객체로 다음 필드를 포함합니
### URITokenMint 플래그
- **tfBurnable (1)**: 발행자가 더 이상 소유자가 아니더라도 토큰을 소각할 수 있도록 허용`,
zh: `在 Xahau 中NFT 通过 **URIToken** 实现,它是账本中的原生对象,用来表示带有关联 URI 的非同质化代币。
### 什么是 URIToken
URIToken 是账本中的一个**唯一**对象,包含:
- **ID**代币唯一标识符LedgerIndex
- **URI**:指向 NFT 元数据或内容的链接图片、JSON 等)
- **Digest**URI 指向内容的可选哈希(用于完整性校验)
- **Owner**:当前持有者账户
- **Issuer**:最初创建它的账户
### URIToken vs ERC-721
| 特性 | ERC-721 (Ethereum) | URIToken (Xahau) |
|---|---|---|
| 创建集合 | 部署 Solidity 合约 | 不需要 |
| Mint NFT | 合约函数 | \`URITokenMint\` 交易 |
| 转移 | 合约函数 | \`URITokenBuy\` 交易 |
| 元数据 | 合约中的 tokenURI | 对象中的原生 URI |
| 成本 | Gas 昂贵 | 最低手续费(约 12 drops |
| 验证 | 依赖合约 | 账本中的原生 Digest |
### 与 URIToken 相关的交易
- **URITokenMint**:创建新的 URIToken
- **URITokenBurn**:销毁 URIToken
- **URITokenCreateSellOffer**:将 URIToken 挂牌出售
- **URITokenCancelSellOffer**:取消卖单
- **URITokenBuy**:购买正在出售的 URIToken
### URITokenMint 的标志
- **tfBurnable (1)**:即使发行方已经不是持有者,仍允许其销毁该代币`,
},
codeBlocks: [
{
@@ -157,6 +192,7 @@ URIToken은 레저 상의 **고유한** 객체로 다음 필드를 포함합니
en: "Create (Mint) a URIToken",
jp: "URITokenを作成ミントする",
ko: "URIToken 생성 (민팅)",
zh: "创建Mint一个 URIToken",
},
language: "javascript",
code: {
@@ -346,6 +382,53 @@ async function mintURIToken() {
await client.disconnect();
}
mintURIToken();`,
zh: `require("dotenv").config();
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(process.env.WALLET_SEED, {algorithm: 'secp256k1'});
// 使用指向元数据的 URI 创建一个 URIToken
const mint = {
TransactionType: "URITokenMint",
Account: creator.address,
// URI 示例(可以是 IPFS、HTTPS 等)- 例如ipfs://bafybeieza5w4rkes55paw7jgpo4kzsbyywhw7ildltk3kjx2ttkmt7texa/106.json
URI: toHex("https://example.com/nft/metadata.json"),
Flags: 1, // tfBurnable发行方可以销毁该代币
};
const prepared = await client.autofill(mint);
const signed = creator.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
console.log("结果:", result.result.meta.TransactionResult);
if (result.result.meta.TransactionResult === "tesSUCCESS") {
console.log("URIToken 创建成功!");
console.log("交易哈希:", signed.hash);
// 在受影响节点中查找创建出的 URIToken
const created = result.result.meta.AffectedNodes.find(
(n) => n.CreatedNode?.LedgerEntryType === "URIToken"
);
if (created) {
console.log("URIToken ID:", created.CreatedNode.LedgerIndex);
console.log("地址:", creator.address);
}
}
await client.disconnect();
}
mintURIToken();`,
},
},
@@ -355,6 +438,7 @@ mintURIToken();`,
en: "Query URITokens for an Account",
jp: "アカウントのURITokenを照会する",
ko: "계정의 URIToken 조회",
zh: "查询账户的 URIToken",
},
language: "javascript",
code: {
@@ -501,38 +585,77 @@ async function getURITokens(address) {
await client.disconnect();
}
// 조회할 주소로 교체하세요. 예: r9oB9E7jnRjp88fTrxHzngAietepwCCcqV
getURITokens("rYourAddressHere");`,
zh: `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(\`=== \${address} 的 URIToken ===\`);
console.log(\`总数: \${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(\` 发行方: \${token.Issuer}\`);
console.log(\` 持有者: \${token.Owner}\`);
if (token.Digest) {
console.log(\` Digest: \${token.Digest}\`);
}
if (token.Amount) {
console.log(\` 挂牌价格: \${Number(token.Amount) / 1_000_000} XAH\`);
}
console.log();
}
await client.disconnect();
}
// 替换成你要查询的地址,例如 r9oB9E7jnRjp88fTrxHzngAietepwCCcqV
getURITokens("rYourAddressHere");`,
},
},
],
slides: [
{
title: { es: "URITokens en Xahau", en: "URITokens on Xahau", jp: "XahauのURIToken", ko: "Xahau의 URIToken" },
title: { es: "URITokens en Xahau", en: "URITokens on Xahau", jp: "XahauのURIToken", ko: "Xahau의 URIToken", zh: "Xahau 上的 URIToken" },
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: "Native NFTs on the Xahau ledger\n\n• URI → Link to metadata\n• Digest → Verification hash\n• Owner → Current owner\n• Issuer → Original creator\n\nNo smart contracts needed",
jp: "Xahauレジャーのネイティブ NFT\n\n• URI → メタデータへのリンク\n• Digest → 検証ハッシュ\n• Owner → 現在の所有者\n• Issuer → 元の作成者\n\nスマートコントラクト不要",
ko: "Xahau 레저의 네이티브 NFT\n\n• URI → 메타데이터 링크\n• Digest → 검증 해시\n• Owner → 현재 소유자\n• Issuer → 최초 생성자\n\n스마트 컨트랙트 불필요",
zh: "Xahau 账本中的原生 NFT\n\n• URI → 元数据链接\n• Digest → 校验哈希\n• Owner → 当前持有者\n• Issuer → 原始创建者\n\n不需要智能合约",
},
visual: "🎨",
},
{
title: { es: "Operaciones con URITokens", en: "URIToken Operations", jp: "URITokenの操作", ko: "URIToken 작업" },
title: { es: "Operaciones con URITokens", en: "URIToken Operations", jp: "URITokenの操作", ko: "URIToken 작업", zh: "URIToken 操作" },
content: {
es: "• URITokenMint → Crear NFT\n• URITokenBurn → Destruir NFT\n• URITokenCreateSellOffer → Vender\n• URITokenCancelSellOffer → Cancelar venta\n• URITokenBuy → Comprar",
en: "• URITokenMint → Create NFT\n• URITokenBurn → Destroy NFT\n• URITokenCreateSellOffer → Sell\n• URITokenCancelSellOffer → Cancel sale\n• URITokenBuy → Buy",
jp: "• URITokenMint → NFTを作成\n• URITokenBurn → NFTを破棄\n• URITokenCreateSellOffer → 売りに出す\n• URITokenCancelSellOffer → 売りをキャンセル\n• URITokenBuy → 購入する",
ko: "• URITokenMint → NFT 생성\n• URITokenBurn → NFT 소각\n• URITokenCreateSellOffer → 판매 등록\n• URITokenCancelSellOffer → 판매 취소\n• URITokenBuy → 구매",
zh: "• URITokenMint → 创建 NFT\n• URITokenBurn → 销毁 NFT\n• URITokenCreateSellOffer → 出售\n• URITokenCancelSellOffer → 取消出售\n• URITokenBuy → 购买",
},
visual: "🔧",
},
{
title: { es: "URIToken vs ERC-721", en: "URIToken vs ERC-721", jp: "URIToken vs ERC-721", ko: "URIToken vs ERC-721" },
title: { es: "URIToken vs ERC-721", en: "URIToken vs ERC-721", jp: "URIToken vs ERC-721", ko: "URIToken vs ERC-721", zh: "URIToken vs ERC-721" },
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: "URIToken (Xahau):\n• Native to the ledger, no contracts\n• Minimal fee (~12 drops)\n• Native Digest for verification\n\nERC-721 (Ethereum):\n• Requires Solidity contract\n• Expensive and variable gas\n• Verification depends on contract",
jp: "URITokenXahau\n• レジャーネイティブ、コントラクト不要\n• 最小限のFee〜12 drops\n• 検証用のネイティブDigest\n\nERC-721Ethereum\n• Solidityコントラクトが必要\n• 高価で変動するガス代\n• 検証はコントラクトに依存",
ko: "URIToken (Xahau):\n• 레저 네이티브, 컨트랙트 불필요\n• 최소 수수료 (~12 drops)\n• 검증을 위한 네이티브 Digest\n\nERC-721 (Ethereum):\n• Solidity 컨트랙트 필요\n• 비싸고 가변적인 가스비\n• 검증이 컨트랙트에 의존",
zh: "URITokenXahau\n• 账本原生,无需合约\n• 最低手续费(约 12 drops\n• 使用原生 Digest 验证\n\nERC-721Ethereum\n• 需要 Solidity 合约\n• Gas 昂贵且波动大\n• 验证依赖合约",
},
visual: "⚖️",
},
@@ -545,6 +668,7 @@ getURITokens("rYourAddressHere");`,
en: "Buying and Selling URITokens",
jp: "URITokenの売買",
ko: "URIToken 매매",
zh: "URIToken 的买卖",
},
theory: {
es: `Xahau incluye un sistema nativo para la compra-venta de URITokens, sin necesidad de marketplaces externos ni smart contracts.
@@ -623,6 +747,25 @@ URIToken을 비용 없이 전송(선물)하려면 \`Amount: "0"\`과 특정 \`De
### URIToken 소각
현재 소유자는 언제든지 \`URITokenBurn\`으로 URIToken을 소각(파괴)할 수 있습니다. \`tfBurnable\` 플래그로 생성된 토큰은 원래 발행자도 소각할 수 있습니다.`,
zh: `Xahau 内置了买卖 URIToken 的原生系统,不需要外部市场或智能合约。
### 出售流程
1. 持有者通过 \`URITokenCreateSellOffer\` 创建**卖单**,并指定 XAH 或其他货币的价格。
2. 任何人都可以通过 \`URITokenBuy\` **购买** URIToken并支付标价。
3. 持有者可以通过 \`URITokenCancelSellOffer\` **取消**该卖单。
### 卖给指定接收者
你可以使用 \`Destination\` 字段创建面向特定账户的卖单。只有该账户才能购买这个 URIToken。
### 免费转移
如果你想免费转移一个 URIToken例如赠送可以创建一个 \`Amount: "0"\` 且带有特定 \`Destination\` 的卖单。
### 销毁 URIToken
当前持有者始终可以通过 \`URITokenBurn\` 销毁自己的 URIToken。如果该代币在创建时启用了 \`tfBurnable\` 标志,则最初的发行方也可以销毁它。`,
},
codeBlocks: [
{
@@ -631,6 +774,7 @@ URIToken을 비용 없이 전송(선물)하려면 \`Amount: "0"\`과 특정 \`De
en: "List a URIToken for Sale",
jp: "URITokenを売りに出す",
ko: "URIToken 판매 등록",
zh: "将 URIToken 挂牌出售",
},
language: "javascript",
code: {
@@ -757,6 +901,37 @@ async function sellURIToken() {
await client.disconnect();
}
sellURIToken();`,
zh: `require("dotenv").config();
const { Client, Wallet, xahToDrops } = require("xahau");
async function sellURIToken() {
const client = new Client("wss://xahau-test.net");
await client.connect();
const owner = Wallet.fromSeed(process.env.WALLET_SEED, {algorithm: 'secp256k1'});
// 创建一个售价为 5 XAH 的卖单
const sellOffer = {
TransactionType: "URITokenCreateSellOffer",
Account: owner.address,
URITokenID: "YOUR_URITOKEN_ID_HERE", // 要出售的 URIToken ID
Amount: xahToDrops(5), // 价格5 XAH
};
const prepared = await client.autofill(sellOffer);
const signed = owner.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
console.log("结果:", result.result.meta.TransactionResult);
if (result.result.meta.TransactionResult === "tesSUCCESS") {
console.log("URIToken 已以 5 XAH 挂牌出售!");
}
await client.disconnect();
}
sellURIToken();`,
},
},
@@ -766,6 +941,7 @@ sellURIToken();`,
en: "Buy a URIToken That Is Listed for Sale",
jp: "売りに出ているURITokenを購入する",
ko: "판매 중인 URIToken 구매",
zh: "购买正在出售的 URIToken",
},
language: "javascript",
code: {
@@ -900,38 +1076,74 @@ async function buyURIToken() {
await client.disconnect();
}
buyURIToken();`,
zh: `require("dotenv").config();
const { Client, Wallet, xahToDrops } = require("xahau");
async function buyURIToken() {
const client = new Client("wss://xahau-test.net");
await client.connect();
const buyer = Wallet.fromSeed(process.env.BUYER_SEED, {algorithm: 'secp256k1'});
// 按照卖价支付并购买 URIToken
const buy = {
TransactionType: "URITokenBuy",
Account: buyer.address,
URITokenID: "YOUR_URITOKEN_ID_HERE", // 要购买的 URIToken ID
Amount: xahToDrops(5), // 必须与卖价一致
};
const prepared = await client.autofill(buy);
const signed = buyer.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
console.log("结果:", result.result.meta.TransactionResult);
if (result.result.meta.TransactionResult === "tesSUCCESS") {
console.log("URIToken 购买成功!");
console.log("这个 NFT 现在属于你了。");
console.log("买家地址:", buyer.address);
}
await client.disconnect();
}
buyURIToken();`,
},
},
],
slides: [
{
title: { es: "Flujo de venta", en: "Sale Flow", jp: "売却フロー", ko: "판매 흐름" },
title: { es: "Flujo de venta", en: "Sale Flow", jp: "売却フロー", ko: "판매 흐름", zh: "出售流程" },
content: {
es: "1⃣ URITokenCreateSellOffer → Poner precio\n2⃣ URITokenBuy → Comprador paga\n3⃣ Transferencia automática\n\nTodo nativo, sin marketplace externo",
en: "1⃣ URITokenCreateSellOffer → Set price\n2⃣ URITokenBuy → Buyer pays\n3⃣ Automatic transfer\n\nAll native, no external marketplace",
jp: "1⃣ URITokenCreateSellOffer → 価格を設定\n2⃣ URITokenBuy → 購入者が支払う\n3⃣ 自動転送\n\nすべてネイティブ、外部マーケットプレイス不要",
ko: "1⃣ URITokenCreateSellOffer → 가격 설정\n2⃣ URITokenBuy → 구매자 지불\n3⃣ 자동 전송\n\n모두 네이티브, 외부 마켓플레이스 불필요",
zh: "1⃣ URITokenCreateSellOffer → 设置价格\n2⃣ URITokenBuy → 买家付款\n3⃣ 自动转移\n\n全部为原生流程无需外部市场",
},
visual: "💰",
},
{
title: { es: "Transferir y quemar", en: "Transfer and Burn", jp: "転送とバーン", ko: "전송과 소각" },
title: { es: "Transferir y quemar", en: "Transfer and Burn", jp: "転送とバーン", ko: "전송과 소각", zh: "转移与销毁" },
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: "Free transfer:\n• SellOffer with Amount: 0 + Destination\n\nBurn (destroy):\n• URITokenBurn by the owner\n• Or by the issuer if tfBurnable flag is set",
jp: "無料転送:\n• Amount: 0 + DestinationのSellOffer\n\nバーン破棄\n• 所有者によるURITokenBurn\n• またはtfBurnableフラグがあれば発行者も可",
ko: "무료 전송:\n• Amount: 0 + Destination의 SellOffer\n\n소각 (파괴):\n• 소유자에 의한 URITokenBurn\n• 또는 tfBurnable 플래그가 있으면 발행자도 가능",
zh: "免费转移:\n• 使用 Amount: 0 + Destination 的 SellOffer\n\n销毁\n• 持有者执行 URITokenBurn\n• 或者在启用 tfBurnable 时由发行方执行",
},
visual: "🔥",
},
{
title: { es: "Quemar URITokens en detalle", en: "Burning URITokens in Detail", jp: "URITokenのバーンの詳細", ko: "URIToken 소각 상세" },
title: { es: "Quemar URITokens en detalle", en: "Burning URITokens in Detail", jp: "URITokenのバーンの詳細", ko: "URIToken 소각 상세", zh: "URIToken 销毁详解" },
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: "tfBurnable flag (1) at mint time:\n• Allows the issuer to burn the token\n• Even if they are no longer the owner\n\nWithout tfBurnable:\n• Only the current owner can burn\n\nUse cases: fix minting errors,\nexpired content, revocable tokens",
jp: "ミント時のtfBurnableフラグ1\n• 発行者がトークンをバーンできる\n• もはや所有者でなくても\n\ntfBurnableなし\n• 現在の所有者のみバーン可能\n\nユースケースミントエラーの修正、\n期限切れコンテンツ、取り消し可能なトークン",
ko: "민팅 시 tfBurnable 플래그 (1):\n• 발행자가 토큰을 소각할 수 있음\n• 더 이상 소유자가 아니더라도\n\ntfBurnable 없음:\n• 현재 소유자만 소각 가능\n\n사용 사례: 민팅 오류 수정,\n만료된 콘텐츠, 취소 가능한 토큰",
zh: "Mint 时的 tfBurnable 标志1\n• 允许发行方销毁该代币\n• 即使其已不再是持有者\n\n没有 tfBurnable\n• 只有当前持有者可以销毁\n\n使用场景修复 mint 错误、\n过期内容、可撤销代币",
},
visual: "🗑️",
},
@@ -944,6 +1156,7 @@ buyURIToken();`,
en: "Metadata and Standards for URITokens",
jp: "URITokenのメタデータと標準",
ko: "URIToken의 메타데이터와 표준",
zh: "URIToken 的元数据与标准",
},
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.
@@ -1154,38 +1367,93 @@ ERC-721과 유사한 표준을 따라, URIToken의 JSON 메타데이터에는
- **프로덕션에는 IPFS를 사용하세요**: 불변성과 분산화가 NFT의 가치를 보호합니다.
- **JSON을 일관되게 유지하세요**: 마켓플레이스 및 탐색기와의 호환성을 위해 메타데이터 표준을 따르세요.
- **URI에 민감한 데이터를 넣지 마세요**: 레저의 모든 것은 공개 정보입니다.`,
zh: `元数据是让 NFT 变得有用且可验证的关键。在 Xahau 中URIToken 使用 **URI** 和 **Digest** 字段来链接并验证相关内容。
### URI 字段:应填写什么
URI 是指向 NFT 内容或元数据的链接,常见选择包括:
- **IPFS 链接**\`ipfs://QmXxx...\`):去中心化存储。内容不可变,并通过哈希寻址。这是生产环境中**推荐**的选择
- **HTTPS 链接**\`https://my-server.com/metadata/1.json\`):中心化存储。实现简单,但依赖服务器可用性
### Digest 字段:完整性验证
**Digest** 是 URI 所指向内容的 SHA-256 哈希。它允许任何人验证该内容自 NFT 创建以来是否被篡改。它以 64 个十六进制字符的形式存储在账本中。
### JSON 元数据标准
遵循与 ERC-721 类似的标准URIToken 的 JSON 元数据通常包含:
\`\`\`json
{
"content": {
"url": "ipfs://bafybeign6w3zkxxqohchtxyv4qot6zrwcrvosmmrz2c6ayijl67h42s3km/106.png"
},
"details": {
"title": "你的 NFT 名称",
"categories": [
"0001"
],
"publisher": {
"name": "你的名字",
"url": "https://www.yourwebsite.com",
"email": "youremail@gmail.com"
},
"group": {
"title": "你的合集标题"
}
}
}
\`\`\`
### 存储选项
| 选项 | 优点 | 缺点 |
|---|---|---|
| **IPFS** | 去中心化、不可变、哈希寻址 | 需要 pinning 来保证持久性 |
| **中心化服务器** | 简单、快速 | 单点故障、可变 |
### 最佳实践
- **始终设置 Digest**:这样可以随时验证内容完整性
- **生产环境优先使用 IPFS**:不可变性和去中心化能保护 NFT 的价值
- **保持 JSON 结构一致**:遵循元数据标准有助于与市场和浏览器兼容
- **不要把敏感数据放进 URI**:账本上的一切都是公开的`,
},
codeBlocks: [
],
slides: [
{
title: { es: "El campo URI: opciones de enlace", en: "The URI Field: Link Options", jp: "URIフィールドリンクの選択肢", ko: "URI 필드: 링크 옵션" },
title: { es: "El campo URI: opciones de enlace", en: "The URI Field: Link Options", jp: "URIフィールドリンクの選択肢", ko: "URI 필드: 링크 옵션", zh: "URI 字段:链接选项" },
content: {
es: "¿A dónde apunta tu NFT?\n\n• ipfs://Qm... → Descentralizado e inmutable\n• https://... → Centralizado pero simple\n",
en: "Where does your NFT point to?\n\n• ipfs://Qm... → Decentralized and immutable\n• https://... → Centralized but simple\n",
jp: "あなたのNFTはどこを指しているか\n\n• ipfs://Qm... → 分散型かつ不変\n• https://... → 集中型だがシンプル\n",
ko: "당신의 NFT는 어디를 가리키나요?\n\n• ipfs://Qm... → 분산형이며 불변\n• https://... → 중앙화되었지만 단순\n",
zh: "你的 NFT 指向哪里?\n\n• ipfs://Qm... → 去中心化且不可变\n• https://... → 中心化但简单\n",
},
visual: "🔗",
},
{
title: { es: "Digest: verificación de integridad", en: "Digest: Integrity Verification", jp: "Digest整合性検証", ko: "Digest: 무결성 검증" },
title: { es: "Digest: verificación de integridad", en: "Digest: Integrity Verification", jp: "Digest整合性検証", ko: "Digest: 무결성 검증", zh: "Digest完整性验证" },
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: "SHA-256 of the content → recorded on the ledger\n\n• Anyone can verify\n• Detects tampering\n• 64 hexadecimal characters\n\nAlways set the Digest to protect your NFT",
jp: "コンテンツのSHA-256 → レジャーに記録\n\n• 誰でも検証可能\n• 改ざんを検出\n• 64文字の16進数\n\nNFTを守るため常にDigestを設定すること",
ko: "콘텐츠의 SHA-256 → 레저에 기록\n\n• 누구나 검증 가능\n• 변조 감지\n• 64자의 16진수 문자\n\nNFT를 보호하기 위해 항상 Digest를 설정하세요",
zh: "内容的 SHA-256 → 记录在账本中\n\n• 任何人都可以验证\n• 能检测篡改\n• 64 个十六进制字符\n\n始终设置 Digest 来保护你的 NFT",
},
visual: "🔏",
},
{
title: { es: "Estándar de metadatos JSON", en: "JSON Metadata Standard", jp: "JSONメタデータ標準", ko: "JSON 메타데이터 표준" },
title: { es: "Estándar de metadatos JSON", en: "JSON Metadata Standard", jp: "JSONメタデータ標準", ko: "JSON 메타데이터 표준", zh: "JSON 元数据标准" },
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: "Recommended structure (similar to ERC-721):\n\n• name → NFT name\n• description → Description\n• image → Link to image\n• attributes → Array of properties\n\nConsistency = compatibility with explorers",
jp: "推奨構造ERC-721と類似\n\n• name → NFT名\n• description → 説明\n• image → 画像へのリンク\n• attributes → プロパティの配列\n\n一貫性 = エクスプローラーとの互換性",
ko: "권장 구조 (ERC-721과 유사):\n\n• name → NFT 이름\n• description → 설명\n• image → 이미지 링크\n• attributes → 속성 배열\n\n일관성 = 탐색기와의 호환성",
zh: "推荐结构(类似 ERC-721\n\n• name → NFT 名称\n• description → 描述\n• image → 图片链接\n• attributes → 属性数组\n\n保持一致 = 更好兼容浏览器与市场",
},
visual: "📋",
},

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@ export default {
en: "Xaman Integration (XUMM SDK)",
jp: "Xaman連携XUMM SDK",
ko: "Xaman 통합 (XUMM SDK)",
zh: "Xaman 集成XUMM SDK",
},
lessons: [
{
@@ -15,6 +16,7 @@ export default {
en: "The Xaman SDK and developer portal",
jp: "Xaman SDKと開発者ポータル",
ko: "Xaman SDK와 개발자 포털",
zh: "Xaman SDK 与开发者门户",
},
theory: {
es: `**Xaman** (anteriormente XUMM) no es solo una wallet: es una plataforma de firma de transacciones que expone una **API REST y SDK** para desarrolladores. Gracias a ella puedes crear aplicaciones web o móviles que piden al usuario que firme transacciones en Xahau sin que nunca tengas acceso a sus claves privadas.
@@ -188,6 +190,22 @@ Your app Xaman API Xaman (mobile)
- Secret은 **백엔드에서만** 사용
이 구조를 이해하면 프론트엔드 로그인과 결제 요청 플로우를 쉽게 설계할 수 있습니다.`,
zh: `**Xaman** 不只是钱包,它还是一个让开发者构建签名流程的 **API 与 SDK 平台**。应用可以在完全不接触用户私钥的情况下安全地请求交易签名。
### XUMM SDK 可以做什么
- 基于 SignIn 的登录
- 为任意交易创建 payload
- 提供二维码或深链接
- 通过 WebSocket 实时接收批准结果
### 开始前准备
- 在 \`apps.xaman.dev\` 创建应用
- 获取 \`API Key\`\`API Secret\`
- Secret **只能放在后端**
理解这个结构后,就能更轻松地设计前端登录和支付请求流程。`,
},
codeBlocks: [
{
@@ -196,6 +214,7 @@ Your app Xaman API Xaman (mobile)
en: "SDK installation and basic setup",
jp: "SDKのインストールと基本設定",
ko: "SDK 설치 및 기본 설정",
zh: "SDK 安装与基础配置",
},
language: "bash",
code: {
@@ -230,6 +249,14 @@ npm install xumm
npm install xumm
# 설치된 버전 확인
npm list xumm`,
zh: `# 安装 Xaman SDK
npm install xumm
# React/Vite 项目同样需要
npm install xumm
# 查看已安装版本
npm list xumm`,
},
},
@@ -239,6 +266,7 @@ npm list xumm`,
en: "Initialization: frontend vs backend",
jp: "初期化:フロントエンドとバックエンド",
ko: "초기화: 프론트엔드 vs 백엔드",
zh: "初始化:前端 vs 后端",
},
language: "javascript",
code: {
@@ -314,6 +342,24 @@ const xummBackend = new Xumm("your-api-key-here", "your-api-secret-here");
const appInfo = await xumm.environment.getAppInfo();
console.log("앱 연결됨:", appInfo?.name);
console.log("앱 UUID:", appInfo?.uuidv4);`,
zh: `import { Xumm } from "xumm";
// ─────────────────────────────────────────────
// 前端(浏览器)— 仅使用 API Key
// API Key 是公开的,并走安全的 PKCE 流程
// ─────────────────────────────────────────────
const xumm = new Xumm("your-api-key-here");
// ─────────────────────────────────────────────
// 后端Node.js 服务器)— API Key + Secret
// Secret 绝不能出现在浏览器中
// ─────────────────────────────────────────────
const xummBackend = new Xumm("your-api-key-here", "your-api-secret-here");
// 验证连接是否正常
const appInfo = await xumm.environment.getAppInfo();
console.log("应用已连接:", appInfo?.name);
console.log("应用 UUID:", appInfo?.uuidv4);`,
},
},
],
@@ -324,12 +370,14 @@ console.log("앱 UUID:", appInfo?.uuidv4);`,
en: "What is the XUMM SDK?",
jp: "XUMM SDKとは",
ko: "XUMM SDK란?",
zh: "什么是 XUMM SDK",
},
content: {
es: "SDK oficial para integrar Xaman en tu app\n\n• Autenticar usuarios con SignIn\n• Crear payloads (solicitudes de firma)\n• Mostrar QR — el usuario escanea con Xaman\n• WebSocket: respuesta en tiempo real\n• El usuario firma, tú nunca ves las claves",
en: "Official SDK to integrate Xaman in your app\n\n• Authenticate users with SignIn\n• Create payloads (sign requests)\n• Show QR — user scans with Xaman\n• WebSocket: real-time response\n• User signs, you never see private keys",
jp: "アプリにXamanを統合するための公式SDK\n\n• SignInによるユーザー認証\n• ペイロード(署名リクエスト)の作成\n• QR表示 — ユーザーがXamanでスキャン\n• WebSocketリアルタイムレスポンス\n• ユーザーが署名、秘密鍵は見えない",
ko: "앱에 Xaman을 통합하는 공식 SDK\n\n• SignIn으로 사용자 인증\n• payload(서명 요청) 생성\n• QR 표시 — 사용자가 Xaman으로 스캔\n• WebSocket: 실시간 응답\n• 사용자가 서명, 개인키는 절대 노출 안 됨",
zh: "将 Xaman 集成到应用中的官方 SDK\n\n• 使用 SignIn 认证用户\n• 创建 payload签名请求\n• 显示二维码,用户用 Xaman 扫描\n• WebSocket实时响应\n• 用户自己签名,你不会看到私钥",
},
visual: "🔑",
},
@@ -339,12 +387,14 @@ console.log("앱 UUID:", appInfo?.uuidv4);`,
en: "Developer portal",
jp: "開発者ポータル",
ko: "개발자 포털",
zh: "开发者门户",
},
content: {
es: "apps.xaman.dev — tu centro de control\n\n• Crear app → obtener API Key + Secret\n• Whitelist de dominios permitidos\n• Configurar webhook URL\n• Ver estadísticas y logs de API\n\ndocs.xumm.dev — documentación completa",
en: "apps.xaman.dev — your control center\n\n• Create app → get API Key + Secret\n• Whitelist of allowed domains\n• Configure webhook URL\n• View stats and API logs\n\ndocs.xumm.dev — full documentation",
jp: "apps.xaman.dev — あなたのコントロールセンター\n\n• アプリ作成 → APIキーシークレット取得\n• 許可ドメインのホワイトリスト\n• WebhookURL設定\n• 統計とAPIログの確認\n\ndocs.xumm.dev — 完全なドキュメント",
ko: "apps.xaman.dev — 제어 센터\n\n• 앱 생성 → API Key + Secret 발급\n• 허용 도메인 화이트리스트\n• Webhook URL 설정\n• 통계 및 API 로그 확인\n\ndocs.xumm.dev — 전체 문서",
zh: "apps.xaman.dev —— 你的控制中心\n\n• 创建应用 → 获取 API Key + Secret\n• 配置允许域名白名单\n• 设置 Webhook URL\n• 查看统计与 API 日志\n\ndocs.xumm.dev —— 完整文档",
},
visual: "🖥️",
},
@@ -354,12 +404,14 @@ console.log("앱 UUID:", appInfo?.uuidv4);`,
en: "API Key vs API Secret",
jp: "APIキー対APIシークレット",
ko: "API Key vs API Secret",
zh: "API Key vs API Secret",
},
content: {
es: "Dos credenciales con roles distintos:\n\nAPI Key (pública)\n• Segura en el navegador\n• Flujo PKCE — no necesita Secret\n• Va en el código React/JS del frontend\n\nAPI Secret (privada)\n• SOLO en el servidor (Node.js)\n• NUNCA en el navegador\n• Permisos de escritura completos",
en: "Two credentials with different roles:\n\nAPI Key (public)\n• Safe in the browser\n• PKCE flow — no Secret needed\n• Goes in frontend React/JS code\n\nAPI Secret (private)\n• Server ONLY (Node.js)\n• NEVER in the browser\n• Full write permissions",
jp: "異なる役割を持つ2つの認証情報\n\nAPIキー公開\n• ブラウザで安全\n• PKCEフロー — シークレット不要\n• フロントエンドのReact/JSコードに記載\n\nAPIシークレット非公開\n• サーバーのみNode.js\n• ブラウザには絶対に記載しない\n• 完全な書き込み権限",
ko: "역할이 다른 두 가지 자격증명:\n\nAPI Key (공개)\n• 브라우저에서 안전\n• PKCE 흐름 — Secret 불필요\n• 프론트엔드 React/JS 코드에 사용\n\nAPI Secret (비공개)\n• 서버 전용 (Node.js)\n• 절대 브라우저에 노출 금지\n• 전체 쓰기 권한",
zh: "两种职责不同的凭证:\n\nAPI Key公开\n• 可安全放在浏览器中\n• 使用 PKCE 流程,不需要 Secret\n• 放在前端 React/JS 代码中\n\nAPI Secret私密\n• 只能放在服务器Node.js\n• 绝不能暴露到浏览器\n• 拥有完整写权限",
},
visual: "🔐",
},
@@ -372,6 +424,7 @@ console.log("앱 UUID:", appInfo?.uuidv4);`,
en: "Frontend: authentication with Xaman (QR Login)",
jp: "フロントエンドXamanによる認証QRログイン",
ko: "프론트엔드: Xaman 인증 (QR 로그인)",
zh: "前端:使用 Xaman 认证(二维码登录)",
},
theory: {
es: `La primera integración que construirás es el **login con Xaman**: un flujo en el que el usuario escanea un QR con su app Xaman y queda autenticado en tu aplicación web. Es el equivalente a "Conectar con MetaMask" pero para el ecosistema Xahau.
@@ -588,6 +641,23 @@ Viteがプロジェクトを自動生成します。変更が必要なファイ
- 딥링크 지원
프론트엔드 통합의 첫 단계로 가장 적합한 시나리오입니다.`,
zh: `Xaman 登录是让用户扫描二维码并在应用中签名,从而登录网页应用的方式。它和 Web3 中常见的“连接钱包”流程非常相似。
### 流程概览
1. 应用创建 SignIn payload
2. Xaman 返回二维码 URL 与 UUID
3. 用户扫描并在手机上确认
4. 应用接收地址并完成登录
### 优点
- 无密码
- 非托管
- 可通过签名验证身份
- 对移动端非常友好
这是前端集成中最适合入门的场景。`,
},
codeBlocks: [
{
@@ -596,6 +666,7 @@ Viteがプロジェクトを自動生成します。変更が必要なファイ
en: "SDK installation and project basic setup",
jp: "SDKのインストールとプロジェクトの基本設定",
ko: "SDK 설치 및 프로젝트 기본 설정",
zh: "SDK 安装与项目基础配置",
},
language: "bash",
code: {
@@ -614,6 +685,11 @@ npm run dev`,
cd xaman-login
npm install xumm xahau
# src/App.jsx 수정 후 실행:
npm run dev`,
zh: `npm create vite@latest xaman-login -- --template react
cd xaman-login
npm install xumm xahau
# 修改 src/App.jsx 后执行:
npm run dev`,
},
},
@@ -623,6 +699,7 @@ npm run dev`,
en: "App.jsx — QR modal login",
jp: "App.jsx — QRモーダルログイン",
ko: "App.jsx — QR 모달 로그인",
zh: "App.jsx —— 二维码弹窗登录",
},
language: "javascript",
code: {
@@ -1106,6 +1183,72 @@ export default function App() {
)}
</div>
);
}`,
zh: `// src/App.jsx —— 在页面内用二维码弹窗完成 Xaman 登录
import { useEffect, useState } from "react";
import { Xumm } from "xumm";
const xumm = new Xumm("YOUR_API_KEY_HERE");
function QRModal({ title, qrUrl, deepLink, onCancel }) {
return (
<div>
<h2>{title}</h2>
<img src={qrUrl} alt="QR Xaman" width={220} />
<p><a href={deepLink}>在手机中打开 Xaman</a></p>
<button onClick={onCancel}>取消</button>
</div>
);
}
export default function App() {
const [account, setAccount] = useState(null);
const [qrUrl, setQrUrl] = useState(null);
const [deepLink, setDeepLink] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
xumm.on("ready", async () => {
const me = await xumm.me;
if (me?.account) setAccount(me.account);
});
}, []);
async function connectWithXaman() {
setLoading(true);
setError(null);
try {
const { created, resolved } = await xumm.payload.createAndSubscribe(
{ txjson: { TransactionType: "SignIn", NetworkID: 21338 } },
(event) => {
if (typeof event.data.signed !== "undefined") return event.data;
}
);
setQrUrl(created.refs.qr_png);
setDeepLink(created.next.always);
const result = await resolved;
setQrUrl(null);
setDeepLink(null);
if (result.signed) {
const payload = await xumm.payload.get(created.uuid);
setAccount(payload.response.account);
} else {
setError("用户拒绝了登录");
}
} finally {
setLoading(false);
}
}
return (
<div>
<h1>Xaman 登录 —— 二维码弹窗</h1>
{account ? <p>已连接:{account}</p> : <button onClick={connectWithXaman} disabled={loading}>{loading ? "正在生成二维码..." : "连接 Xaman"}</button>}
{error && <p style={{ color: "red" }}>{error}</p>}
{qrUrl && <QRModal title="使用 Xaman 登录" qrUrl={qrUrl} deepLink={deepLink} onCancel={() => setQrUrl(null)} />}
</div>
);
}`,
ko: `// src/App.jsx — 자신의 페이지에서 QR 모달 로그인
// 실행 전:
@@ -1235,12 +1378,14 @@ export default function App() {
es: "Flujo de login con Xaman",
en: "Xaman login flow",
jp: "Xamanログインフロー",
zh: "Xaman 登录流程",
ko: "Xaman 로그인 흐름",
},
content: {
es: "Autenticación sin contraseña:\n\n1. Tu app crea payload SignIn\n2. Muestras el QR al usuario\n3. Usuario escanea con Xaman\n4. Usuario toca 'Sign' (sin fee)\n5. WebSocket te entrega la dirección\n6. El usuario está autenticado ✓",
en: "Passwordless authentication:\n\n1. Your app creates SignIn payload\n2. You show the QR to the user\n3. User scans with Xaman\n4. User taps 'Sign' (no fee)\n5. WebSocket delivers the address\n6. User is authenticated ✓",
jp: "パスワードレス認証:\n\n1. アプリがSignInペイロードを作成\n2. ユーザーにQRを表示\n3. ユーザーがXamanでスキャン\n4. ユーザーが「Sign」をタップ手数料なし\n5. WebSocketがアドレスを配信\n6. ユーザーが認証済み ✓",
zh: "无密码认证流程:\n\n1. 应用创建 SignIn payload\n2. 向用户显示二维码\n3. 用户用 Xaman 扫码\n4. 用户点击“Sign”无手续费\n5. WebSocket 返回地址\n6. 用户完成认证 ✓",
ko: "비밀번호 없는 인증:\n\n1. 앱이 SignIn payload 생성\n2. 사용자에게 QR 표시\n3. 사용자가 Xaman으로 스캔\n4. 사용자가 'Sign' 탭 (수수료 없음)\n5. WebSocket이 주소 전달\n6. 사용자 인증 완료 ✓",
},
visual: "📱",
@@ -1250,12 +1395,14 @@ export default function App() {
es: "Escritorio vs Móvil",
en: "Desktop vs Mobile",
jp: "デスクトップ対モバイル",
zh: "桌面端 vs 移动端",
ko: "데스크톱 vs 모바일",
},
content: {
es: "El modal maneja escritorio y móvil:\n\nEscritorio\n• El modal muestra la imagen QR (qr_png)\n• El usuario escanea con su app Xaman\n• El modal se cierra al confirmar la firma\n\nMóvil\n• El modal muestra el deep link (next.always)\n• Pulsa el enlace → abre Xaman automáticamente\n• Sin necesidad de escanear",
en: "The modal handles desktop and mobile:\n\nDesktop\n• Modal shows the QR image (qr_png)\n• User scans with their Xaman app\n• Modal closes when signature is confirmed\n\nMobile\n• Modal shows the deep link (next.always)\n• Tap the link → Xaman opens automatically\n• No scanning needed",
jp: "モーダルがデスクトップとモバイルを処理:\n\nデスクトップ\n• モーダルがQR画像qr_pngを表示\n• ユーザーがXamanアプリでスキャン\n• 署名確認後にモーダルが閉じる\n\nモバイル\n• モーダルがディープリンクnext.alwaysを表示\n• リンクをタップ → Xamanが自動で起動\n• スキャン不要",
zh: "弹窗同时处理桌面端和移动端:\n\n桌面端\n• 弹窗显示二维码图片qr_png\n• 用户用 Xaman 应用扫码\n• 签名确认后弹窗关闭\n\n移动端\n• 弹窗显示深链next.always\n• 点击链接 → 自动打开 Xaman\n• 无需扫码",
ko: "모달이 데스크톱과 모바일 모두 처리:\n\n데스크톱\n• 모달이 QR 이미지(qr_png) 표시\n• 사용자가 Xaman 앱으로 스캔\n• 서명 확인 후 모달 닫힘\n\n모바일\n• 모달이 딥링크(next.always) 표시\n• 링크 탭 → Xaman 자동 실행\n• 스캔 불필요",
},
visual: "💻",
@@ -1265,12 +1412,14 @@ export default function App() {
es: "Eventos del SDK",
en: "SDK events",
jp: "SDKイベント",
zh: "SDK 事件",
ko: "SDK 이벤트",
},
content: {
es: "payload.createAndSubscribe() desde el browser:\n\n1. La origin http://localhost:5173 está en la whitelist\n2. El browser envía Origin header → Xaman valida el CORS\n3. Devuelve created.refs.qr_png → imagen del QR\n4. Muestra el QR dentro del modal de tu página\n5. WebSocket espera → usuario firma → modal se cierra\n\nNo se abre ninguna ventana externa",
en: "payload.createAndSubscribe() from the browser:\n\n1. Origin http://localhost:5173 is in the whitelist\n2. Browser sends Origin header → Xaman validates CORS\n3. Returns created.refs.qr_png → QR image\n4. Shows QR inside your page modal\n5. WebSocket waits → user signs → modal closes\n\nNo external window is opened",
jp: "ブラウザからのpayload.createAndSubscribe()\n\n1. http://localhost:5173がホワイトリストにある\n2. ブラウザがOriginヘッダーを送信 → XamanがCORSを検証\n3. created.refs.qr_pngを返す → QR画像\n4. ページのモーダル内にQRを表示\n5. WebSocketが待機 → ユーザーが署名 → モーダルが閉じる\n\n外部ウィンドウは開かない",
zh: "浏览器中的 payload.createAndSubscribe()\n\n1. 将 http://localhost:5173 加入白名单\n2. 浏览器发送 Origin header → Xaman 校验 CORS\n3. 返回 created.refs.qr_png → 二维码图片\n4. 在页面弹窗中显示二维码\n5. WebSocket 等待 → 用户签名 → 弹窗关闭\n\n不会打开任何外部窗口",
ko: "브라우저에서 payload.createAndSubscribe():\n\n1. http://localhost:5173이 화이트리스트에 있음\n2. 브라우저가 Origin 헤더 전송 → Xaman이 CORS 검증\n3. created.refs.qr_png 반환 → QR 이미지\n4. 페이지 모달 내에 QR 표시\n5. WebSocket 대기 → 사용자 서명 → 모달 닫힘\n\n외부 창은 열리지 않음",
},
visual: "📡",
@@ -1283,6 +1432,7 @@ export default function App() {
es: "Frontend: construir y firmar un Payment con Xaman",
en: "Frontend: build and sign a Payment with Xaman",
jp: "フロントエンドXamanでPaymentを構築・署名",
zh: "前端:使用 Xaman 构建并签署 Payment",
ko: "프론트엔드: Xaman으로 Payment 생성 및 서명",
},
theory: {
@@ -1478,6 +1628,23 @@ const payloadResult = await xumm.payload.get(created.uuid);
const status = payloadResult.response.dispatched_result; // "tesSUCCESS" またはエラーコード
const txid = result.txid; // トランザクションハッシュ
\`\`\``,
zh: `**用户登录后**,就可以通过 Xaman 请求实际的 **Payment 交易签名**。网页应用负责构造交易 JSON用户在手机上做最终确认。
### 典型流程
1. 输入收款地址与金额
2. 创建 \`Payment\` payload
3. 显示新的二维码
4. 用户在 Xaman 中查看并批准
5. 应用接收 \`txid\` 和结果
### 注意点
- 金额始终要转换为 **drops**
- 明确设置当前网络 ID
- 批准后要确认交易是否真的写入了账本
把登录和支付分开理解,整个实现会清晰很多。`,
ko: `사용자가 로그인한 뒤에는 Xaman을 통해 실제 **Payment 트랜잭션 서명**을 요청할 수 있습니다. 웹앱은 트랜잭션 JSON을 만들고, 사용자는 모바일에서 최종 승인을 합니다.
### 전형적인 흐름
@@ -1502,6 +1669,7 @@ const txid = result.txid; // トランザクシ
es: "Instalación y configuración básica del proyecto",
en: "SDK installation and project basic setup",
jp: "SDKのインストールとプロジェクトの基本設定",
zh: "项目安装与基础配置",
ko: "SDK 설치 및 프로젝트 기본 설정",
},
language: "bash",
@@ -1519,6 +1687,12 @@ npm install xumm xahau
# After modifying src/App.jsx run:
npm run dev`,
jp: ``,
zh: `# 如果上一节已经做过,这一步可以跳过
npm create vite@latest xaman-login -- --template react
cd xaman-login
npm install xumm xahau
# 修改 src/App.jsx 后执行:
npm run dev`,
ko: `# 이전 단계에서 이미 했다면 이 부분은 건너뛰어도 됩니다
npm create vite@latest xaman-login -- --template react
cd xaman-login
@@ -1532,6 +1706,7 @@ npm run dev`,
es: "App.jsx — Login con QR modal + Payment con QR modal",
en: "App.jsx — QR modal login + QR modal payment",
jp: "App.jsx — QRモーダルログインQRモーダルPayment",
zh: "App.jsx —— 二维码弹窗登录 + 二维码弹窗支付",
ko: "App.jsx — QR 모달 로그인 + QR 모달 Payment",
},
language: "javascript",
@@ -2173,6 +2348,93 @@ export default function App() {
{qrUrl && <QRModal title={qrTitle} qrUrl={qrUrl} deepLink={deepLink} onCancel={cancel} />}
</div>
);
}`,
zh: `// src/App.jsx —— 同一页面内完成登录与 Payment 签名
import { useEffect, useState } from "react";
import { Xumm } from "xumm";
const xumm = new Xumm("YOUR_API_KEY_HERE");
function xahToDrops(xah) {
return String(Math.floor(Number(xah) * 1_000_000));
}
export default function App() {
const [account, setAccount] = useState(null);
const [destination, setDestination] = useState("");
const [amount, setAmount] = useState("");
const [qrUrl, setQrUrl] = useState(null);
const [deepLink, setDeepLink] = useState(null);
const [txid, setTxid] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
xumm.on("ready", async () => {
const me = await xumm.me;
if (me?.account) setAccount(me.account);
});
}, []);
async function connectWithXaman() {
const { created, resolved } = await xumm.payload.createAndSubscribe(
{ txjson: { TransactionType: "SignIn", NetworkID: 21338 } },
(event) => {
if (typeof event.data.signed !== "undefined") return event.data;
}
);
setQrUrl(created.refs.qr_png);
setDeepLink(created.next.always);
const result = await resolved;
setQrUrl(null);
setDeepLink(null);
if (result.signed) {
const payload = await xumm.payload.get(created.uuid);
setAccount(payload.response.account);
}
}
async function sendPayment(e) {
e.preventDefault();
setError(null);
const { created, resolved } = await xumm.payload.createAndSubscribe(
{
txjson: {
TransactionType: "Payment",
NetworkID: 21338,
Account: account,
Destination: destination,
Amount: xahToDrops(amount),
},
},
(event) => {
if (typeof event.data.signed !== "undefined") return event.data;
}
);
setQrUrl(created.refs.qr_png);
setDeepLink(created.next.always);
const result = await resolved;
setQrUrl(null);
setDeepLink(null);
if (result.signed) setTxid(result.txid);
else setError("用户拒绝了交易");
}
return (
<div>
<h1>Xaman Payment —— 二维码弹窗</h1>
{!account ? <button onClick={connectWithXaman}>连接 Xaman</button> : null}
{account && !qrUrl && !txid ? (
<form onSubmit={sendPayment}>
<input value={destination} onChange={(e) => setDestination(e.target.value)} placeholder="收款地址" />
<input value={amount} onChange={(e) => setAmount(e.target.value)} placeholder="金额XAH" />
<button type="submit">发送 Payment</button>
</form>
) : null}
{qrUrl && <a href={deepLink}>打开 Xaman</a>}
{txid && <p>TXID: {txid}</p>}
{error && <p style={{ color: "red" }}>{error}</p>}
</div>
);
}`,
jp: `// src/App.jsx — すべて自分のページでログインもPaymentもQRモーダル
// 実行前に:
@@ -2530,12 +2792,14 @@ export default function App() {
es: "Flujo de pago con Xaman",
en: "Payment flow with Xaman",
jp: "Xamanを使った支払いフロー",
zh: "使用 Xaman 的支付流程",
ko: "Xaman을 사용한 결제 흐름",
},
content: {
es: "El usuario firma dos veces:\n\n1er QR — Login (SignIn, sin fee)\n• Identifica al usuario → obttienes su dirección\n\n2do QR — Pago (Payment, con fee)\n• Muestra destino y cantidad\n• Usuario revisa y aprueba\n• Recibes txid de la tx firmada",
en: "The user scans twice:\n\n1st QR — Login (SignIn, no fee)\n• Identifies user → you get their address\n\n2nd QR — Payment (with fee)\n• Shows destination and amount\n• User reviews and approves\n• You receive txid of signed tx",
jp: "ユーザーは2回スキャン\n\n1枚目QR — ログインSignIn、手数料なし\n• ユーザーを識別 → アドレスを取得\n\n2枚目QR — 支払い(手数料あり)\n• 宛先と金額を表示\n• ユーザーが確認・承認\n• 署名済みtxのtxidを受信",
zh: "用户需要扫描两次:\n\n第 1 个二维码 —— 登录SignIn无手续费\n• 识别用户 → 获取其地址\n\n第 2 个二维码 —— 支付Payment有手续费\n• 显示目标地址和金额\n• 用户检查并批准\n• 你收到已签名交易的 txid",
ko: "사용자가 두 번 스캔:\n\n1번째 QR — 로그인 (SignIn, 수수료 없음)\n• 사용자 식별 → 주소 획득\n\n2번째 QR — 결제 (수수료 있음)\n• 수신 주소와 금액 표시\n• 사용자가 확인 후 승인\n• 서명된 tx의 txid 수신",
},
visual: "💸",
@@ -2545,12 +2809,14 @@ export default function App() {
es: "Drops: la unidad de XAH",
en: "Drops: the XAH unit",
jp: "DropsXAHの単位",
zh: "DropsXAH 的单位",
ko: "Drops: XAH 단위",
},
content: {
es: "Las cantidades se expresan en drops:\n\n1 XAH = 1,000,000 drops\n0.5 XAH = 500,000 drops\n0.000001 XAH = 1 drop (mínimo)\n\nConversión en código:\ndrops = Math.floor(xah * 1_000_000)\nxah = drops / 1_000_000\n\nSiempre usa strings para Amount en el JSON",
en: "Amounts are expressed in drops:\n\n1 XAH = 1,000,000 drops\n0.5 XAH = 500,000 drops\n0.000001 XAH = 1 drop (minimum)\n\nConversion in code:\ndrops = Math.floor(xah * 1_000_000)\nxah = drops / 1_000_000\n\nAlways use strings for Amount in JSON",
jp: "金額はdropsで表します\n\n1 XAH = 1,000,000 drops\n0.5 XAH = 500,000 drops\n0.000001 XAH = 1 drop最小単位\n\nコードでの変換\ndrops = Math.floor(xah × 1,000,000)\nxah = drops / 1,000,000\n\nJSONのAmountには常にstringを使用",
zh: "金额以 drops 表示:\n\n1 XAH = 1,000,000 drops\n0.5 XAH = 500,000 drops\n0.000001 XAH = 1 drop最小单位\n\n代码中的换算\ndrops = Math.floor(xah * 1_000_000)\nxah = drops / 1_000_000\n\nJSON 中的 Amount 一律使用字符串",
ko: "금액은 drops로 표현:\n\n1 XAH = 1,000,000 drops\n0.5 XAH = 500,000 drops\n0.000001 XAH = 1 drop (최소)\n\n코드에서 변환:\ndrops = Math.floor(xah * 1_000_000)\nxah = drops / 1_000_000\n\nJSON의 Amount에는 항상 string 사용",
},
visual: "🔢",
@@ -2560,12 +2826,14 @@ export default function App() {
es: "createAndSubscribe: el método clave",
en: "createAndSubscribe: the key method",
jp: "createAndSubscribe重要なメソッド",
zh: "createAndSubscribe关键方法",
ko: "createAndSubscribe: 핵심 메서드",
},
content: {
es: "Un solo método para crear + escuchar:\n\nconst { created, resolved } = await\n xumm.payload.createAndSubscribe(\n { txjson: transaccion },\n (event) => {\n if ('signed' in event.data)\n return event.data\n }\n )\n\ncreated.refs.qr_png → URL del QR\nawait resolved → firma o rechazo",
en: "One method to create + listen:\n\nconst { created, resolved } = await\n xumm.payload.createAndSubscribe(\n { txjson: transaction },\n (event) => {\n if ('signed' in event.data)\n return event.data\n }\n )\n\ncreated.refs.qr_png → QR URL\nawait resolved → sign or reject",
jp: "作成+リッスンを一つのメソッドで:\n\nconst { created, resolved } = await\n xumm.payload.createAndSubscribe(\n { txjson: transaction },\n (event) => {\n if ('signed' in event.data)\n return event.data\n }\n )\n\ncreated.refs.qr_png → QR URL\nawait resolved → 署名または拒否",
zh: "一个方法同时完成创建 + 监听:\n\nconst { created, resolved } = await\n xumm.payload.createAndSubscribe(\n { txjson: transaction },\n (event) => {\n if ('signed' in event.data)\n return event.data\n }\n )\n\ncreated.refs.qr_png → 二维码 URL\nawait resolved → 签名或拒绝",
ko: "생성 + 수신을 한 메서드로:\n\nconst { created, resolved } = await\n xumm.payload.createAndSubscribe(\n { txjson: transaction },\n (event) => {\n if ('signed' in event.data)\n return event.data\n }\n )\n\ncreated.refs.qr_png → QR URL\nawait resolved → 서명 또는 거부",
},
visual: "🔄",
@@ -2578,6 +2846,7 @@ export default function App() {
es: "Backend: servidor Node.js con Express y Xaman",
en: "Backend: Node.js server with Express and Xaman",
jp: "バックエンドExpressとXamanを使ったNode.jsサーバー",
zh: "后端:使用 Express 与 Xaman 的 Node.js 服务器",
ko: "백엔드: Express와 Xaman을 사용하는 Node.js 서버",
},
theory: {
@@ -2809,6 +3078,23 @@ xaman-backend/
│ └── payment.js # 支払いルート
└── webhook.js # XamanのWebhookハンドラー
\`\`\``,
zh: `虽然也可以在浏览器里直接创建 payload但加入 **后端** 后,在安全性和业务逻辑上会更有优势。
### 后端方式的优点
- \`API Secret\` 只保存在服务器
- 可以在付款前加入校验逻辑
- 可以保存交易记录与审计日志
- 更方便接收 Webhook 并对接其他系统
### 常见结构
- 前端调用 \`/payment\` 之类的接口
- 服务器创建 Xaman payload
- 服务器返回 QR URL 与 UUID
- 通过 Webhook 或轮询确认签名结果
在实际项目里,这通常会是默认架构。`,
ko: `브라우저에서 직접 payload를 만드는 방법도 가능하지만, **백엔드**를 두면 보안과 비즈니스 로직 측면에서 훨씬 유리합니다.
### 백엔드 방식의 장점
@@ -2833,6 +3119,7 @@ xaman-backend/
es: "Comandos de instalación",
en: "Installation commands",
jp: "インストールコマンド",
zh: "安装命令",
ko: "설치 명령어",
},
language: "bash",
@@ -2891,6 +3178,24 @@ printf ".env\\nnode_modules/\\n" > .gitignore
# 5. Start in development mode (once you have package.json, server.js and public/index.html)
npm run dev
# Open http://localhost:3001 in the browser`,
zh: `# 1. 创建项目目录
mkdir xaman-backend
cd xaman-backend
# 2. 创建静态前端文件目录
mkdir public
# 3. 安装依赖
npm init -y
npm install express xumm dotenv cors
npm install --save-dev nodemon
# 4. 创建 .gitignore
printf ".env\\nnode_modules/\\n" > .gitignore
# 5. 开发模式启动(准备好 package.json、server.js 和 public/index.html 之后)
npm run dev
# 在浏览器中打开 http://localhost:3001`,
ko: `# 1. 프로젝트 디렉토리 생성
mkdir xaman-backend
cd xaman-backend
@@ -2916,6 +3221,7 @@ npm run dev
es: "package.json — copia y pega este archivo completo",
en: "package.json — copy and paste this complete file",
jp: "package.json — このファイルをそのままコピー",
zh: "package.json —— 直接复制整个文件",
ko: "package.json — 이 파일 전체를 복사하세요",
},
language: "json",
@@ -2943,6 +3249,7 @@ npm run dev
es: ".env — credenciales (nunca subir a git)",
en: ".env — credentials (never push to git)",
jp: ".env — 認証情報gitにpushしない",
zh: ".env —— 凭证(不要提交到 git",
ko: ".env — 자격증명 (git에 절대 올리지 마세요)",
},
language: "bash",
@@ -2962,6 +3269,12 @@ PORT=3001`,
jp: `# Create the .env file in the root of the xaman-backend/ project
# Replace the values with those from your app at apps.xaman.dev
XUMM_API_KEY=your-api-key-here
XUMM_API_SECRET=your-api-secret-here
PORT=3001`,
zh: `# 在 xaman-backend/ 项目根目录创建 .env 文件
# 用 apps.xaman.dev 中你的应用值替换下面内容
XUMM_API_KEY=your-api-key-here
XUMM_API_SECRET=your-api-secret-here
PORT=3001`,
@@ -2978,6 +3291,7 @@ PORT=3001`,
es: "server.js — Servidor Express completo con Xaman",
en: "server.js — Full Express server with Xaman",
jp: "server.js — XamanとExpressの完全なサーバー",
zh: "server.js —— 完整的 Express + Xaman 服务端",
ko: "server.js — Xaman과 Express 완전한 서버",
},
language: "javascript",
@@ -3275,6 +3589,75 @@ app.post("/webhook/xaman", (req, res) => {
app.listen(PORT, () => {
console.log(\`Server running at http://localhost:\${PORT}\`);
console.log(\`Open in browser: http://localhost:\${PORT}\`);
});`,
zh: `// server.js —— 使用 Express 与 Xaman 的最小后端示例
import "dotenv/config";
import express from "express";
import cors from "cors";
import { Xumm } from "xumm";
const app = express();
const PORT = process.env.PORT || 3001;
const xumm = new Xumm(process.env.XUMM_API_KEY, process.env.XUMM_API_SECRET);
app.use(cors());
app.use(express.json());
app.use(express.static("public"));
app.post("/api/login", async (_req, res) => {
const payload = await xumm.payload.create({
txjson: { TransactionType: "SignIn", NetworkID: 21338 },
});
res.json({
uuid: payload.uuid,
qrUrl: payload.refs.qr_png,
deepLink: payload.next.always,
});
});
app.get("/api/login/:uuid", async (req, res) => {
const payload = await xumm.payload.get(req.params.uuid);
res.json({
signed: payload.meta.signed,
expired: payload.meta.expired,
account: payload.response?.account ?? null,
});
});
app.post("/api/payment", async (req, res) => {
const { origin, destination, amountXAH } = req.body;
const payload = await xumm.payload.create({
txjson: {
TransactionType: "Payment",
NetworkID: 21338,
Account: origin,
Destination: destination,
Amount: String(Math.floor(Number(amountXAH) * 1_000_000)),
},
});
res.json({
uuid: payload.uuid,
qrUrl: payload.refs.qr_png,
deepLink: payload.next.always,
});
});
app.get("/api/payment/:uuid", async (req, res) => {
const payload = await xumm.payload.get(req.params.uuid);
res.json({
signed: payload.meta.signed,
expired: payload.meta.expired,
txid: payload.response?.txid ?? null,
});
});
app.post("/webhook/xaman", (req, res) => {
console.log("Webhook received:", JSON.stringify(req.body, null, 2));
res.sendStatus(200);
});
app.listen(PORT, () => {
console.log(\`Server running at http://localhost:\${PORT}\`);
});`,
jp: `// server.js
import "dotenv/config";
@@ -3425,6 +3808,7 @@ app.listen(PORT, () => {
es: "public/index.html — Interfaz completa (pégala en xaman-backend/public/)",
en: "public/index.html — Full UI (paste into xaman-backend/public/)",
jp: "public/index.html — 完全なUIxaman-backend/public/に貼り付け)",
zh: "public/index.html —— 完整界面(粘贴到 xaman-backend/public/",
ko: "public/index.html — 전체 UI (xaman-backend/public/에 붙여넣기)",
},
language: "html",
@@ -3806,6 +4190,48 @@ app.listen(PORT, () => {
}
</script>
</body>
</html>`,
zh: `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Xaman Backend Demo</title>
</head>
<body>
<h1>💸 Xaman Backend Demo</h1>
<p>这个页面通过后端创建登录与支付 payload。</p>
<section>
<button id="btnLogin">连接 Xaman</button>
<div id="loginQR"></div>
</section>
<section>
<input id="inputDestination" placeholder="收款地址" />
<input id="inputAmount" placeholder="金额XAH" />
<button id="btnPayment">发送 Payment</button>
<div id="paymentQR"></div>
<p id="result"></p>
</section>
<script>
const API = "/api";
async function waitForStatus(uuid, route) {
return new Promise((resolve) => {
const timer = setInterval(async () => {
const res = await fetch(\`\${API}/\${route}/\${uuid}\`);
const data = await res.json();
if (data.signed || data.expired) {
clearInterval(timer);
resolve(data);
}
}, 2000);
});
}
</script>
</body>
</html>`,
jp: `<!DOCTYPE html>
<html lang="ja">
@@ -4192,6 +4618,7 @@ app.listen(PORT, () => {
es: "src/App.jsx — Frontend React que consume el backend",
en: "src/App.jsx — React frontend consuming the backend",
jp: "src/App.jsx — バックエンドを使用するReactフロントエンドステータスポーリング",
zh: "src/App.jsx —— 使用后端的 React 前端",
ko: "src/App.jsx — 백엔드를 사용하는 React 프론트엔드 (상태 폴링)",
},
language: "javascript",
@@ -4517,6 +4944,75 @@ export default function App() {
)}
</div>
);
}`,
zh: `// src/App.jsx —— 通过后端创建 payload 的 React 前端
import { useState } from "react";
const API = "http://localhost:3001/api";
async function waitForSignature(uuid, route, intervalMs = 2000) {
return new Promise((resolve) => {
const timer = setInterval(async () => {
const res = await fetch(\`\${API}/\${route}/\${uuid}\`);
const data = await res.json();
if (data.signed || data.expired) {
clearInterval(timer);
resolve(data);
}
}, intervalMs);
});
}
export default function App() {
const [account, setAccount] = useState(null);
const [qrUrl, setQrUrl] = useState(null);
const [deepLink, setDeepLink] = useState(null);
const [destination, setDestination] = useState("");
const [amount, setAmount] = useState("");
const [txid, setTxid] = useState(null);
async function handleLogin() {
const res = await fetch(\`\${API}/login\`, { method: "POST" });
const data = await res.json();
setQrUrl(data.qrUrl);
setDeepLink(data.deepLink);
const result = await waitForSignature(data.uuid, "login");
setQrUrl(null);
setDeepLink(null);
if (result.signed) setAccount(result.account);
}
async function handlePayment(e) {
e.preventDefault();
const res = await fetch(\`\${API}/payment\`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ origin: account, destination, amountXAH: Number(amount) }),
});
const data = await res.json();
setQrUrl(data.qrUrl);
setDeepLink(data.deepLink);
const result = await waitForSignature(data.uuid, "payment");
setQrUrl(null);
setDeepLink(null);
if (result.signed) setTxid(result.txid);
}
return (
<div>
<h1>💸 Xahau Payment (Backend)</h1>
{!account ? <button onClick={handleLogin}>连接 Xaman</button> : <p>已连接:{account}</p>}
{qrUrl ? <a href={deepLink}>在 Xaman 中打开</a> : null}
{account && !txid ? (
<form onSubmit={handlePayment}>
<input value={destination} onChange={(e) => setDestination(e.target.value)} placeholder="收款地址" />
<input value={amount} onChange={(e) => setAmount(e.target.value)} placeholder="金额XAH" />
<button type="submit">发送</button>
</form>
) : null}
{txid ? <p>交易已发送:{txid}</p> : null}
</div>
);
}`,
jp: `// src/App.jsx — バックエンドを使用してペイロードを作成するフロントエンド
import { useState } from "react";
@@ -4849,12 +5345,14 @@ export default function App() {
es: "Frontend vs Backend: cuándo usar cada uno",
en: "Frontend vs Backend: when to use each",
jp: "フロントエンド対バックエンド:使い分け",
zh: "Frontend vs Backend什么时候用哪一种",
ko: "프론트엔드 vs 백엔드: 언제 무엇을 사용할지",
},
content: {
es: "Frontend (solo API Key)\n• Apps simples, demos, prototipos\n• Sin lógica de negocio compleja\n• El SDK crea los payloads en el navegador\n\nBackend (API Key + Secret)\n• Aplicaciones de producción\n• Validación y auditoría del servidor\n• Webhooks para notificaciones\n• Integración con base de datos",
en: "Frontend (API Key only)\n• Simple apps, demos, prototypes\n• No complex business logic\n• SDK creates payloads in browser\n\nBackend (API Key + Secret)\n• Production applications\n• Server-side validation and audit\n• Webhooks for notifications\n• Database integration",
jp: "フロントエンドAPIキーのみ\n• シンプルなアプリ、デモ、プロトタイプ\n• 複雑なビジネスロジックなし\n• SDKがブラウザでペイロードを作成\n\nバックエンドAPIキーシークレット\n• 本番アプリケーション\n• サーバーサイドの検証と監査\n• 通知用Webhook\n• データベース連携",
zh: "Frontend仅 API Key\n• 简单应用、演示、原型\n• 没有复杂业务逻辑\n• SDK 在浏览器中创建 payload\n\nBackendAPI Key + Secret\n• 生产环境应用\n• 服务器侧校验与审计\n• 用 Webhook 接收通知\n• 便于集成数据库",
ko: "프론트엔드 (API Key만)\n• 간단한 앱, 데모, 프로토타입\n• 복잡한 비즈니스 로직 없음\n• SDK가 브라우저에서 payload 생성\n\n백엔드 (API Key + Secret)\n• 프로덕션 애플리케이션\n• 서버 측 검증 및 감사\n• 알림용 Webhook\n• 데이터베이스 연동",
},
visual: "⚖️",
@@ -4864,12 +5362,14 @@ export default function App() {
es: "Arquitectura: frontend + backend + Xaman",
en: "Architecture: frontend + backend + Xaman",
jp: "アーキテクチャフロントエンドバックエンドXaman",
zh: "架构:前端 + 后端 + Xaman",
ko: "아키텍처: 프론트엔드 + 백엔드 + Xaman",
},
content: {
es: "Flujo de datos completo:\n\n1. React → POST /api/pago → Express\n2. Express → crear payload → Xaman API\n3. Xaman API → uuid + QR → Express\n4. Express → qrUrl → React\n5. React muestra QR al usuario\n6. Usuario firma en Xaman app\n7. Xaman → webhook → Express\n8. Express guarda txid en BD",
en: "Complete data flow:\n\n1. React → POST /api/pago → Express\n2. Express → create payload → Xaman API\n3. Xaman API → uuid + QR → Express\n4. Express → qrUrl → React\n5. React shows QR to user\n6. User signs in Xaman app\n7. Xaman → webhook → Express\n8. Express saves txid to DB",
jp: "完全なデータフロー:\n\n1. React → POST /api/pago → Express\n2. Express → ペイロード作成 → Xaman API\n3. Xaman API → uuid + QR → Express\n4. Express → qrUrl → React\n5. ReactがユーザーにQRを表示\n6. ユーザーがXamanアプリで署名\n7. Xaman → webhook → Express\n8. ExpressがtxidをDBに保存",
zh: "完整的数据流:\n\n1. React → POST /api/payment → Express\n2. Express → 创建 payload → Xaman API\n3. Xaman API → uuid + QR → Express\n4. Express → qrUrl → React\n5. React 向用户显示二维码\n6. 用户在 Xaman 应用中签名\n7. Xaman → webhook → Express\n8. Express 将 txid 保存到数据库",
ko: "완전한 데이터 흐름:\n\n1. React → POST /api/payment → Express\n2. Express → payload 생성 → Xaman API\n3. Xaman API → uuid + QR → Express\n4. Express → qrUrl → React\n5. React가 사용자에게 QR 표시\n6. 사용자가 Xaman 앱에서 서명\n7. Xaman → webhook → Express\n8. Express가 txid를 DB에 저장",
},
visual: "🏗️",
@@ -4879,12 +5379,14 @@ export default function App() {
es: "Webhooks: recibir la firma en el servidor",
en: "Webhooks: receive the signature on the server",
jp: "Webhookサーバーで署名を受信",
zh: "Webhook在服务器端接收签名",
ko: "Webhook: 서버에서 서명 수신",
},
content: {
es: "Configura tu webhook en apps.xaman.dev\n\nXaman llama a tu endpoint cuando:\n• El usuario firma el payload ✅\n• El usuario rechaza el payload ❌\n• El payload expira ⏰\n\nTu servidor debe responder 200 rápido\nProcesa la lógica de forma asíncrona\nUsa ngrok para probar en local",
en: "Configure your webhook at apps.xaman.dev\n\nXaman calls your endpoint when:\n• User signs the payload ✅\n• User rejects the payload ❌\n• Payload expires ⏰\n\nYour server must respond 200 quickly\nProcess logic asynchronously\nUse ngrok to test locally",
jp: "apps.xaman.devでWebhookを設定\n\nXamanがエンドポイントを呼び出す時\n• ユーザーがペイロードに署名 ✅\n• ユーザーがペイロードを拒否 ❌\n• ペイロードが期限切れ ⏰\n\nサーバーは素早く200で応答する必要あり\nロジックは非同期で処理\nローカルテストにはngrokを使用",
zh: "在 apps.xaman.dev 中配置你的 webhook\n\nXaman 会在以下情况调用你的端点:\n• 用户签署 payload ✅\n• 用户拒绝 payload ❌\n• payload 过期 ⏰\n\n服务器应尽快返回 200\n业务逻辑异步处理\n本地测试可使用 ngrok",
ko: "apps.xaman.dev에서 webhook 설정\n\nXaman이 엔드포인트를 호출하는 경우:\n• 사용자가 payload에 서명 ✅\n• 사용자가 payload를 거부 ❌\n• Payload가 만료 ⏰\n\n서버는 빠르게 200으로 응답해야 함\n로직은 비동기로 처리\n로컬 테스트에는 ngrok 사용",
},
visual: "🔔",