Compare commits

..

6 Commits

Author SHA1 Message Date
amarantha-k
4bafb3502a Updated paths in sidebar.yaml file 2026-01-09 23:07:39 +05:30
amarantha-k
f9a9ae9ca8 Fixed tutorial sidebar to use group headings 2026-01-09 21:39:40 +05:30
amarantha-k
b3b40a8eff Fix broken links 2026-01-09 18:00:41 +05:30
amarantha-k
00fb447a76 fix broken links 2026-01-09 03:06:43 +05:30
amarantha-k
1e9e2ba43b Fix broken links 2025-12-18 17:49:45 -08:00
amarantha-k
2dde2a7f60 WIP revised tutorials IA 2025-12-08 14:36:20 -08:00
586 changed files with 9208 additions and 70253 deletions

2
.gitignore vendored
View File

@@ -8,8 +8,6 @@ yarn-error.log
*.iml
.venv/
_code-samples/*/js/package-lock.json
*.css.map
# PHP
composer.lock
.cursor/

View File

@@ -74,8 +74,8 @@ Si una dirección de reserva se ve comprometida, las consecuencias son similares
- [Cuentas](index.md)
- [Claves criptográficas](cryptographic-keys.md)
- **Tutoriales:**
- [Asignar par de claves regulares](../../tutorials/how-tos/manage-account-settings/assign-a-regular-key-pair.md)
- [Cambiar o eliminar par de claves regulares](../../tutorials/how-tos/manage-account-settings/change-or-remove-a-regular-key-pair.md)
- [Asignar par de claves regulares](/docs/tutorials/best-practices/key-management/assign-a-regular-key-pair.md)
- [Cambiar o eliminar par de claves regulares](/docs/tutorials/best-practices/key-management/change-or-remove-a-regular-key-pair.md)
- **Referencias:**
- [metodo account_info][]
- [Transacción SetRegularKey][]

View File

@@ -88,7 +88,7 @@ El [metodo wallet_propose][] es una forma de generar el par de claves maestras.
**Atención:** Si un actor malicioso conoce tu clave privada maestra (o semilla), tendrá control completo sobre tu cuenta, a no ser que tu par de claves maestras se inhabilite. Puedes tomar todo tu dinero de la cuenta posee y causar un daño irreparable. ¡Trata tus valores secretos con cuidado!
Dado que cambiar el par de claves maestras es imposible, debes cuidarlo en proporción al valor de lo que posea. Una buena práctica es [guardar tu par de claves maestras offline](../../tutorials/how-tos/manage-account-settings/offline-account-setup.md) y configurar un par de claves normales para firmar transacciones en tu cuenta. Al mantener el par de claves maestras activadas pero offline, puedes estar razonablemente seguro de que nadie puede acceder a él a través de Internet, pero aun así deberías encontrarlo en caso de una emergencia.
Dado que cambiar el par de claves maestras es imposible, debes cuidarlo en proporción al valor de lo que posea. Una buena práctica es [guardar tu par de claves maestras offline](/docs/tutorials/best-practices/key-management/offline-account-setup.md) y configurar un par de claves normales para firmar transacciones en tu cuenta. Al mantener el par de claves maestras activadas pero offline, puedes estar razonablemente seguro de que nadie puede acceder a él a través de Internet, pero aun así deberías encontrarlo en caso de una emergencia.
Mantener tu par de claves maestras offline significa no colocar tu información secreta (passphrase, semilla, or clave privada) en cualquier sitio en que los actores maliciosos puedan tener acceso a él. En general, esto quiere decir que no está al alcance de un programa inofrmático que interactúe con Internet. Por ejemplo, puedes guardarlo en un equipo que no se conecta nunca a Internet, en un trozo de papel guardado en una caja fuerte, o tenerla completamente memorizada. (Memorizarla tiene algunos puntos inconvenientes, incluido ser imposible pasar la clave una vez muerto.)
@@ -119,7 +119,7 @@ Una buena práctica de seguridad es guardar tu clave privada maestra en algun si
El par de claves normales tiene el mismo formato que el par de claves maestras. Las generas de la misma forma (por ejemplo, usando el [método wallet_propose][]). La única diferencia es que el par de claves normales es que el par no está intrínsicamente vinculado a la cuenta para la que firma transacciones. Es posible (pero no es buena idea) utilizar el par de claves maestras de una cuenta como lel par de claves normales para otra cuenta.
La [transacción SetRegularKey][] asigna o cambia el par de claves normales de una cuenta. Para un tutorial de asignación o cambio de un par de claves normales, ver [Asignar par de claves normales](../../tutorials/how-tos/manage-account-settings/assign-a-regular-key-pair.md).
La [transacción SetRegularKey][] asigna o cambia el par de claves normales de una cuenta. Para un tutorial de asignación o cambio de un par de claves normales, ver [Asignar par de claves normales](/docs/tutorials/best-practices/key-management/assign-a-regular-key-pair.md).
## Algorítmos de firma
@@ -248,8 +248,8 @@ Los pasos para derivar par de claves de cuenta XRP Ledger secp256k1 desde un val
- **Conceptos:**
- [Direcciones de emisión y operacionales](account-types.md)
- **Tutoriales:**
- [Asignación de par de claves normales](../../tutorials/how-tos/manage-account-settings/assign-a-regular-key-pair.md)
- [Cambiar o eliminar par de claves normales](../../tutorials/how-tos/manage-account-settings/change-or-remove-a-regular-key-pair.md)
- [Asignación de par de claves normales](/docs/tutorials/best-practices/key-management/assign-a-regular-key-pair.md)
- [Cambiar o eliminar par de claves normales](/docs/tutorials/best-practices/key-management/change-or-remove-a-regular-key-pair.md)
- **Referencias:**
- [Transacción SetRegularKey][]
- [Objeto de ledger AccountRoot](../../references/protocol/ledger-data/ledger-entry-types/accountroot.md)

View File

@@ -63,7 +63,7 @@ La forma típica de obtener una cuenta en el XRP Ledger es la siguiente:
- [Transacción Payment][]
- [Objeto AccountRoot](../../references/protocol/ledger-data/ledger-entry-types/accountroot.md)
- **Tutoriales:**
- [Administrar configuración de la cuenta (Categoría)](../../tutorials/how-tos/manage-account-settings/index.md)
- [Monitorizar pagos entrantes con WebSocket](../../tutorials/http-websocket-apis/build-apps/monitor-incoming-payments-with-websocket.md)
- [Administrar configuración de la cuenta (Categoría)](/docs/tutorials/best-practices/key-management/assign-a-regular-key-pair.md)
- [Monitorizar pagos entrantes con WebSocket](/docs/tutorials/advanced-developer-topics/client-library-development/monitor-incoming-payments-with-websocket.md)
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -60,7 +60,7 @@ Podría darse el caso donde crees una lista de multi firma como "plan de respald
Para enviar transacciones multi-signed de forma satisfactoria, debes de hacer todo lo siguiente:
* La dirección que envía la transacción (especificada en el campo `Account`) debe tener un [objeto `SignerList` en el ledger ](../../references/protocol/ledger-data/ledger-entry-types/signerlist.md). Para instrucciones de cómo hacer esto, ver [Set Up Multi-Signing](../../tutorials/how-tos/manage-account-settings/set-up-multi-signing.md).
* La dirección que envía la transacción (especificada en el campo `Account`) debe tener un [objeto `SignerList` en el ledger ](../../references/protocol/ledger-data/ledger-entry-types/signerlist.md). Para instrucciones de cómo hacer esto, ver [Set Up Multi-Signing](/docs/tutorials/best-practices/key-management/set-up-multi-signing.md).
* La transacción debe incluir el campo `SigningPubKey` como un valor vacío.
* La transacción debe incluir el [campo `Signers`](../../references/protocol/transactions/common-fields.md#signers-field) conteniendo un array de firmas.
* Las firmas presentadas en el array `Signers` debe coincidir con los firmantes definidos en la `SignerList`.
@@ -72,8 +72,8 @@ Para enviar transacciones multi-signed de forma satisfactoria, debes de hacer to
## Ver también
- **Tutoriales:**
- [Configurar Multi-Signing](../../tutorials/how-tos/manage-account-settings/set-up-multi-signing.md)
- [Envíar una transacción Multi-Signed](../../tutorials/how-tos/manage-account-settings/send-a-multi-signed-transaction.md)
- [Configurar Multi-Signing](/docs/tutorials/best-practices/key-management/set-up-multi-signing.md)
- [Envíar una transacción Multi-Signed](/docs/tutorials/best-practices/key-management/send-a-multi-signed-transaction.md)
- **Conceptos:**
- [Claves criptográficas](cryptographic-keys.md)
- [Coste de transacción especial para transacciones Multi-signed](../transactions/transaction-cost.md#special-transaction-costs)

View File

@@ -54,7 +54,7 @@ Las aplicaciones pueden buscar los valores de las reservas base e incremental ac
Para determinar las reservas de propietario de una cuenta, hay que multiplicar la reserva incremental por el número de objetos que la cuenta posee. Para mirar el número de objetos que una cuenta posee, llama al [método account_info][] y toma `account_data.OwnerCount`.
Para calcular el requisito total de direcciones, multiplica `OwnerCount` por `reserve_inc_xrp`, y luego suma `reserve_base_xrp`. [Aquí tienes una demostración](../../tutorials/python/build-apps/build-a-desktop-wallet-in-python.md#codeblock-17) del cálculo en Python.
Para calcular el requisito total de direcciones, multiplica `OwnerCount` por `reserve_inc_xrp`, y luego suma `reserve_base_xrp`. [Aquí tienes una demostración](/docs/tutorials/sample-apps/build-a-desktop-wallet-in-python.md#codeblock-17) del cálculo en Python.
## Quedarse por debajo del requisito de reserva
@@ -76,6 +76,6 @@ El XRP Ledger tiene un mecanismo para ajustar los requisitos de reserva. Estos a
- [Objeto AccountRoot][]
- [Votación de Fee](../consensus-protocol/fee-voting.md)
- [Pseudo-transacción SetFee][]
- [Tutorial: Calcular y mostrar los requisitos de reserva (Python)](../../tutorials/python/build-apps/build-a-desktop-wallet-in-python.md#3-display-an-account)
- [Tutorial: Calcular y mostrar los requisitos de reserva (Python)](/docs/tutorials/sample-apps/build-a-desktop-wallet-in-python.md#3-display-an-account)
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -63,7 +63,7 @@ Cualquier cuenta puede crear y utilizar Tickets en cualquier tipo de transaccion
- **Conceptos:**
- [Multi-Signing](multi-signing.md)
- **Tutoriales:**
- [Usar Tickets](../../tutorials/how-tos/manage-account-settings/use-tickets.md)
- [Usar Tickets](/docs/tutorials/best-practices/transaction-sending/use-tickets.md)
- **Referencias:**
- [Transacción TicketCreate][]
- [Campos comunes de una transacción](../../references/protocol/transactions/common-fields.md)

View File

@@ -27,7 +27,7 @@ Necesitas confiar en el servidor que utilizas. Si te conectas a un servidor mali
* Podría selectivamente mostrar u ocultar los caminos (o paths) de pago y las foertas de intercambio de divisas para garantizar su propio beneficio mientras no te ofrece la mejor oferta.
* Si le enviaste la clave secreta de tu dirección, esto podría generar transacciones arbitrarias en tu nombre e incluso transferir o destruir todo el dinero que la dirección posee.
Adicionalmente, ejecutar tu propio servidor te da [acceso de administrador](../../tutorials/http-websocket-apis/build-apps/get-started.md#admin-access), lo que te permite ejecutar comandos exclusivos de administrador y de carga intensa. Si utilizas un servidor compartido, debes preocuparte por los otros usuarios del mismo servidor compitiendo contra ti por el poder de computación del servidor. Muchos de los comandos en el API WebSocket puede poner mucha presión sobre el servidor, por lo que el servidor tiene la opción de reducir sus respuestas cuando lo necesite. Si compartes un servidor con otros, puede que no siempre consigas los mejores resultados posibles.
Adicionalmente, ejecutar tu propio servidor te da [acceso de administrador](/docs/tutorials/get-started/get-started-http-websocket-apis.md#admin-access), lo que te permite ejecutar comandos exclusivos de administrador y de carga intensa. Si utilizas un servidor compartido, debes preocuparte por los otros usuarios del mismo servidor compitiendo contra ti por el poder de computación del servidor. Muchos de los comandos en el API WebSocket puede poner mucha presión sobre el servidor, por lo que el servidor tiene la opción de reducir sus respuestas cuando lo necesite. Si compartes un servidor con otros, puede que no siempre consigas los mejores resultados posibles.
Finalmente, si ejecutas un servidor de validación, puedes utilizar un servidor común como proxy a la red pública mientras mantienes tu servidor de vaalidación en una red privada la cual es solo accesible desde el mundo exterior desde tu servidor común. Esto hace más difícil comprometer la integridad de tu servidor de validación.

View File

@@ -12,7 +12,7 @@ El software del servidor `rippled` puede ejecutarse en varios modos dependiendo
- [**Modo P2P**](#modo-p2p) - Este es el modo principal del servidor: sigue la red peer-to-peer, procesa transacciones, y mantiene cierta cantidad de [histórico del ledger](ledger-history.md). Este modo se puede configurar para alguno o todos los siguientes roles:
- [**Validador**](#validadores) - Ayuda a asegurar la red participando en el consenso.
- [**Servidor API**](#servidores-api) - Proporciona [acceso API](../../tutorials/http-websocket-apis/build-apps/get-started.md) para leer datos del ledger compartido, enviar transacciones, y mirar la actividad en el ledger. Opcionalmente, puede ser un [**servidor full history**](#servidores-full-history), el cual guarda un registro completo de transacciones y el histórico del ledger.
- [**Servidor API**](#servidores-api) - Proporciona [acceso API](/docs/tutorials/get-started/get-started-http-websocket-apis.md) para leer datos del ledger compartido, enviar transacciones, y mirar la actividad en el ledger. Opcionalmente, puede ser un [**servidor full history**](#servidores-full-history), el cual guarda un registro completo de transacciones y el histórico del ledger.
- [**Servidor hub**](#hubs-públicos) - Transmite mensajes entre muchos otros miembros de la red peer-to-peer.
- [**Modo solitario**](#modo-solitario) - Un modo offline para pruebas. No se conecta a la red peer-to-peer ni usa consenso.

View File

@@ -50,11 +50,11 @@ Para más información sobre Cheques en el XRP Ledger, ver:
- [CheckCash][]
- [CheckCancel][]
- [Tutoriales de cheques](../../tutorials/how-tos/use-specialized-payment-types/use-checks/index.md)
- [Enviar un cheque](../../tutorials/how-tos/use-specialized-payment-types/use-checks/send-a-check.md)
- [Buscar cheques](../../tutorials/how-tos/use-specialized-payment-types/use-checks/look-up-checks.md)
- [Canjear un cheque por la cantidad exacta](../../tutorials/how-tos/use-specialized-payment-types/use-checks/cash-a-check-for-an-exact-amount.md)
- [Canjear un cheque por una cantidad flexible](../../tutorials/how-tos/use-specialized-payment-types/use-checks/cash-a-check-for-a-flexible-amount.md)
- [Cancelar un cheque](../../tutorials/how-tos/use-specialized-payment-types/use-checks/cancel-a-check.md)
- [Enviar un cheque](/docs/tutorials/payments/send-a-check.md)
- [Buscar cheques](/docs/tutorials/ /how-tos/use-specialized-payment-types/use-checks/look-up-checks.md)
- [Canjear un cheque por la cantidad exacta](/docs/tutorials/payments/cash-a-check-for-an-exact-amount.md)
- [Canjear un cheque por una cantidad flexible](/docs/tutorials/payments/cash-a-check-for-a-flexible-amount.md)
- [Cancelar un cheque](/docs/tutorials/payments/cancel-a-check.md)
- [Enmienda Cheques][]
Para más información sobre funciones relacionadas, ver:

View File

@@ -39,8 +39,8 @@ La base de cualquier sistema financiero es la transferencia de valor. El método
## Ver también
- **Tutoriales:**
- [Enviar XRP (Tutorial interactivo)](../../tutorials/how-tos/send-xrp.md)
- [Monitorizar pagos entrantes con WebSocket](../../tutorials/http-websocket-apis/build-apps/monitor-incoming-payments-with-websocket.md)
- [Enviar XRP (Tutorial interactivo)](/docs/tutorials/payments/send-xrp.md)
- [Monitorizar pagos entrantes con WebSocket](/docs/tutorials/advanced-developer-topics/client-library-development/monitor-incoming-payments-with-websocket.md)
- **Referencias:**
- [Transacción Payment][]
- [Resultados de Transaction](../../references/protocol/transactions/transaction-results/index.md)

View File

@@ -128,7 +128,7 @@ Utilizar [el campo `delivered_amount`](#the-delivered_amount-field) al procesar
- [Transacciones](../transactions/index.md)
- **Tutoriales:**
- [Buscar resultados de transacciones](../transactions/finality-of-results/look-up-transaction-results.md)
- [Monitorear pagos recibidos con WebSocket](../../tutorials/http-websocket-apis/build-apps/monitor-incoming-payments-with-websocket.md)
- [Monitorear pagos recibidos con WebSocket](/docs/tutorials/advanced-developer-topics/client-library-development/monitor-incoming-payments-with-websocket.md)
- [Usar tipos de pagos especializados](../../tutorials/how-tos/use-specialized-payment-types/index.md)
- [Listar XRP en un Exchange](../../use-cases/defi/list-xrp-as-an-exchange.md)
- **Referencias:**

View File

@@ -6,4 +6,4 @@ Checkを換金するための前提条件は、正確な金額を換金する場
- 発行済み通貨用のCheckの場合は、ご自身受取人にイシュアーに対するトラストラインがある必要があります。このトラストライン上のご自身の限度額は、受け取る金額を追加するための残高より十分高くなければなりません。
- トラストラインと限度額について詳しくは、[トークン](../concepts/tokens/index.md)および[トラストラインと発行](../concepts/tokens/fungible-tokens/index.md)をご覧ください。
- [トランザクションに安全に署名できる手段](../concepts/transactions/secure-signing.md)。
- XRP Ledgerに接続できる[クライアントライブラリ](../references/client-libraries.md)か、それとも[HTTPライブラリ、WebSocketライブラリなど](../tutorials/http-websocket-apis/build-apps/get-started.md)。
- XRP Ledgerに接続できる[クライアントライブラリ](../references/client-libraries.md)か、それとも[HTTPライブラリ、WebSocketライブラリなど](../tutorials/http-websocket-apis/get-started.md)。

View File

@@ -2,7 +2,7 @@
`rippled`ログメッセージの詳細は、[ログメッセージについて](../infrastructure/troubleshooting/understanding-log-messages.md)をご覧ください。
`rippled`が残りのネットワークと同期されたら、ストック`rippled`サーバが完全に機能するようになります。このサーバを、ローカル署名やXRP LedgerへのAPIアクセスに使用できます。`rippled`サーバがネットワークと同期されているかどうかを判別するには、[`rippled`サーバの状況](../references/http-websocket-apis/api-conventions/rippled-server-states.md)を使用します。[`rippled`のコマンドラインインターフェイス](../tutorials/http-websocket-apis/build-apps/get-started.md#コマンドライン)を使用すれば、これを迅速にテストできます。
`rippled`が残りのネットワークと同期されたら、ストック`rippled`サーバが完全に機能するようになります。このサーバを、ローカル署名やXRP LedgerへのAPIアクセスに使用できます。`rippled`サーバがネットワークと同期されているかどうかを判別するには、[`rippled`サーバの状況](../references/http-websocket-apis/api-conventions/rippled-server-states.md)を使用します。[`rippled`のコマンドラインインターフェイス](../tutorials/http-websocket-apis/get-started.md#コマンドライン)を使用すれば、これを迅速にテストできます。
```sh
rippled server_info

View File

@@ -54,7 +54,7 @@ XRP Ledgerでは、スパムや悪意のある使用によって、共有グロ
アカウントの所有者準備金を決定するには、増分準備金にアカウントが所有するオブジェクトの数を掛けます。アカウントが所有しているオブジェクトの数を調べるには、[account_infoメソッド][]を呼び出し、`account_data.OwnerCount`を取得します。
アドレスの必要となる合計準備金を計算するには、`OwnerCount``reserve_inc_xrp`を掛け、次に`reserve_base_xrp`を加えます。[この計算をPythonで行うデモ](../../tutorials/python/build-apps/build-a-desktop-wallet-in-python.md#codeblock-17)があります。
アドレスの必要となる合計準備金を計算するには、`OwnerCount``reserve_inc_xrp`を掛け、次に`reserve_base_xrp`を加えます。[この計算をPythonで行うデモ](/docs/tutorials/sample-apps/build-a-desktop-wallet-in-python.md#codeblock-17)があります。
## 必要準備金を下回る
@@ -76,6 +76,6 @@ XRP Ledgerには、準備金要件を調整する仕組みがあります。こ
- [AccountRootオブジェクト][]
- [手数料の投票](../consensus-protocol/fee-voting.md)
- [SetFee疑似トランザクション][]疑似トランザクション
- [チュートリアル: 必要準備金の計算と表示Python](../../tutorials/python/build-apps/build-a-desktop-wallet-in-python.md#3-display-an-account)
- [チュートリアル: 必要準備金の計算と表示Python](/docs/tutorials/sample-apps/build-a-desktop-wallet-in-python.md#3-display-an-account)
{% raw-partial file="/@l10n/ja/docs/_snippets/common-links.md" /%}

View File

@@ -27,7 +27,7 @@ XRP Ledgerを動かすサーバソフトウェアは、主に2種類あります
* 選択的に支払いパスや通貨交換のオファーを表示または非表示にすることができ、最良の取引を提供せず、彼ら自身の利益を確保する可能性があります。
* もし、アドレスの秘密鍵を送信してしまった場合、サーバの管理者はあなたに代わって任意のトランザクションを実行し、アドレスが保有するすべての資金を転送または破棄する可能性があります。
さらに、独自のサーバを運営することで、[管理者アクセス権限](../../tutorials/http-websocket-apis/build-apps/get-started.md#管理者アクセス権限)が与えられ、重要な管理者専用コマンドや負荷の高いコマンドを実行することができます。共有サーバを使用する場合、同じサーバの他のユーザとサーバの計算能力を共有することを考慮しなければいけません。WebSocket APIのコマンドの多くはサーバに大きな負担をかけるので、サーバには必要なときにレスポンスを縮小するオプションがあります。サーバを他人と共有する場合、常に最良の結果を得られるとは限りません。
さらに、独自のサーバを運営することで、[管理者アクセス権限](/docs/tutorials/get-started/get-started-http-websocket-apis.md#管理者アクセス権限)が与えられ、重要な管理者専用コマンドや負荷の高いコマンドを実行することができます。共有サーバを使用する場合、同じサーバの他のユーザとサーバの計算能力を共有することを考慮しなければいけません。WebSocket APIのコマンドの多くはサーバに大きな負担をかけるので、サーバには必要なときにレスポンスを縮小するオプションがあります。サーバを他人と共有する場合、常に最良の結果を得られるとは限りません。
最後に、バリデーションサーバを運用する場合、パブリックネットワークへのプロキシとしてストックサーバを使用し、バリデーションサーバをプライベートネットワークに置いて、ストックサーバを通してのみ外部にアクセスできるようにすることができます。これにより、バリデーションサーバに侵入することがより困難になります。

View File

@@ -12,7 +12,7 @@ labels:
- [**P2Pモード**](#p2pモード) - ピアツーピアネットワークをフォローし、トランザクションを処理し、ある程度の[レジャー履歴](ledger-history.md)を維持します。このモードは、以下の役割のいずれか、またはすべてを行うように設定することができます。
- [**バリデータ**](#バリデータ) - コンセンサスに参加することで、ネットワークの安全確保に貢献します。
- [**APIサーバ**](#apiサーバ) - 共有レジャーからデータを読み込んだり、トランザクションを送信したり、レジャーのアクティビティを監視するための[APIアクセス](../../tutorials/http-websocket-apis/build-apps/get-started.md)を提供します。オプションとして、トランザクションやレジャーの履歴を完全に記録する [**全履歴サーバ**](#全履歴サーバ) とすることができます。
- [**APIサーバ**](#apiサーバ) - 共有レジャーからデータを読み込んだり、トランザクションを送信したり、レジャーのアクティビティを監視するための[APIアクセス](/docs/tutorials/get-started/get-started-http-websocket-apis.md)を提供します。オプションとして、トランザクションやレジャーの履歴を完全に記録する [**全履歴サーバ**](#全履歴サーバ) とすることができます。
- [**ハブサーバ**](#公開ハブ) - ピアツーピアネットワークの他の多くのメンバー間のメッセージを中継します。
- [**レポートモード**](#レポートモード) - リレーショナルデータベースからのAPIリクエストに対応するための専用モードです。ピアツーピアネットワークには参加しないため、P2Pモードサーバを実行し、信頼できるgRPC接続を使用してレポートモードサーバに接続する必要があります。 {% badge href="https://github.com/XRPLF/rippled/releases/tag/1.7.0" %}新規: rippled 1.7.0{% /badge %}
- [**スタンドアロンモード**](#スタンドアロンモード) - テスト用のオフラインモードです。ピアツーピアネットワークに接続せず、コンセンサスも使用しません。

View File

@@ -97,12 +97,12 @@ XRP LedgerのChecksの詳細は、以下をご覧ください。
- [CheckCreate][]
- [CheckCash][]
- [CheckCancel][]
- [Checksのチュートリアル](../../tutorials/how-tos/use-specialized-payment-types/use-checks/index.md)
- [Checkの送信](../../tutorials/how-tos/use-specialized-payment-types/use-checks/send-a-check.md)
- [Checksの検索](../../tutorials/how-tos/use-specialized-payment-types/use-checks/look-up-checks.md)
- [Checkの指定された金額での換金](../../tutorials/how-tos/use-specialized-payment-types/use-checks/cash-a-check-for-an-exact-amount.md)
- [Checkの変動金額での換金](../../tutorials/how-tos/use-specialized-payment-types/use-checks/cash-a-check-for-a-flexible-amount.md)
- [Checkの取消し](../../tutorials/how-tos/use-specialized-payment-types/use-checks/cancel-a-check.md)
- Checksのチュートリアル
- [Checkの送信](/docs/tutorials/payments/send-a-check.md)
- [Checksの検索](/docs/tutorials/payments/look-up-checks.md)
- [Checkの指定された金額での換金](/docs/tutorials/payments/cash-a-check-for-an-exact-amount.md)
- [Checkの変動金額での換金](/docs/tutorials/payments/cash-a-check-for-a-flexible-amount.md)
- [Checkの取消し](/docs/tutorials/payments/cancel-a-check.md)
- [Checks Amendment][]
関連機能の詳細については、以下をご覧ください。

View File

@@ -207,7 +207,7 @@ XRP Ledgerは、トランザクションオブジェクトが送信元アドレ
- [安全な署名の設定](secure-signing.md)
- [XRPの送金](../../tutorials/how-tos/send-xrp.md)
- [トランザクションの結果の確認](finality-of-results/look-up-transaction-results.md)
- [WebSocketを使用した着信ペイメントの監視](../../tutorials/http-websocket-apis/build-apps/monitor-incoming-payments-with-websocket.md)
- [WebSocketを使用した着信ペイメントの監視](/docs/tutorials/advanced-developer-topics/client-library-development/monitor-incoming-payments-with-websocket.md)
- [トランザクションの取り消しまたはスキップ](finality-of-results/canceling-a-transaction.md)
- [信頼できるトランザクションの送信](reliable-transaction-submission.md)
- **リファレンス:**

View File

@@ -48,7 +48,7 @@ labels:
[構成ファイルの例](https://github.com/XRPLF/rippled/blob/8429dd67e60ba360da591bfa905b58a35638fda1/cfg/rippled-example.cfg#L1050-L1073)では、ローカルループバックネットワーク上127.0.0.1のポート5005でJSON-RPCHTTP、ポート6006でWebSocketWSの接続をリッスンし、接続されるすべてのクライアントを管理者として扱っています。
{% admonition type="warning" name="注意" %}署名に[コマンドラインAPI](../../references/http-websocket-apis/api-conventions/request-formatting.md#コマンドライン形式)を使用する場合は、コマンドラインでないクライアントで[Websocket APIやJSON-RPC APIを使用](../../tutorials/http-websocket-apis/build-apps/get-started.md)する場合よりもセキュリティが弱くなります。コマンドライン構文を使用すると、秘密鍵がシステムのプロセスリストで他のユーザに見える可能性があり、シェル履歴にプレーンテキスト形式でキーが保存される可能性があります。{% /admonition %}
{% admonition type="warning" name="注意" %}署名に[コマンドラインAPI](../../references/http-websocket-apis/api-conventions/request-formatting.md#コマンドライン形式)を使用する場合は、コマンドラインでないクライアントで[Websocket APIやJSON-RPC APIを使用](/docs/tutorials/get-started/get-started-http-websocket-apis.md)する場合よりもセキュリティが弱くなります。コマンドライン構文を使用すると、秘密鍵がシステムのプロセスリストで他のユーザに見える可能性があり、シェル履歴にプレーンテキスト形式でキーが保存される可能性があります。{% /admonition %}
3. サーバの使用中は、稼働状態と最新状態を維持して、ネットワークと同期されるようにしておく必要があります。

View File

@@ -9,7 +9,7 @@ labels:
---
# Amendment投票機能の設定
バリデータとして設定されたサーバは、[featureメソッド][]を使ってXRP Ledgerプロトコルの[Amendment](../../concepts/networks-and-servers/amendments.md)に投票することができます。(この方法には[管理者アクセス](../../tutorials/http-websocket-apis/build-apps/get-started.md#管理者アクセス権限)が必要です).
バリデータとして設定されたサーバは、[featureメソッド][]を使ってXRP Ledgerプロトコルの[Amendment](../../concepts/networks-and-servers/amendments.md)に投票することができます。(この方法には[管理者アクセス](/docs/tutorials/get-started/get-started-http-websocket-apis.md#管理者アクセス権限)が必要です).
例えば、「SHAMapV2」Amendmentに反対票を投じるには、以下のコマンドを実行します。

View File

@@ -49,7 +49,7 @@ gRPCを有効にするには、次の前提条件を満たす必要がありま
- [XRP Ledgerの概要](/about/)
- [`rippled`サーバのモード](../../concepts/networks-and-servers/rippled-server-modes.md)
- **チュートリアル:**
- [HTTP / WebSocketAPIを使ってみる](../../tutorials/http-websocket-apis/build-apps/get-started.md)
- [HTTP / WebSocketAPIを使ってみる](/docs/tutorials/get-started/get-started-http-websocket-apis.md)
- [信頼できるトランザクションの送信](../../concepts/transactions/reliable-transaction-submission.md)
- [rippledサーバの管理](../installation/install-rippled-on-ubuntu.md)
- **リファレンス:**

View File

@@ -57,7 +57,7 @@ labels:
2. サーバに対してオンライン削除を指示する[can_deleteメソッド][]の実行をテストします。
このコマンドの実行には[`rippled`コマンドラインインターフェイス](../../../tutorials/http-websocket-apis/build-apps/get-started.md#コマンドライン)を使用できます。例:
このコマンドの実行には[`rippled`コマンドラインインターフェイス](/docs/tutorials/get-started/get-started-http-websocket-apis.md#コマンドライン)を使用できます。例:
```
$ rippled --conf=/etc/opt/ripple/rippled.cfg can_delete now

View File

@@ -11,7 +11,7 @@ labels:
{% badge href="https://github.com/XRPLF/rippled/releases/tag/1.1.0" %}新規: rippled 1.1.0{% /badge %}デフォルトでは、`rippled`の署名メソッドは管理者接続に限定されています。v1.1.0以前のバージョンの`rippled`のように、署名メソッドをパブリックAPIメソッドとして使用できるようにするには、構成を変更することで、これを使用できるようにします。
これにより、サーバが「パブリック」[JSON-RPC接続およびWebSocket接続](../../tutorials/http-websocket-apis/build-apps/get-started.md)を受け入れる場合は、これらのパブリック接続で以下のメソッドが使用できるようになります。
これにより、サーバが「パブリック」[JSON-RPC接続およびWebSocket接続](/docs/tutorials/get-started/get-started-http-websocket-apis.md)を受け入れる場合は、これらのパブリック接続で以下のメソッドが使用できるようになります。
- [sign][signメソッド]
- [sign_for][sign_forメソッド]

View File

@@ -80,7 +80,7 @@ labels:
- **チュートリアル:**
- [rippledの構成](../configuration/index.md)
- [rippledのトラブルシューティング](../troubleshooting/index.md)
- [rippled APIの使用開始](../../tutorials/http-websocket-apis/build-apps/get-started.md)
- [rippled APIの使用開始](/docs/tutorials/get-started/get-started-http-websocket-apis.md)
- **リファレンス:**
- [rippled APIリファレンス](../../references/http-websocket-apis/index.md)
- [`rippled`コマンドラインの使用](../commandline-usage.md)

View File

@@ -182,7 +182,7 @@ Terminating thread doJob:AcquisitionDone: unhandled
11. `rippled`サービスが正常に起動したかどうかを確認します。
[コマンドラインインターフェイス](../../tutorials/http-websocket-apis/build-apps/get-started.md#コマンドライン)を使用してサーバの状況を確認できますサーバがJSON-RPCリクエストを受け入れないように設定している場合を除く。次に例を示します。
[コマンドラインインターフェイス](/docs/tutorials/get-started/get-started-http-websocket-apis.md#コマンドライン)を使用してサーバの状況を確認できますサーバがJSON-RPCリクエストを受け入れないように設定している場合を除く。次に例を示します。
```
/opt/ripple/bin/rippled server_info

View File

@@ -12,10 +12,10 @@ seo:
| 言語 | ライブラリ名 | Get Started | APIリファレンス | ソースコード |
|---------------------------------|------------------------|--------------|---------------|-------------|
| **Python** | `xrpl-py` | [Pythonを使ってみよう](../tutorials/python/build-apps/get-started.md) | [API リファレンス](https://xrpl-py.readthedocs.io/) | [リポジトリ](https://github.com/XRPLF/xrpl-py) |
| **JavaScript** / **TypeScript** | `xrpl.js` | [JavaScriptを使ってみよう](../tutorials/javascript/build-apps/get-started.md) | [API リファレンス](https://js.xrpl.org/) | [リポジトリ](https://github.com/XRPLF/xrpl.js) |
| **Python** | `xrpl-py` | [Pythonを使ってみよう](/docs/tutorials/get-started/get-started-python.md) | [API リファレンス](https://xrpl-py.readthedocs.io/) | [リポジトリ](https://github.com/XRPLF/xrpl-py) |
| **JavaScript** / **TypeScript** | `xrpl.js` | [JavaScriptを使ってみよう](/docs/tutorials/get-started/get-started-javascript.md) | [API リファレンス](https://js.xrpl.org/) | [リポジトリ](https://github.com/XRPLF/xrpl.js) |
| **JavaScript** / **TypeScript** | `xrpl-client` | [触ってみる](https://jsfiddle.net/WietseWind/35az6p1b/) | [NPM リファレンス](https://www.npmjs.com/package/xrpl-client) | [リポジトリ](https://github.com/XRPL-Labs/xrpl-client) |
| **JavaScript** / **TypeScript** | `xrpl-accountlib` | [触ってみる](https://jsfiddle.net/WietseWind/gkefpnu0/) | [NPM リファレンス](https://www.npmjs.com/package/xrpl-accountlib) | [リポジトリ](https://github.com/WietseWind/xrpl-accountlib) |
| **C++** | `rippled` 署名ライブラリ | [署名ライブラリを使ってみよう](https://github.com/XRPLF/rippled/tree/develop/Builds/linux#signing-library) | | ([`rippled`](https://github.com/XRPLF/rippled/))の一部 |
| **Java** | `xrpl4j` | [Javaを使ってみよう](../tutorials/java/build-apps/get-started.md) | [API リファレンス](https://javadoc.io/doc/org.xrpl/) | [リポジトリ](https://github.com/XRPLF/xrpl4j) |
| **PHP** | `XRPL_PHP` | [PHPを使ってみよう](../tutorials/php/build-apps/get-started.md) | [XRPL_PHP ドキュメント](https://alexanderbuzz.github.io/xrpl-php-docs/) | [リポジトリ](https://github.com/AlexanderBuzz/xrpl-php) |
| **Java** | `xrpl4j` | [Javaを使ってみよう](/docs/tutorials/get-started/get-started-java.md) | [API リファレンス](https://javadoc.io/doc/org.xrpl/) | [リポジトリ](https://github.com/XRPLF/xrpl4j) |
| **PHP** | `XRPL_PHP` | [PHPを使ってみよう](/docs/tutorials/get-started/get-started-php.md) | [XRPL_PHP ドキュメント](https://alexanderbuzz.github.io/xrpl-php-docs/) | [リポジトリ](https://github.com/AlexanderBuzz/xrpl-php) |

View File

@@ -14,6 +14,6 @@ nav_omit: true
## Alternatives
アカウント残高や取引履歴のリクエストなど、ほとんどの一般的な操作では、[WebSocket接続](../tutorials/http-websocket-apis/get-started.md#websocket-api)または[JSON-RPCHTTP POST](../tutorials/http-websocket-apis/build-apps/get-started.md#json-rpc)を使用して、セルフホストまたは[公開XRP Ledgerサーバ](../tutorials/public-servers.md)にリクエストすることとができます。
アカウント残高や取引履歴のリクエストなど、ほとんどの一般的な操作では、[WebSocket接続](/docs/tutorials/get-started/get-started-http-websocket-apis.md#websocket-api)または[JSON-RPCHTTP POST](/docs/tutorials/get-started/get-started-http-websocket-apis.md#json-rpc)を使用して、セルフホストまたは[公開XRP Ledgerサーバ](../tutorials/public-servers.md)にリクエストすることとができます。
詳細については、[HTTP / WebSocket APIsの使用を開始する](../tutorials/http-websocket-apis/build-apps/get-started.md)ページをご覧ください。
詳細については、[HTTP / WebSocket APIsの使用を開始する](/docs/tutorials/get-started/get-started-http-websocket-apis.md)ページをご覧ください。

View File

@@ -10,7 +10,7 @@ labels:
`rippled`サーバと直接通信する際には管理APIメソッドを使用します。管理メソッドは、信頼できるサーバ運用担当者のみを対象としています。管理メソッドには、サーバの管理、監視、デバッグのためのコマンドが含まれています。
管理コマンドを使用できるのは、管理者として、`rippled.cfg`ファイルに指定されているホストとポートで`rippled`サーバに接続している場合に限られます。デフォルトでは、コマンドラインクライアントが管理接続を使用します。`rippled`への接続についての詳細は、[rippled API入門](../../../tutorials/http-websocket-apis/build-apps/get-started.md)をご覧ください。
管理コマンドを使用できるのは、管理者として、`rippled.cfg`ファイルに指定されているホストとポートで`rippled`サーバに接続している場合に限られます。デフォルトでは、コマンドラインクライアントが管理接続を使用します。`rippled`への接続についての詳細は、[rippled API入門](/docs/tutorials/get-started/get-started-http-websocket-apis.md)をご覧ください。
## [キー生成メソッド](key-generation-methods/index.md)

View File

@@ -10,7 +10,7 @@ labels:
`rippled`サーバはAPIクライアントが公開APIにリクエストできるレートを制限できます。レート制限はクライアントのIPアドレスに基づいて行われるため、[ネットワークアドレス変換](https://ja.wikipedia.org/wiki/ネットワークアドレス変換)の背後にいるクライアントは公開IPアドレスに基づく制限を共有します。
{% admonition type="success" name="ヒント" %}レート制限は、クライアントが[管理者](../../../tutorials/http-websocket-apis/build-apps/get-started.md#管理者アクセス権限)として接続されているときには適用されません{% /admonition %}
{% admonition type="success" name="ヒント" %}レート制限は、クライアントが[管理者](/docs/tutorials/get-started/get-started-http-websocket-apis.md#管理者アクセス権限)として接続されているときには適用されません{% /admonition %}
クライアントがレート制限に近づいている場合、サーバは[APIレスポンス](response-formatting.md)のトップレベルにフィールド`"warning" "load"`というフィールドを[APIレスポンス](response-formatting.md)のトップレベルに追加します。この警告はすべてのレスポンスに追加されるわけではありませんが、サーバはクライアントを切断する前に何度かこのような警告を送ることがあります。
@@ -58,7 +58,7 @@ Server is overloaded
- [`rippled`サーバ](../../../concepts/networks-and-servers/index.md)
- [ソフトウェアエコシステム](../../../introduction/software-ecosystem.md)
- **チュートリアル:**
- [XRP Ledger APIの使用開始](../../../tutorials/http-websocket-apis/build-apps/get-started.md)
- [XRP Ledger APIの使用開始](/docs/tutorials/get-started/get-started-http-websocket-apis.md)
- [rippledのトラブルシューティング](../../../infrastructure/troubleshooting/index.md)
- **リファレンス:**
- [rippled APIリファレンス](../index.md)

View File

@@ -130,7 +130,7 @@ HTTP Status: 200 OK
この警告は、XRP Ledgerプロトコルの1つ以上の[Amendment](../../../concepts/networks-and-servers/amendments.md)が有効になる予定であるが、現在のサーバにはそれらのAmendmentの実装がないことを示しています。これらのAmendmentが有効になると、現在のサーバは[Amendmentブロック](../../../concepts/networks-and-servers/amendments.md#amendment-blocked-servers)されるため、できるだけ早く[最新の`rippled`バージョンにアップグレード](../../../infrastructure/installation/index.md)する必要があります。
サーバは、この警告を送信するのは、クライアントが[管理者として接続している](../../../tutorials/http-websocket-apis/build-apps/get-started.md#admin-access)場合のみです。
サーバは、この警告を送信するのは、クライアントが[管理者として接続している](/docs/tutorials/get-started/get-started-http-websocket-apis.md#admin-access)場合のみです。
この警告には、以下のフィールドを含む`details`フィールドが含まれます。
@@ -189,7 +189,7 @@ HTTP Status: 200 OK
- [Amendment](../../../concepts/networks-and-servers/amendments.md)
- [既知のAmendment](/resources/known-amendments.md)
- **チュートリアル:**
- [XRP LedgerのAPIを触ってみよう](../../../tutorials/http-websocket-apis/build-apps/get-started.md)
- [XRP LedgerのAPIを触ってみよう](/docs/tutorials/get-started/get-started-http-websocket-apis.md)
- [`rippled`のインストールと更新](../../../infrastructure/installation/index.md)
- **リファレンス:**
- [featureメソッド][]

View File

@@ -104,16 +104,142 @@ rippled tx C53ECF838647FA5A4C780377025FEC7999AB4182590510CA461444B207AB74A9 fals
{% tabs %}
{% tab label="WebSocket" %}
{% code-snippet file="/_api-examples/tx/ws-response.json" language="json" /%}
{% tab label="WebSocket (Hash)" %}
{% code-snippet file="/_api-examples/tx/ws-response-hash.json" language="json" /%}
{% /tab %}
{% tab label="JSON-RPC" %}
{% code-snippet file="/_api-examples/tx/jsonrpc-response.json" language="json" prefix="200 OK\n\n" /%}
{% tab label="WebSocket (CTID)" %}
{% code-snippet file="/_api-examples/tx/ws-response-ctid.json" language="json" /%}
{% /tab %}
{% tab label="JSON-RPC (Hash)" %}
{% code-snippet file="/_api-examples/tx/jsonrpc-response-hash.json" language="json" /%}
{% /tab %}
{% tab label="JSON-RPC (CTID)" %}
{% code-snippet file="/_api-examples/tx/jsonrpc-response-ctid.json" language="json" /%}
{% /tab %}
{% tab label="Commandline" %}
{% code-snippet file="/_api-examples/tx/jsonrpc-response.json" language="json" prefix="Loading: \"/etc/opt/ripple/rippled.cfg\"\n2025-Dec-19 03:16:00.638871262 UTC HTTPClient:NFO Connecting to 127.0.0.1:5005\n\n" /%}
```json
{
"result" : {
"Account" : "rhhh49pFH96roGyuC4E5P4CHaNjS1k8gzM",
"Fee" : "12",
"Flags" : 0,
"LastLedgerSequence" : 56865248,
"OfferSequence" : 5037708,
"Sequence" : 5037710,
"SigningPubKey" : "03B51A3EDF70E4098DA7FB053A01C5A6A0A163A30ED1445F14F87C7C3295FCB3BE",
"TakerGets" : "15000000000",
"TakerPays" : {
"currency" : "CNY",
"issuer" : "rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y",
"value" : "20160.75"
},
"TransactionType" : "OfferCreate",
"TxnSignature" : "3045022100A5023A0E64923616FCDB6D664F569644C7C9D1895772F986CD6B981B515B02A00220530C973E9A8395BC6FE2484948D2751F6B030FC7FB8575D1BFB406368AD554D9",
"date" : 648248020,
"hash" : "C53ECF838647FA5A4C780377025FEC7999AB4182590510CA461444B207AB74A9",
"inLedger" : 56865245,
"ledger_index" : 56865245,
"meta" : {
"AffectedNodes" : [
{
"ModifiedNode" : {
"FinalFields" : {
"ExchangeRate" : "4F04C66806CF7400",
"Flags" : 0,
"RootIndex" : "02BAAC1E67C1CE0E96F0FA2E8061020536CEDD043FEB0FF54F04C66806CF7400",
"TakerGetsCurrency" : "0000000000000000000000000000000000000000",
"TakerGetsIssuer" : "0000000000000000000000000000000000000000",
"TakerPaysCurrency" : "000000000000000000000000434E590000000000",
"TakerPaysIssuer" : "CED6E99370D5C00EF4EBF72567DA99F5661BFB3A"
},
"LedgerEntryType" : "DirectoryNode",
"LedgerIndex" : "02BAAC1E67C1CE0E96F0FA2E8061020536CEDD043FEB0FF54F04C66806CF7400"
}
},
{
"ModifiedNode" : {
"FinalFields" : {
"Account" : "rhhh49pFH96roGyuC4E5P4CHaNjS1k8gzM",
"Balance" : "10404767991",
"Flags" : 0,
"OwnerCount" : 3,
"Sequence" : 5037711
},
"LedgerEntryType" : "AccountRoot",
"LedgerIndex" : "1DECD9844E95FFBA273F1B94BA0BF2564DDF69F2804497A6D7837B52050174A2",
"PreviousFields" : {
"Balance" : "10404768003",
"Sequence" : 5037710
},
"PreviousTxnID" : "4DC47B246B5EB9CCE92ABA8C482479E3BF1F946CABBEF74CA4DE36521D5F9008",
"PreviousTxnLgrSeq" : 56865244
}
},
{
"DeletedNode" : {
"FinalFields" : {
"Account" : "rhhh49pFH96roGyuC4E5P4CHaNjS1k8gzM",
"BookDirectory" : "02BAAC1E67C1CE0E96F0FA2E8061020536CEDD043FEB0FF54F04C66806CF7400",
"BookNode" : "0000000000000000",
"Flags" : 0,
"OwnerNode" : "0000000000000000",
"PreviousTxnID" : "8F5FF57B404827F12BDA7561876A13C3E3B3095CBF75334DBFB5F227391A660C",
"PreviousTxnLgrSeq" : 56865244,
"Sequence" : 5037708,
"TakerGets" : "15000000000",
"TakerPays" : {
"currency" : "CNY",
"issuer" : "rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y",
"value" : "20160.75"
}
},
"LedgerEntryType" : "Offer",
"LedgerIndex" : "26AAE6CA8D29E28A47C92ADF22D5D96A0216F0551E16936856DDC8CB1AAEE93B"
}
},
{
"ModifiedNode" : {
"FinalFields" : {
"Flags" : 0,
"IndexNext" : "0000000000000000",
"IndexPrevious" : "0000000000000000",
"Owner" : "rhhh49pFH96roGyuC4E5P4CHaNjS1k8gzM",
"RootIndex" : "47FAF5D102D8CE655574F440CDB97AC67C5A11068BB3759E87C2B9745EE94548"
},
"LedgerEntryType" : "DirectoryNode",
"LedgerIndex" : "47FAF5D102D8CE655574F440CDB97AC67C5A11068BB3759E87C2B9745EE94548"
}
},
{
"CreatedNode" : {
"LedgerEntryType" : "Offer",
"LedgerIndex" : "8BAEE3C7DE04A568E96007420FA11ABD0BC9AE44D35932BB5640E9C3FB46BC9B",
"NewFields" : {
"Account" : "rhhh49pFH96roGyuC4E5P4CHaNjS1k8gzM",
"BookDirectory" : "02BAAC1E67C1CE0E96F0FA2E8061020536CEDD043FEB0FF54F04C66806CF7400",
"Sequence" : 5037710,
"TakerGets" : "15000000000",
"TakerPays" : {
"currency" : "CNY",
"issuer" : "rKiCet8SdvWxPXnAgYarFUXMh1zCPz432Y",
"value" : "20160.75"
}
}
}
}
],
"TransactionIndex" : 0,
"TransactionResult" : "tesSUCCESS"
},
"status" : "success",
"validated" : true
}
}
```
{% /tab %}
{% /tabs %}

View File

@@ -20,7 +20,7 @@ steps: ['Generate', 'Connect', 'Check Sequence', 'Prepare & Sign', 'Submit', 'Wa
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
<script type="application/javascript" src="/js/tutorials/use-tickets.js"></script>
このページでは、[xrpl.js](https://js.xrpl.org/)ライブラリを使用したJavaScriptのサンプルを提供しています。設定方法は、[JavaScriptを使ってみよう](../../javascript/build-apps/get-started.md)をご覧ください。
このページでは、[xrpl.js](https://js.xrpl.org/)ライブラリを使用したJavaScriptのサンプルを提供しています。設定方法は、[JavaScriptを使ってみよう](/docs/tutorials/get-started/get-started-javascript.md)をご覧ください。
JavaScriptはWebブラウザ上で動作するため、セットアップなしで読み進められ、インタラクティブな手順を利用することができます。

View File

@@ -23,7 +23,7 @@ steps: ['Generate', 'Connect', 'Prepare', 'Sign', 'Submit', 'Wait', 'Check']
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
<script type="application/javascript" src="/js/tutorials/send-xrp.js"></script>
- このページでは、xrpl.jsライブラリーを使用するJavaScriptの例を紹介します。[xrpl.js入門ガイド](../javascript/build-apps/get-started.md)に、xrpl.jsを使用してJavaScriptからXRP Ledgerデータにアクセスする方法の説明があります。
- このページでは、xrpl.jsライブラリーを使用するJavaScriptの例を紹介します。[xrpl.js入門ガイド](/docs/tutorials/get-started/get-started-javascript.md)に、xrpl.jsを使用してJavaScriptからXRP Ledgerデータにアクセスする方法の説明があります。
- XRP Ledgerでトランザクションを送信するには、まずアドレスと秘密鍵、そしていくらかのXRPが必要となります。次のインターフェイスを使用して、XRP Test NetにあるアドレスとTestnet XRPを入手できます。

View File

@@ -0,0 +1,119 @@
---
html: cancel-an-expired-escrow.html
parent: use-escrows.html
seo:
description: 有効期限切れのEscrowを取り消します。
labels:
- Escrow
- スマートコントラクト
---
# 有効期限切れEscrowの取消し
## 1.有効期限切れEscrowの確認
XRP LedgerのEscrowが有効期限切れとなるのは、その`CancelAfter`の時刻が検証済みレジャーの`close_time`よりも前である場合です。Escrowに`CancelAfter`時刻が指定されていない場合は、Escrowが有効期限切れになることはありません。最新の検証済みレジャーの閉鎖時刻は、[ledgerメソッド][]を使用して検索できます。
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/ledger-request-expiration.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/ledger-response-expiration.json" language="json" /%}
{% /tab %}
{% /tabs %}
[account_objectsメソッド][]を使用してEscrowを検索し、`CancelAfter`の時刻と比較できます。
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/account_objects-request-expiration.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/account_objects-response-expiration.json" language="json" /%}
{% /tab %}
{% /tabs %}
## 2.EscrowCancelトランザクションの送信
XRP Ledgerでは、[EscrowCancelトランザクション][]に[署名して送信する](../../../../concepts/transactions/index.md#トランザクションへの署名とトランザクションの送信)ことで、***誰でも***有効期限切れのEscrowを取り消すことができます。トランザクションの`Owner`フィールドを、そのEscrowを作成した`EscrowCreate`トランザクションの`Account`に設定します。`OfferSequence`フィールドを、`EscrowCreate`トランザクションの`Sequence`に設定します。
{% partial file="/@l10n/ja/docs/_snippets/secret-key-warning.md" /%}
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/submit-request-escrowcancel.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/submit-response-escrowcancel.json" language="json" /%}
{% /tab %}
{% /tabs %}
トランザクションの識別用`hash`値をメモしておきます。これにより、検証済みレジャーバージョンに記録されるときにその最終ステータスを確認できます。
## 3.検証の待機
{% partial file="/@l10n/ja/docs/_snippets/wait-for-validation.md" /%}
## 4.最終結果の確認
EscrowCancelトランザクションの識別用ハッシュを指定した[txメソッド][]を使用してトランザクションの最終ステータスを確認します。トランザクションのメタデータで`LedgerEntryType``Escrow`である`DeletedNode`を探します。また、エスクローに預託された支払いの送金元の`ModifiedNode`(タイプが`AccountRoot`)も探します。オブジェクトの`FinalFields`に、`Balance`フィールドのXRP返金額の増分が表示されている必要があります。
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/tx-request-escrowcancel.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/tx-response-escrowcancel.json" language="json" /%}
{% /tab %}
{% /tabs %}
上記の例では、`r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT`がEscrowの送金元であり、`Balance`が99999**8**9990 dropから99999**9**9990 dropに増加していることから、エスクローに預託されていた10,000 XRP dropが返金されたことがわかりますdrop = 0.01XRP
{% admonition type="success" name="ヒント" %}Escrowを実行する[EscrowFinishトランザクション][]で使用する`OfferSequence`が不明な場合は、Escrowの`PreviousTxnID`フィールドのトランザクションの識別用ハッシュを指定した[txメソッド][]を使用して、そのEscrowを作成したトランザクションを検索します。Escrowを終了するときには、そのトランザクションの`Sequence`の値を`OfferSequence`の値として使用します。{% /admonition %}
{% raw-partial file="/@l10n/ja/docs/_snippets/common-links.md" /%}

View File

@@ -0,0 +1,80 @@
---
html: look-up-escrows.html
parent: use-escrows.html
seo:
description: 送金元または送金先のアドレスを使って保留中のEscrowを検索します。
labels:
- Escrow
- スマートコントラクト
---
# Escrowの検索
保留中のEscrowはすべて[Escrowオブジェクト](../../../../concepts/payment-types/escrow.md)としてレジャーに保管されます。
Escrowオブジェクトを検索するには、[account_objectsメソッド][]で[送金元のアドレス](#送金元のアドレスによるescrowの検索)または[送金先のアドレス](#送金先のアドレスによるescrowの検索)を使用して検索します。
## 送金元のアドレスによるEscrowの検索
[account_objectsメソッド][]を使用して、送金元アドレスからEscrowオブジェクトを検索できます。
たとえば、送金元アドレスが`rfztBskAVszuS3s5Kq7zDS74QtHrw893fm`である保留中のEscrowオブジェクトをすべて検索するとします。以下のリクエストの例に従ってこの検索を実行できます。この例では送金元アドレスは`account`の値です。
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/account_objects-request.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンスは以下の例のようになります。このレスポンスには、送金元アドレスまたは送金先アドレスが`rfztBskAVszuS3s5Kq7zDS74QtHrw893fm`である保留中のEscrowオブジェクトがすべて含まれています。送金元アドレスは`Account`の値であり、送金先アドレスは`Destination`の値です。
この例では、2番目と4番目のEscrowオブジェクトが検索条件に一致しています。これは、これらのオブジェクトの`Account`(送金元のアドレス)の値が`rfztBskAVszuS3s5Kq7zDS74QtHrw893fm`に設定されているためです。
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/account_objects-response.json" language="json" /%}
{% /tab %}
{% /tabs %}
## 送金先のアドレスによるEscrowの検索
[account_objectsメソッド][]を使用して、送金先アドレスからEscrowオブジェクトを検索できます。
{% admonition type="info" name="注記" %}送金先のアドレスによる保留中のEscrowオブジェクトの検索は、[fix1523 Amendment][]が2017/11/14に有効化された後に作成されたEscrowについてのみ行うことができます。{% /admonition %}
たとえば、送金先アドレスが`rfztBskAVszuS3s5Kq7zDS74QtHrw893fm`である保留中のEscrowオブジェクトをすべて検索するとします。以下のリクエストの例に従ってこの検索を実行できます。この例では送金先アドレスは`account`の値です。
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/account_objects-request.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンスは以下の例のようになります。レスポンスには送金先アドレスまたは送金元アドレスが`rfztBskAVszuS3s5Kq7zDS74QtHrw893fm`である保留中のEscrowオブジェクトがすべて含まれています。送金先アドレスは`Destination`の値であり、送金元アドレスは`Account`の値です。
この例では、1番目と3番目のEscrowオブジェクトが検索条件に一致しています。これは、これらのオブジェクトの`Destination`(送金先のアドレス)の値が`rfztBskAVszuS3s5Kq7zDS74QtHrw893fm`に設定されているためです。
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/account_objects-response.json" language="json" /%}
{% /tab %}
{% /tabs %}
{% raw-partial file="/@l10n/ja/docs/_snippets/common-links.md" /%}

View File

@@ -0,0 +1,173 @@
---
html: send-a-conditionally-held-escrow.html
parent: use-escrows.html
seo:
description: 満たされた条件に基づいてリリースとなるEscrowを作成します。
labels:
- Escrow
- スマートコントラクト
---
# 条件に基づくEscrowの送信
## 1.条件とフルフィルメントの生成
XRP Ledger EscrowにはPREIMAGE-SHA-256 [Crypto-Conditions](https://tools.ietf.org/html/draft-thomas-crypto-conditions-03)が必要です。条件とフルフィルメントを適切なフォーマットで計算するには、[five-bells-condition](https://github.com/interledgerjs/five-bells-condition)などのCrypto-conditionsライブラリを使用する必要があります。フルフィルメントについては、以下のフルフィルメントを生成するためのメソッドのいずれかを使用することが推奨されます。
- 暗号論的に安全な乱数ソースを使用して、32バイト以上のランダムバイトを生成します。
- Interledger Protocolの[PSK仕様](https://github.com/interledger/rfcs/blob/master/deprecated/0016-pre-shared-key/0016-pre-shared-key.md)に従い、ILPパケットのHMAC-SHA-256をフルフィルメントとして使用します。
ランダムなフルフィルメントと条件のJavaScriptコードの例:
```js
const cc = require('five-bells-condition')
const crypto = require('crypto')
const preimageData = crypto.randomBytes(32)
const myFulfillment = new cc.PreimageSha256()
myFulfillment.setPreimage(preimageData)
const condition = myFulfillment.getConditionBinary().toString('hex').toUpperCase()
console.log('Condition:', condition)
// (Random hexadecimal, 72 chars in length)
// keep secret until you want to finish executing the held payment:
const fulfillment = myFulfillment.serializeBinary().toString('hex').toUpperCase()
console.log('Fulfillment:', fulfillment)
// (Random hexadecimal, 78 chars in length)
```
後で使用できるように条件とフルフィルメントを保存します。保留中の支払いの実行が完了するまでは、フルフィルメントを公開しないでください。フルフィルメントを知っていれば誰でもEscrowを終了でき、保留中の資金を指定された送金先にリリースできます。
## 2.リリース時刻または取消し時刻の計算
条件付き`Escrow`トランザクションには、`CancelAfter`フィールドと`FinishAfter`フィールドのいずれか、または両方が含まれている必要があります。`CancelAfter`フィールドを使用すると、指定の時刻までに条件を満たすことができなかった場合に送金元へXRPを返金できます。`FinishAfter`フィールドに指定される時刻より前の時間は、正しいフルフィルメントが送信されてもEscrowを実行できません。いずれのフィールドでも、将来の時刻を指定する必要があります。
`CancelAfter`の時刻を24時間先に設定する例:
{% tabs %}
{% tab label="JavaScript" %}
```js
const rippleOffset = 946684800
const CancelAfter = Math.floor(Date.now() / 1000) + (24*60*60) - rippleOffset
console.log(CancelAfter)
// Example:556927412
```
{% /tab %}
{% tab label="Python 2/3" %}
```python
from time import time
ripple_offset = 946684800
cancel_after = int(time()) + (24*60*60) - 946684800
print(cancel_after)
# Example: 556927412
```
{% /tab %}
{% /tabs %}
{% admonition type="danger" name="警告" %}XRP Ledgerでは、時刻を**Rippleエポック2000-01-01T00:00:00Z以降の経過秒数**として指定する必要があります。`CancelAfter`または`FinishAfter`フィールドで、UNIX時刻を同等のRipple時刻に変換せずに使用すると、ロック解除時刻が**30年**先に設定されることになります。{% /admonition %}
## 3.EscrowCreateトランザクションの送信
[EscrowCreateトランザクション][]に[署名して送信](../../../../concepts/transactions/index.md#トランザクションへの署名とトランザクションの送信)します。トランザクションの`Condition`フィールドを、保留中の支払いがリリースされる時刻に設定します。`Destination`を受取人に設定します。受取人と送金元のアドレスは同じでもかまいません。前の手順で算出した`CancelAfter`または`FinishAfter`の時刻も指定します。`Amount`を、Escrowする[XRPのdrop数][]の合計額に設定します。
{% partial file="/@l10n/ja/docs/_snippets/secret-key-warning.md" /%}
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/submit-request-escrowcreate-condition.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/submit-response-escrowcreate-condition.json" language="json" /%}
{% /tab %}
{% /tabs %}
## 4.検証の待機
{% partial file="/@l10n/ja/docs/_snippets/wait-for-validation.md" /%}
## 5.Escrowが作成されたことの確認
トランザクションの識別用ハッシュを指定した[txメソッド][]を使用して、トランザクションの最終ステータスを確認します。特に、[Escrowレジャーオブジェクト](../../../../concepts/payment-types/escrow.md)が作成されたことを示す`CreatedNode`をトランザクションメタデータで探します。
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/tx-request-escrowcreate-condition.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/tx-response-escrowcreate-condition.json" language="json" /%}
{% /tab %}
{% /tabs %}
## 6.EscrowFinishトランザクションの送信
`FinishAfter`の時刻が経過した後で資金のリリースを実行する[EscrowFinishトランザクション][]に[署名して送信](../../../../concepts/transactions/index.md#トランザクションへの署名とトランザクションの送信)します。トランザクションの`Owner`フィールドにEscrowCreateトランザクションの`Account`アドレスを設定し、`OfferSequence` にEscrowCreateトランザクションの`Sequence`番号を設定します。`Condition`フィールドと`Fulfillment`フィールドに、ステップ1で生成した条件値とフルフィルメント値をそれぞれ16進数で設定します。フルフィルメントのサイズバイト数に基づいて`Fee`[トランザクションコスト](../../../../concepts/transactions/transaction-cost.md)の値を設定します。条件付きEscrowFinishでは、少なくとも330 dropXRPと、フルフィルメントのサイズで16バイトごとに10 dropが必要です。
{% admonition type="info" name="注記" %}EscrowCreateトランザクションに`FinishAfter`フィールドが含まれている場合、Escrowの条件として正しいフルフィルメントを指定しても、この時刻よりも前の時点ではこのトランザクションを実行できません。前に閉鎖されたレジャーの閉鎖時刻が`FinishAfter`の時刻よりも前である場合、EscrowFinishトランザクションは[結果コード](../../../../references/protocol/transactions/transaction-results/index.md)`tecNO_PERMISSION`で失敗します。{% /admonition %}
Escrowが有効期限切れの場合は、[Escrowの取消し](cancel-an-expired-escrow.md)だけが可能です。
{% partial file="/@l10n/ja/docs/_snippets/secret-key-warning.md" /%}
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/submit-request-escrowfinish-condition.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/submit-response-escrowfinish-condition.json" language="json" /%}
{% /tab %}
{% /tabs %}
トランザクションの識別用`hash`値をメモしておきます。これにより、検証済みレジャーバージョンに記録されるときにその最終ステータスを確認できます。
## 7.検証の待機
{% partial file="/@l10n/ja/docs/_snippets/wait-for-validation.md" /%}
## 8.最終結果の確認
EscrowFinishトランザクションの識別用ハッシュを指定した[txメソッド][]を使用して、トランザクションの最終ステータスを確認します。特にトランザクションメタデータ内で、エスクローに預託された支払いの送金先の`ModifiedNode`(タイプが`AccountRoot`)を確認します。オブジェクトの`FinalFields`に、`Balance`フィールドのXRP返金額の増分が表示されている必要があります。
リクエスト:
{% code-snippet file="/_api-examples/escrow/websocket/tx-request-escrowfinish-condition.json" language="json" /%}
レスポンス:
{% code-snippet file="/_api-examples/escrow/websocket/tx-response-escrowfinish-condition.json" language="json" /%}
{% raw-partial file="/@l10n/ja/docs/_snippets/common-links.md" /%}

View File

@@ -0,0 +1,188 @@
---
html: send-a-time-held-escrow.html
parent: use-escrows.html
seo:
description: 指定した時間が経過することがリリースの唯一の条件であるEscrowを作成します。
labels:
- Escrow
- スマートコントラクト
---
# 時間に基づくEscrowの送信
[EscrowCreateトランザクション][]タイプでは、リリースの唯一の条件が特定時刻を経過することであるEscrowを作成できます。このためには、`FinishAfter`フィールドを使用し、`Condition`フィールドを省略します。
## 1.リリース時刻の計算
時刻を **[Rippleエポック以降の経過秒数][]** として指定する必要があります。Rippleエポックは、UNIXエポックの946684800秒後です。たとえば、2017年11月13日の午前0時UTCに資金をリリースする場合、以下のようになります。
{% tabs %}
{% tab label="JavaScript" %}
```js
// JavaScript Date() is natively expressed in milliseconds; convert to seconds
const release_date_unix = Math.floor( new Date("2017-11-13T00:00:00Z") / 1000 );
const release_date_ripple = release_date_unix - 946684800;
console.log(release_date_ripple);
// 563846400
```
{% /tab %}
{% tab label="Python 3" %}
```python
import datetime
release_date_utc = datetime.datetime(2017,11,13,0,0,0,tzinfo=datetime.timezone.utc)
release_date_ripple = int(release_date_utc.timestamp()) - 946684800
print(release_date_ripple)
# 563846400
```
{% /tab %}
{% /tabs %}
{% admonition type="danger" name="警告" %}`FinishAfter`フィールドで、UNIX時刻を同等のRipple時刻に変換せずに使用すると、ロック解除時刻が30年先に設定されることになります。{% /admonition %}
## 2.EscrowCreateトランザクションの送信
[EscrowCreateトランザクション][]に[署名して送信](../../../../concepts/transactions/index.md#トランザクションへの署名とトランザクションの送信)します。トランザクションの`FinishAfter`フィールドを、保留中の支払いがリリースされる時刻に設定します。`Condition`フィールドを省略して、時刻を保留中の支払いをリリースする唯一の条件とします。`Destination`を受取人に設定します。受取人と送金元のアドレスは同じでもかまいません。`Amount`を、Escrowする[XRPのdrop数][]の合計額に設定します。
{% partial file="/@l10n/ja/docs/_snippets/secret-key-warning.md" /%}
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/submit-request-escrowcreate-time.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/submit-response-escrowcreate-time.json" language="json" /%}
{% /tab %}
{% /tabs %}
トランザクションの識別用`hash`値をメモしておきます。これにより、検証済みレジャーバージョンに記録されるときにその最終ステータスを確認できます。
## 3.検証の待機
{% partial file="/@l10n/ja/docs/_snippets/wait-for-validation.md" /%}
## 4.Escrowが作成されたことの確認
トランザクションの識別用ハッシュを指定した[txメソッド][]を使用して、トランザクションの最終ステータスを確認します。[Escrowレジャーオブジェクト](../../../../concepts/payment-types/escrow.md)が作成されたことを示す`CreatedNode`をトランザクションメタデータで探します。
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/tx-request-escrowcreate-time.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/tx-response-escrowcreate-time.json" language="json" /%}
{% /tab %}
{% /tabs %}
## 5.リリース時刻までの待機
`FinishAfter`時刻が指定されている保留中の支払いは、Escrowードの`FinishAfter`時刻よりも後の[`close_time`ヘッダーフィールド](../../../../references/protocol/ledger-data/ledger-header.md)の時刻でレジャーが閉鎖するまでは完了できません。
最新の検証済みレジャーの閉鎖時刻は、[ledgerメソッド][]を使用して検索できます。
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/ledger-request.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/ledger-response.json" language="json" /%}
{% /tab %}
{% /tabs %}
## 6.EscrowFinishトランザクションの送信
`FinishAfter`の時刻が経過した後で資金のリリースを実行する[EscrowFinishトランザクション][]に[署名して送信](../../../../concepts/transactions/index.md#トランザクションへの署名とトランザクションの送信)します。トランザクションの`Owner`フィールドにEscrowCreateトランザクションの`Account`アドレスを設定し、`OfferSequence` にEscrowCreateトランザクションの`Sequence`番号を設定します。時刻のみに基づいて保留されているEscrowの場合は、`Condition`フィールドと`Fulfillment`フィールドを省略します。
{% admonition type="success" name="ヒント" %}XRP Ledgerの状態はトランザクションでしか変更できないため、EscrowFinishトランザクションが必要です。このトランザクションの送信者は、Escrowの受取人、Escrowの元としての送金人、またはその他のXRP Ledgerアドレスのいずれかです。{% /admonition %}
Escrowが有効期限切れの場合は、[Escrowの取消し](cancel-an-expired-escrow.md)だけが可能です。
{% partial file="/@l10n/ja/docs/_snippets/secret-key-warning.md" /%}
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/submit-request-escrowfinish-time.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/submit-response-escrowfinish-time.json" language="json" /%}
{% /tab %}
{% /tabs %}
トランザクションの識別用`hash`値をメモしておきます。これにより、検証済みレジャーバージョンに記録されるときにその最終ステータスを確認できます。
## 7.検証の待機
{% partial file="/@l10n/ja/docs/_snippets/wait-for-validation.md" /%}
## 8.最終結果の確認
EscrowFinishトランザクションの識別用ハッシュを指定した[txメソッド][]を使用して、トランザクションの最終ステータスを確認します。特にトランザクションメタデータ内で、エスクローに預託された支払いの送金先の`ModifiedNode`(タイプが`AccountRoot`)を確認します。オブジェクトの`FinalFields`に、`Balance`フィールドのXRP返金額の増分が表示されている必要があります。
リクエスト:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/tx-request-escrowfinish-time.json" language="json" /%}
{% /tab %}
{% /tabs %}
レスポンス:
{% tabs %}
{% tab label="Websocket" %}
{% code-snippet file="/_api-examples/escrow/websocket/tx-response-escrowfinish-time.json" language="json" /%}
{% /tab %}
{% /tabs %}
{% raw-partial file="/@l10n/ja/docs/_snippets/common-links.md" /%}

View File

@@ -72,7 +72,7 @@ XRP Ledgerの分散型取引所(DEX)には、「アルゴリズムトレード
### トレードの発注
XRP Ledgerの分散型取引所で _代替可能_ トークンとXRPを売買するには、通常[OfferCreateトランザクション](../../references/protocol/transactions/types/offercreate.md)を送信します。この方法でトレードを行うためのコードと技術的ステップの詳細なウォークスルーについては、[分散型取引所でのトレード](../../tutorials/how-tos/use-tokens/trade-in-the-decentralized-exchange.md)をご覧ください。[Paymentトランザクション](../../references/protocol/transactions/types/payment.md)を使用して通貨を両替することも可能です。[クロスカレンしー支払い](../../concepts/payment-types/cross-currency-payments.md)を他のユーザに送ったり、長い[パス](../../concepts/tokens/fungible-tokens/paths.md)を使って裁定取引の機会を1つの操作にまとめることで、自分自身に送り返すこともできます。
XRP Ledgerの分散型取引所で _代替可能_ トークンとXRPを売買するには、通常[OfferCreateトランザクション](../../references/protocol/transactions/types/offercreate.md)を送信します。この方法でトレードを行うためのコードと技術的ステップの詳細なウォークスルーについては、[分散型取引所でのトレード](/docs/tutorials/dex/trade-in-the-decentralized-exchange.md)をご覧ください。[Paymentトランザクション](../../references/protocol/transactions/types/payment.md)を使用して通貨を両替することも可能です。[クロスカレンしー支払い](../../concepts/payment-types/cross-currency-payments.md)を他のユーザに送ったり、長い[パス](../../concepts/tokens/fungible-tokens/paths.md)を使って裁定取引の機会を1つの操作にまとめることで、自分自身に送り返すこともできます。
NFTをトレードするためのコードと技術的な手順については、[JavaScriptを使用したNFTokenの送信](../../tutorials/javascript/nfts/transfer-nfts.md)をご覧ください。
@@ -89,7 +89,7 @@ XRP Ledgerのトレード活動に関する情報源は数多くあります。
XRP Ledgerは既存の中央指値注文ベース(CLOB)の分散型取引所と連携するネイティブな自動マーケットメーカー(AMM)の機能をネイティブサポートしています。AMMはXRP Ledger上のトレードにおいて重要な要素となっています。詳しくは以下のリンクをご覧ください。
- [自動マーケットメーカー](../../concepts/tokens/decentralized-exchange/automated-market-makers.md)
- [AMMのオークションスロットを利用して低い取引手数料でトレードする](../../tutorials/javascript/amm/add-assets-to-amm.md)
- [AMMのオークションスロットを利用して低い取引手数料でトレードする](/docs/tutorials/dex/add-assets-to-amm-in-javascript.md)
- [XLS-30 標準規格](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0030-automated-market-maker#readme)
## さらに詳しく

View File

@@ -37,7 +37,7 @@ XRP Ledger上のスマートコントラクトは、条件付きで保有する
オラクルのプログラムが条件を満たしたことを検知した後、エスクローの受取人にfulfillmentの16進数値を渡します。この時点以降、オラクルはエスクローを終了させるなど、何も行いません。エスクローの受取人は、ほとんどの場合、エスクローを終了することになります。
[条件に基づくEscrowの送信](../../tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-conditional-escrow.md)をご覧ください。
[conditionとfulfillmentの生成](../../tutorials/how-tos/use-specialized-payment-types/use-escrows/send-a-conditionally-held-escrow.md#1-generate-condition-and-fulfillment)をご覧ください。
## 例

View File

@@ -67,7 +67,7 @@ NFTをオークション形式で販売することができます。[NFTオー
XRPL NFTの最もシンプルな支払い方法はXRPです。XRPを使ったNFTの売り買いの例については、[NFTokenの取引](../../tutorials/javascript/nfts/transfer-nfts.md)をご覧ください。
他の通貨での取引は、DEXを活用してあらゆる種類の発行通貨を受け入れ、取引することができます。[分散型取引所での取引](../../tutorials/how-tos/use-tokens/trade-in-the-decentralized-exchange.md#trade-in-the-decentralized-exchange)をご覧ください。
他の通貨での取引は、DEXを活用してあらゆる種類の発行通貨を受け入れ、取引することができます。[分散型取引所での取引](/docs/tutorials/dex/trade-in-the-decentralized-exchange.md#trade-in-the-decentralized-exchange)をご覧ください。
## NFTのインデックス

View File

@@ -64,7 +64,7 @@ NFTをオークション形式で販売することができます。[NFTオー
XRPL NFTの最もシンプルな支払い方法はXRPです。XRPを使ったNFTの売り買いの例については、[NFTokenの取引](../../tutorials/javascript/nfts/transfer-nfts.md))をご覧ください。
他の通貨での取引は、DEXを活用してあらゆる種類の発行通貨を受け入れ、取引することができます。[分散型取引所での取引](../../tutorials/how-tos/use-tokens/trade-in-the-decentralized-exchange.md#trade-in-the-decentralized-exchange)をご覧ください。
他の通貨での取引は、DEXを活用してあらゆる種類の発行通貨を受け入れ、取引することができます。[分散型取引所での取引](/docs/tutorials/dex/trade-in-the-decentralized-exchange.md#trade-in-the-decentralized-exchange)をご覧ください。
## NFTのインデックス化

View File

@@ -105,7 +105,7 @@ NFTをオークション形式で販売することができます。[NFTオー
XRPL NFTの最もシンプルな支払い方法はXRPです。XRPを使ったNFTの売り買いの例については、[NFTokenの取引](../../tutorials/javascript/nfts/transfer-nfts.md)をご覧ください。
他の通貨での取引は、DEXを活用してあらゆる種類の発行通貨を受け入れ、取引することができます。[分散型取引所での取引](../../tutorials/how-tos/use-tokens/trade-in-the-decentralized-exchange.md#trade-in-the-decentralized-exchange)をご覧ください。
他の通貨での取引は、DEXを活用してあらゆる種類の発行通貨を受け入れ、取引することができます。[分散型取引所での取引](/docs/tutorials/dex/trade-in-the-decentralized-exchange.md#trade-in-the-decentralized-exchange)をご覧ください。
<!--

View File

@@ -67,7 +67,7 @@ NFTをオークション形式で販売することができます。[NFTオー
XRPL NFTの最もシンプルな支払い方法はXRPです。XRPを使ったNFTの売り買いの例については、[NFTokenの取引](../../tutorials/javascript/nfts/transfer-nfts.md)をご覧ください。
他の通貨での取引は、DEXを活用してあらゆる種類の発行通貨を受け入れ、取引することができます。[分散型取引所での取引](../../tutorials/how-tos/use-tokens/trade-in-the-decentralized-exchange.md#trade-in-the-decentralized-exchange)をご覧ください。
他の通貨での取引は、DEXを活用してあらゆる種類の発行通貨を受け入れ、取引することができます。[分散型取引所での取引](/docs/tutorials/dex/trade-in-the-decentralized-exchange.md#trade-in-the-decentralized-exchange)をご覧ください。
## NFTのインデックス化

View File

@@ -0,0 +1,55 @@
---
html: tutorial-structure.html
parent: contribute-documentation.html
seo:
description: 一般的なチュートリアルの構成要素の要約です。
---
# チュートリアルの構成
各XRP Ledgerチュートリアルは、同一のフォーマットで構成されています。
1. チュートリアルで説明する機能の簡単な説明。
2. コードを実行するための前提条件(必要な場合)、またはサンプルコードへのリンク。
3. チュートリアルの機能の使用例。
4. サンプルコードの解説と、そのスクリプトの特徴的な要素の紹介。
5. 次のステップとして試すべき概念的な情報や優れたチュートリアルへのリンク。
セットアップ(前提条件)と使用方法とコード開発は分けて考えましょう。これらはそれぞれ異なる活動であり、それぞれ脳の異なる領域を動かします。この3つの要素を一度に考えようとすると、混乱につながります。
## 説明
![説明](/docs/img/tut-struct1.png)
そのサンプルが何を示しているかを記載してください。可能であれば、各サンプルには関連する特定のタスクを達成するための手順を記述してください。(NFTの売却オファーの作成、売却オファーの受け入れ、売却オファーの削除など)。チュートリアルで説明されている内容を理解するのに十分なコンセプトに関する情報を記載し、必要であれば、追加情報へのリンクも記載します。
## 前提条件
![前提条件](/docs/img/tut-struct2.png)
必要なソフトウェアと、チュートリアルを実行するために必要なすべてのサンプルコードへのリンクを提供します。必要であれば、サードパーティのツールの使い方を簡単に説明しますが、ユーザが自由に深く掘り下げることができるように、ソースとなるウェブサイトへのリンクを提供します。
## 使用例
![使用例](/docs/img/tut-struct3.png)
チュートリアルのアプリケーションの完成した動作例を提供することから始めましょう。これは、ソフトウェアを使って問題を解決するチャンスです。
 
チュートリアルの各ステップにはスクリーンショットを使用してください。これによって、ユーザは自分でコードを実行しなくてもチュートリアルを理解することができます。もちろん、コードを実行することが _望ましい_ ですが、これにりユーザに選択肢を与えることができます。
適切な条件におけるシナリオを記述してください。インターネットへの接続が途切れなければ、アプリケーションは問題なく動作するはずです。チュートリアルに関連しないトラブルシューティングの情報を提供しないでください。
## コード解説
![コード解説](/docs/img/tut-struct4.png)
コードを1ブロックずつ見ていきましょう。既に説明したトピックを繰り返さないでください。サンプルコードには、HTML構文のような基本的な部分のプログラミング方法については、その実装に独自なものがない限り、詳細な説明はしないでください。
強調すべき重要なことは、XRPLとのやりとりはすべてトランザクションかリクエストであり、すべてのトランザクションとリクエストは本質的に同じであるということです。私たちが提供するサンプルコードは、トランザクションやリクエストを準備する方法と、返された結果を処理する方法を示しています。1つのトランザクションやリクエストをどのように送信しどのようなレスポンスを返すかを知ることは、他のトランザクションやリクエストの処理について非常に良いヒントとなります。
(技術的には、リクエストに似た第3のカテゴリがあります。[Subscriptionメソッド](../../docs/references/http-websocket-apis/public-api-methods/subscription-methods/index.md)をご覧ください)。
## 関連項目
![関連項目](/docs/img/tut-struct5.png)
チュートリアルの最後には、追加の資料、概念的な情報、学習のにおいて有益な次のステップとなるチュートリアルへのリンクを提供します。

View File

@@ -1,152 +1,449 @@
import * as React from "react";
import { useSearchDialog } from "@redocly/theme/core/hooks";
import { SearchDialog } from "@redocly/theme/components/Search/SearchDialog";
import { useThemeConfig, useThemeHooks } from "@redocly/theme/core/hooks";
import { LanguagePicker } from "@redocly/theme/components/LanguagePicker/LanguagePicker";
import { slugify } from "../../helpers";
import { Link } from "@redocly/theme/components/Link/Link";
import { ColorModeSwitcher } from "@redocly/theme/components/ColorModeSwitcher/ColorModeSwitcher";
import { Search } from "@redocly/theme/components/Search/Search";
import arrowUpRight from "../../../static/img/icons/arrow-up-right-custom.svg";
import moment from "moment-timezone";
// Import from modular components
import { AlertBanner } from "./components/AlertBanner";
import { NavLogo } from "./components/NavLogo";
import { NavItems } from "./components/NavItems";
import { NavControls, HamburgerButton } from "./controls";
import { DevelopSubmenu, UseCasesSubmenu, CommunitySubmenu, NetworkSubmenu } from "./submenus";
import { MobileMenu } from "./mobile-menu";
import { alertBanner } from "./constants/navigation";
// @ts-ignore
// Re-export AlertBanner for backwards compatibility
export { AlertBanner } from "./components/AlertBanner";
const alertBanner = {
show: false,
message: "APEX 2025",
button: "REGISTER",
link: "https://www.xrpledgerapex.com/?utm_source=xrplwebsite&utm_medium=direct&utm_campaign=xrpl-event-ho-xrplapex-glb-2025-q1_xrplwebsite_ari_arp_bf_rsvp&utm_content=cta_btn_english_pencilbanner"
};
// Props interface for Navbar (extensible for future use)
interface NavbarProps {
className?: string;
}
export function AlertBanner({ message, button, link, show }) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const bannerRef = React.useRef(null);
const [displayDate, setDisplayDate] = React.useState("JUNE 10-12");
/**
* Main Navbar Component.
* Renders the complete navigation bar including:
* - Alert banner (when enabled)
* - Logo
* - Navigation items with desktop submenus
* - Control buttons (search, theme toggle, language)
* - Mobile menu
*/
export function Navbar(_props: NavbarProps = {}) {
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
const [activeSubmenu, setActiveSubmenu] = React.useState<string | null>(null);
const [closingSubmenu, setClosingSubmenu] = React.useState<string | null>(null);
const submenuTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
const closingTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
// Use Redocly's search dialog hook - shared across navbar and mobile menu
const { isOpen: isSearchOpen, onOpen: onSearchOpen, onClose: onSearchClose } = useSearchDialog();
const handleHamburgerClick = () => {
setMobileMenuOpen(true);
};
const handleMobileMenuClose = () => {
setMobileMenuOpen(false);
};
const handleSubmenuMouseEnter = (itemLabel: string) => {
// Clear any pending close/closing timeouts
if (submenuTimeoutRef.current) {
clearTimeout(submenuTimeoutRef.current);
submenuTimeoutRef.current = null;
}
if (closingTimeoutRef.current) {
clearTimeout(closingTimeoutRef.current);
closingTimeoutRef.current = null;
}
// Cancel closing state and activate the new submenu
setClosingSubmenu(null);
setActiveSubmenu(itemLabel);
};
const handleSubmenuMouseLeave = () => {
submenuTimeoutRef.current = setTimeout(() => {
// Start closing animation
const currentSubmenu = activeSubmenu;
if (currentSubmenu) {
setClosingSubmenu(currentSubmenu);
setActiveSubmenu(null);
// After animation completes (300ms), clear closing state
closingTimeoutRef.current = setTimeout(() => {
setClosingSubmenu(null);
}, 350); // Slightly longer than animation to ensure completion
}
}, 150);
};
const handleSubmenuClose = () => {
// Close submenu immediately (for keyboard navigation)
if (activeSubmenu) {
setClosingSubmenu(activeSubmenu);
setActiveSubmenu(null);
// After animation completes, clear closing state
closingTimeoutRef.current = setTimeout(() => {
setClosingSubmenu(null);
}, 350);
}
};
// Handle scroll lock when submenu is open or closing
React.useEffect(() => {
if (activeSubmenu || closingSubmenu) {
document.body.classList.add('bds-submenu-open');
} else {
document.body.classList.remove('bds-submenu-open');
}
return () => {
document.body.classList.remove('bds-submenu-open');
const calculateCountdown = () => {
// Calculate days until June 11, 2025 8AM Singapore time
// This will automatically adjust for the user's timezone
const target = moment.tz('2025-06-11 08:00:00', 'Asia/Singapore');
const now = moment();
const daysUntil = target.diff(now, 'days');
// Show countdown if event is in the future, otherwise show the provided date
let newDisplayDate = "JUNE 10-12";
if (daysUntil > 0) {
newDisplayDate = daysUntil === 1 ? 'IN 1 DAY' : `IN ${daysUntil} DAYS`;
} else if (daysUntil === 0) {
// Check if it's today
const hoursUntil = target.diff(now, 'hours');
newDisplayDate = hoursUntil > 0 ? 'TODAY' : "JUNE 10-12";
}
setDisplayDate(newDisplayDate);
};
}, [activeSubmenu, closingSubmenu]);
// Calculate immediately
calculateCountdown();
// Update every hour
const interval = setInterval(calculateCountdown, 60 * 60 * 1000);
return () => clearInterval(interval);
}, []);
React.useEffect(() => {
const banner = bannerRef.current;
if (!banner) return;
const handleMouseEnter = () => {
banner.classList.add("has-hover");
};
// Attach the event listener
banner.addEventListener("mouseenter", handleMouseEnter);
// Clean up the event listener on unmount
return () => {
if (submenuTimeoutRef.current) {
clearTimeout(submenuTimeoutRef.current);
}
if (closingTimeoutRef.current) {
clearTimeout(closingTimeoutRef.current);
}
banner.removeEventListener("mouseenter", handleMouseEnter);
};
}, []);
const navbarClasses = [
"bds-navbar",
alertBanner.show ? "bds-navbar--with-banner" : ""
].filter(Boolean).join(" ");
if (show) {
return (
<a
href={link}
target="_blank"
ref={bannerRef}
className="top-banner fixed-top web-banner"
rel="noopener noreferrer"
aria-label="Get Tickets for the APEX 2025 Event"
>
<div className="banner-event-details">
<div className="event-info">{translate(message)}</div>
<div className="event-date">{displayDate}</div>
</div>
<div className="banner-button">
<div className="button-text">{translate(button)}</div>
<img
className="button-icon"
src={arrowUpRight}
alt="Get Tickets Icon"
/>
</div>
</a>
);
}
return null;
}
export function Navbar(props) {
// const [isOpen, setIsOpen] = useMobileMenu(false);
const themeConfig = useThemeConfig();
const { useL10n } = useThemeHooks();
const { changeLanguage } = useL10n();
const menu = themeConfig.navbar?.items;
const logo = themeConfig.logo;
const { href, altText, items } = props;
const pathPrefix = "";
const navItems = menu.map((item, index) => {
if (item.type === "group") {
return (
<NavDropdown
key={index}
label={item.label}
labelTranslationKey={item.labelTranslationKey}
items={item.items}
pathPrefix={pathPrefix}
/>
);
} else {
return (
<NavItem key={index}>
<Link to={item.link} className="nav-link">
{item.label}
</Link>
</NavItem>
);
}
});
React.useEffect(() => {
// Turns out jQuery is necessary for firing events on Bootstrap v4
// dropdowns. These events set classes so that the search bar and other
// submenus collapse on mobile when you expand one submenu.
const dds = $("#topnav-pages .dropdown");
const top_main_nav = document.querySelector("#top-main-nav");
dds.on("show.bs.dropdown", (evt) => {
top_main_nav.classList.add("submenu-expanded");
});
dds.on("hidden.bs.dropdown", (evt) => {
top_main_nav.classList.remove("submenu-expanded");
});
// Close navbar on .dropdown-item click
const toggleNavbar = () => {
const navbarToggler = document.querySelector(".navbar-toggler");
const isNavbarCollapsed =
navbarToggler.getAttribute("aria-expanded") === "true";
if (isNavbarCollapsed) {
navbarToggler?.click(); // Simulate click to toggle navbar
}
};
const dropdownItems = document.querySelectorAll(".dropdown-item");
dropdownItems.forEach((item) => {
item.addEventListener("click", toggleNavbar);
});
// Cleanup function to remove event listeners
return () => {
dropdownItems.forEach((item) => {
item.removeEventListener("click", toggleNavbar);
});
};
}, []);
return (
<>
<AlertBanner {...alertBanner} />
{/* Backdrop blur overlay when submenu is open or closing */}
<div
className={`bds-submenu-backdrop ${activeSubmenu || closingSubmenu ? 'bds-submenu-backdrop--active' : ''}`}
onClick={() => setActiveSubmenu(null)}
/>
<header
className={navbarClasses}
onMouseLeave={handleSubmenuMouseLeave}
>
<div className="bds-navbar__content">
<NavLogo />
<NavItems activeSubmenu={activeSubmenu} onSubmenuEnter={handleSubmenuMouseEnter} onSubmenuClose={handleSubmenuClose} />
<NavControls onSearch={onSearchOpen} />
<HamburgerButton onClick={handleHamburgerClick} />
</div>
{/* Submenus positioned relative to navbar */}
<div onMouseEnter={() => activeSubmenu && handleSubmenuMouseEnter(activeSubmenu)}>
<DevelopSubmenu isActive={activeSubmenu === 'Develop'} isClosing={closingSubmenu === 'Develop'} onClose={handleSubmenuClose} />
<UseCasesSubmenu isActive={activeSubmenu === 'Use Cases'} isClosing={closingSubmenu === 'Use Cases'} onClose={handleSubmenuClose} />
<CommunitySubmenu isActive={activeSubmenu === 'Community'} isClosing={closingSubmenu === 'Community'} onClose={handleSubmenuClose} />
<NetworkSubmenu isActive={activeSubmenu === 'Network'} isClosing={closingSubmenu === 'Network'} onClose={handleSubmenuClose} />
</div>
</header>
<MobileMenu isOpen={mobileMenuOpen} onClose={handleMobileMenuClose} onSearch={onSearchOpen} />
{/* Render SearchDialog when open - this is the actual search modal */}
{isSearchOpen && <SearchDialog onClose={onSearchClose} />}
<NavWrapper belowAlertBanner={alertBanner.show}>
<LogoBlock to={href} img={logo} alt={altText} />
<NavControls>
<MobileMenuIcon />
</NavControls>
<TopNavCollapsible>
<NavItems>
{navItems}
<div id="topnav-search" className="nav-item search">
<Search className="topnav-search" />
</div>
<div id="topnav-language" className="nav-item">
<LanguagePicker
onChangeLanguage={changeLanguage}
onlyIcon
alignment="end"
/>
</div>
<div id="topnav-theme" className="nav-item">
<ColorModeSwitcher />
</div>
</NavItems>
</TopNavCollapsible>
</NavWrapper>
</>
);
}
export function TopNavCollapsible({ children }) {
return (
<div
className="collapse navbar-collapse justify-content-between"
id="top-main-nav"
>
{children}
</div>
);
}
export function NavDropdown(props) {
const { label, items, pathPrefix, labelTranslationKey } = props;
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const dropdownGroups = items.map((item, index) => {
if (item.items) {
const groupLinks = item.items.map((item2, index2) => {
const cls2 = item2.external
? "dropdown-item external-link"
: "dropdown-item";
let item2_href = item2.link;
if (item2_href && !item2_href.match(/^https?:/)) {
item2_href = pathPrefix + item2_href;
}
//conditional specific for brand kit
if (item2.link === "/XRPL_Brand_Kit.zip") {
return (
<a key={index2} href="/XRPL_Brand_Kit.zip" className={cls2}>
{translate(item2.labelTranslationKey, item2.label)}
</a>
);
}
return (
<Link key={index2} className={cls2} to={item2_href}>
{translate(item2.labelTranslationKey, item2.label)}
</Link>
);
});
const clnm = "navcol col-for-" + slugify(item.label);
return (
<div key={index} className={clnm}>
<h5 className="dropdown-item">
{translate(item.labelTranslationKey, item.label)}
</h5>
{groupLinks}
</div>
);
} else if (item.icon) {
const hero_id = "dropdown-hero-for-" + slugify(label);
const img_alt = item.label + " icon";
let hero_href = item.link;
if (hero_href && !hero_href.match(/^https?:/)) {
hero_href = pathPrefix + hero_href;
}
const splitlabel = item.label.split(" || ");
let splittranslationkey = ["", ""];
if (item.labelTranslationKey) {
splittranslationkey = item.labelTranslationKey.split(" || ");
}
const newlabel = translate(splittranslationkey[0], splitlabel[0]);
const description = translate(splittranslationkey[1], splitlabel[1]); // splitlabel[1] might be undefined, that's ok
return (
<Link
key={index}
className="dropdown-item dropdown-hero"
id={hero_id}
to={hero_href}
>
<img id={item.hero} alt={img_alt} src={item.icon} />
<div className="dropdown-hero-text">
<h4>{newlabel}</h4>
<p>{description}</p>
</div>
</Link>
);
} else {
const cls = item.external
? "dropdown-item ungrouped external-link"
: "dropdown-item ungrouped";
let item_href = item.link;
if (item_href && !item_href.match(/^https?:/)) {
item_href = pathPrefix + item_href;
}
return (
<Link key={index} className={cls} to={item_href}>
{translate(item.labelTranslationKey, item.label)}
</Link>
);
}
});
const toggler_id = "topnav_" + slugify(label);
const dd_id = "topnav_dd_" + slugify(label);
return (
<li className="nav-item dropdown">
<a
className="nav-link dropdown-toggle"
href="#"
id={toggler_id}
role="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<span>{translate(labelTranslationKey, label)}</span>
</a>
<div className="dropdown-menu" aria-labelledby={toggler_id} id={dd_id}>
{dropdownGroups}
</div>
</li>
);
}
export function NavWrapper(props) {
return (
<nav
className="top-nav navbar navbar-expand-lg navbar-dark fixed-top"
style={props.belowAlertBanner ? { marginTop: "52px" } : {}}
>
{props.children}
</nav>
);
}
export function NavControls(props) {
return (
<button
className="navbar-toggler collapsed"
type="button"
data-toggle="collapse"
data-target="#top-main-nav"
aria-controls="navbarHolder"
aria-expanded="false"
aria-label="Toggle navigation"
>
{props.children}
</button>
);
}
export function MobileMenuIcon() {
return (
<span className="navbar-toggler-icon">
<div></div>
</span>
);
}
export function GetStartedButton() {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return (
<Link
className="btn btn-primary"
to={"/docs/tutorials"}
style={{ height: "38px", paddingTop: "11px" }}
>
{translate("Get Started")}
</Link>
);
}
export function NavItems(props) {
return (
<ul className="nav navbar-nav" id="topnav-pages">
{props.children}
</ul>
);
}
export function NavItem(props) {
return <li className="nav-item">{props.children}</li>;
}
export function LogoBlock(props) {
const { to, img, altText } = props;
return (
<Link className="navbar-brand" to="/">
<img className="logo" alt={"XRP LEDGER"} height="40" src="data:," />
</Link>
);
}
export class ThemeToggle extends React.Component {
auto_update_theme() {
const upc = window.localStorage.getItem("user-prefers-color");
let theme = "dark"; // Default to dark theme
if (!upc) {
// User hasn't saved a preference specifically for this site; check
// the browser-level preferences.
if (
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: light)").matches
) {
theme = "light";
}
} else {
// Follow user's saved setting.
theme = upc == "light" ? "light" : "dark";
}
const disable_theme = theme == "dark" ? "light" : "dark";
document.documentElement.classList.add(theme);
document.documentElement.classList.remove(disable_theme);
}
user_choose_theme() {
const new_theme = document.documentElement.classList.contains("dark")
? "light"
: "dark";
window.localStorage.setItem("user-prefers-color", new_theme);
document.body.style.transition = "background-color .2s ease";
const disable_theme = new_theme == "dark" ? "light" : "dark";
document.documentElement.classList.add(new_theme);
document.documentElement.classList.remove(disable_theme);
}
render() {
return (
<div className="nav-item" id="topnav-theme">
<form className="form-inline">
<div
className="custom-control custom-theme-toggle form-inline-item"
title=""
data-toggle="tooltip"
data-placement="left"
data-original-title="Toggle Dark Mode"
>
<input
type="checkbox"
className="custom-control-input"
id="css-toggle-btn"
onClick={this.user_choose_theme}
/>
<label className="custom-control-label" htmlFor="css-toggle-btn">
<span className="d-lg-none">Light/Dark Theme</span>
</label>
</div>
</form>
</div>
);
}
componentDidMount() {
this.auto_update_theme();
}
}

View File

@@ -1,82 +0,0 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import moment from "moment-timezone";
import { arrowUpRight } from "../constants/icons";
interface AlertBannerProps {
message: string;
button: string;
link: string;
show: boolean;
}
/**
* Alert Banner Component.
* Displays a promotional banner at the top of the page.
*/
export function AlertBanner({ message, button, link, show }: AlertBannerProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const bannerRef = React.useRef<HTMLAnchorElement>(null);
// Use null initial state to avoid hydration mismatch - server and client both render null initially
const [displayDate, setDisplayDate] = React.useState<string | null>(null);
React.useEffect(() => {
const calculateCountdown = () => {
const target = moment.tz('2025-06-11 08:00:00', 'Asia/Singapore');
const now = moment();
const daysUntil = target.diff(now, 'days');
let newDisplayDate = "JUNE 10-12";
if (daysUntil > 0) {
newDisplayDate = daysUntil === 1 ? 'IN 1 DAY' : `IN ${daysUntil} DAYS`;
} else if (daysUntil === 0) {
const hoursUntil = target.diff(now, 'hours');
newDisplayDate = hoursUntil > 0 ? 'TODAY' : "JUNE 10-12";
}
setDisplayDate(newDisplayDate);
};
calculateCountdown();
const interval = setInterval(calculateCountdown, 60 * 60 * 1000);
return () => clearInterval(interval);
}, []);
React.useEffect(() => {
const banner = bannerRef.current;
if (!banner) return;
const handleMouseEnter = () => {
banner.classList.add("has-hover");
};
banner.addEventListener("mouseenter", handleMouseEnter);
return () => {
banner.removeEventListener("mouseenter", handleMouseEnter);
};
}, []);
if (!show) return null;
return (
<a
href={link}
target="_blank"
ref={bannerRef}
className="top-banner fixed-top web-banner"
rel="noopener noreferrer"
aria-label={translate("Get Tickets for the APEX 2025 Event")}
>
<div className="banner-event-details">
<div className="event-info">{translate(message)}</div>
<div className="event-date">{displayDate ?? translate("JUNE 10-12")}</div>
</div>
<div className="banner-button">
<div className="button-text">{translate(button)}</div>
<img className="button-icon" src={arrowUpRight} alt="" />
</div>
</a>
);
}

View File

@@ -1,114 +0,0 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { BdsLink } from "../../../../shared/components/Link/Link";
import { navItems } from "../constants/navigation";
interface NavItemsProps {
activeSubmenu: string | null;
onSubmenuEnter: (itemLabel: string) => void;
onSubmenuClose?: () => void;
}
/**
* Nav Items Component.
* Centered navigation links with submenu support.
* ARIA compliant with full keyboard navigation support.
*/
export function NavItems({ activeSubmenu, onSubmenuEnter, onSubmenuClose }: NavItemsProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const [activeItem, setActiveItem] = React.useState<string | null>(null);
const handleMouseEnter = (itemLabel: string, hasSubmenu: boolean) => {
setActiveItem(itemLabel);
if (hasSubmenu) {
onSubmenuEnter(itemLabel);
}
};
const handleMouseLeave = (hasSubmenu: boolean) => {
if (!hasSubmenu) {
setActiveItem(null);
}
// Don't close submenu on leave - let the parent Navbar handle that
};
const handleKeyDown = (event: React.KeyboardEvent, itemLabel: string) => {
switch (event.key) {
case 'Enter':
case ' ':
event.preventDefault();
// Toggle submenu on Enter/Space
if (activeSubmenu === itemLabel) {
onSubmenuClose?.();
} else {
onSubmenuEnter(itemLabel);
}
break;
case 'Escape':
event.preventDefault();
onSubmenuClose?.();
break;
case 'Tab':
// If submenu is open and Tab is pressed (without Shift), move focus into submenu
if (activeSubmenu === itemLabel && !event.shiftKey) {
event.preventDefault();
// Focus first focusable element in submenu
const submenu = document.querySelector('.bds-submenu--active');
const firstFocusable = submenu?.querySelector<HTMLElement>('a, button');
firstFocusable?.focus();
}
break;
case 'ArrowDown':
// If submenu is open, move focus into submenu
if (activeSubmenu === itemLabel) {
event.preventDefault();
// Focus first focusable element in submenu
const submenu = document.querySelector('.bds-submenu--active');
const firstFocusable = submenu?.querySelector<HTMLElement>('a, button');
firstFocusable?.focus();
}
break;
}
};
// Sync activeItem with activeSubmenu state
React.useEffect(() => {
if (!activeSubmenu) {
setActiveItem(null);
}
}, [activeSubmenu]);
return (
<nav className="bds-navbar__items" aria-label={translate("Main navigation")}>
{navItems.map((item) => (
item.hasSubmenu ? (
<button
key={item.label}
type="button"
className={`bds-navbar__item ${activeItem === item.label || activeSubmenu === item.label ? 'bds-navbar__item--active' : ''}`}
onMouseEnter={() => handleMouseEnter(item.label, true)}
onMouseLeave={() => handleMouseLeave(true)}
onKeyDown={(e) => handleKeyDown(e, item.label)}
aria-expanded={activeSubmenu === item.label}
aria-haspopup="menu"
>
{translate(item.labelTranslationKey, item.label)}
</button>
) : (
<BdsLink
key={item.label}
href={item.href}
className={`bds-navbar__item ${activeItem === item.label ? 'bds-navbar__item--active' : ''}`}
onMouseEnter={() => handleMouseEnter(item.label, false)}
onMouseLeave={() => handleMouseLeave(false)}
variant="inline"
>
{translate(item.labelTranslationKey, item.label)}
</BdsLink>
)
))}
</nav>
);
}

View File

@@ -1,34 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { BdsLink } from "../../../../shared/components/Link/Link";
import { xrpSymbolBlack, xrpLogotypeBlack, xrpLedgerNav } from "../constants/icons";
/**
* Nav Logo Component.
* Shows symbol on desktop/mobile, full logotype on tablet.
* On desktop hover, the "XRP LEDGER" text animates out to the right.
*/
export function NavLogo() {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return (
<BdsLink href="/" className="bds-navbar__logo" aria-label={translate("XRP Ledger Home")} variant="inline">
<img
src={xrpSymbolBlack}
alt={translate("XRP Ledger")}
className="bds-navbar__logo-symbol"
/>
<img
src={xrpLedgerNav}
alt=""
className="bds-navbar__logo-text"
/>
<img
src={xrpLogotypeBlack}
alt={translate("XRP Ledger")}
className="bds-navbar__logo-full"
/>
</BdsLink>
);
}

View File

@@ -1,5 +0,0 @@
// Re-export navbar components
export { AlertBanner } from './AlertBanner';
export { NavLogo } from './NavLogo';
export { NavItems } from './NavItems';

View File

@@ -1,85 +0,0 @@
// Navbar icon imports
// Main navbar icons
export { default as xrpSymbolBlack } from "../../../../static/img/navbar/xrp-symbol-black.svg";
export { default as xrpLogotypeBlack } from "../../../../static/img/navbar/xrp-logotype-black.svg";
export { default as xrpLedgerNav } from "../../../../static/img/navbar/xrp-ledger-nav.svg";
export { default as searchIcon } from "../../../../static/img/navbar/search-icon.svg";
export { default as modeToggleIcon } from "../../../../static/img/navbar/mode-toggle.svg";
export { default as globeIcon } from "../../../../static/img/navbar/globe-icon.svg";
export { default as chevronDown } from "../../../../static/img/navbar/chevron-down.svg";
export { default as hamburgerIcon } from "../../../../static/img/navbar/hamburger-icon.svg";
export { default as arrowUpRight } from "../../../../static/img/icons/arrow-up-right-custom.svg";
// Wallet icons for submenu
export { default as greenWallet } from "../../../../static/img/navbar/green-wallet.svg";
export { default as lilacWallet } from "../../../../static/img/navbar/lilac-wallet.svg";
export { default as yellowWallet } from "../../../../static/img/navbar/yellow-wallet.svg";
export { default as pinkWallet } from "../../../../static/img/navbar/pink-wallet.svg";
export { default as blueWallet } from "../../../../static/img/navbar/blue-wallet.svg";
// Develop submenu icons
export { default as devHomeIcon } from "../../../../static/img/navbar/dev_home.svg";
export { default as learnIcon } from "../../../../static/img/navbar/learn.svg";
export { default as codeSamplesIcon } from "../../../../static/img/navbar/code_samples.svg";
export { default as docsIcon } from "../../../../static/img/navbar/docs.svg";
export { default as clientLibIcon } from "../../../../static/img/navbar/client_lib.svg";
// Use Cases submenu icons
export { default as paymentsIcon } from "../../../../static/img/navbar/payments.svg";
export { default as tokenizationIcon } from "../../../../static/img/navbar/tokenization.svg";
export { default as creditIcon } from "../../../../static/img/navbar/credit.svg";
export { default as tradingIcon } from "../../../../static/img/navbar/trading.svg";
// Community submenu icons
export { default as communityIcon } from "../../../../static/img/navbar/community.svg";
// Network submenu icons
export { default as insightsIcon } from "../../../../static/img/navbar/insights.svg";
export { default as resourcesIcon } from "../../../../static/img/navbar/resources.svg";
// Network submenu pattern images
export { default as resourcesPurplePattern } from "../../../../static/img/navbar/resources-purple.svg";
export { default as insightsGreenPattern } from "../../../../static/img/navbar/insights-green.svg";
export { default as darkInsightsGreenPattern } from "../../../../static/img/navbar/dark-insights-green.svg";
export { default as darkLilacPattern } from "../../../../static/img/navbar/dark-lilac.svg";
// Wallet icon mapping for dynamic icon lookup
import greenWallet from "../../../../static/img/navbar/green-wallet.svg";
import lilacWallet from "../../../../static/img/navbar/lilac-wallet.svg";
import yellowWallet from "../../../../static/img/navbar/yellow-wallet.svg";
import pinkWallet from "../../../../static/img/navbar/pink-wallet.svg";
import blueWallet from "../../../../static/img/navbar/blue-wallet.svg";
import devHomeIcon from "../../../../static/img/navbar/dev_home.svg";
import learnIcon from "../../../../static/img/navbar/learn.svg";
import codeSamplesIcon from "../../../../static/img/navbar/code_samples.svg";
import docsIcon from "../../../../static/img/navbar/docs.svg";
import clientLibIcon from "../../../../static/img/navbar/client_lib.svg";
import paymentsIcon from "../../../../static/img/navbar/payments.svg";
import tokenizationIcon from "../../../../static/img/navbar/tokenization.svg";
import creditIcon from "../../../../static/img/navbar/credit.svg";
import tradingIcon from "../../../../static/img/navbar/trading.svg";
import communityIcon from "../../../../static/img/navbar/community.svg";
import insightsIcon from "../../../../static/img/navbar/insights.svg";
import resourcesIcon from "../../../../static/img/navbar/resources.svg";
export const walletIcons: Record<string, string> = {
green: greenWallet,
lilac: lilacWallet,
yellow: yellowWallet,
pink: pinkWallet,
blue: blueWallet,
dev_home: devHomeIcon,
learn: learnIcon,
code_samples: codeSamplesIcon,
docs: docsIcon,
client_lib: clientLibIcon,
payments: paymentsIcon,
tokenization: tokenizationIcon,
credit: creditIcon,
trading: tradingIcon,
community: communityIcon,
insights: insightsIcon,
resources: resourcesIcon,
};

View File

@@ -1,4 +0,0 @@
// Re-export all constants
export * from './icons';
export * from './navigation';

View File

@@ -1,166 +0,0 @@
import type { NavItem, SubmenuItemBase, SubmenuItemWithChildren, SubmenuItem, NetworkSubmenuSection } from '../types';
// Alert Banner Configuration
export const alertBanner = {
show: false,
message: "APEX 2025",
button: "REGISTER",
link: "https://www.xrpledgerapex.com/?utm_source=xrplwebsite&utm_medium=direct&utm_campaign=xrpl-event-ho-xrplapex-glb-2025-q1_xrplwebsite_ari_arp_bf_rsvp&utm_content=cta_btn_english_pencilbanner"
};
// Main navigation items
export const navItems: NavItem[] = [
{ label: "Develop", labelTranslationKey: "navbar.develop", href: "/docs", hasSubmenu: true },
{ label: "Use Cases", labelTranslationKey: "navbar.usecases", href: "/about/uses", hasSubmenu: true },
{ label: "Community", labelTranslationKey: "navbar.community", href: "/community", hasSubmenu: true },
{ label: "Network", labelTranslationKey: "navbar.network", href: "/docs/concepts/networks-and-servers", hasSubmenu: true },
];
// Develop submenu data structure
export const developSubmenuData: {
left: SubmenuItemBase[];
right: SubmenuItemWithChildren[];
} = {
left: [
{ label: "Developer's Home", href: "/docs", icon: "dev_home" },
{ label: "Learn", href: "/docs/tutorials", icon: "learn" },
{ label: "Code Samples", href: "/_code-samples", icon: "code_samples" },
],
right: [
{
label: "Docs",
href: "/docs",
icon: "docs",
children: [
{ label: "API Reference", href: "/docs/references" },
{ label: "Tutorials", href: "/docs/tutorials" },
{ label: "Concepts", href: "/docs/concepts" },
{ label: "Infrastructure", href: "/docs/infrastructure" },
],
},
{
label: "Client Libraries",
href: "/docs/references/client-libraries",
icon: "client_lib",
children: [
{ label: "JavaScript", href: "/docs/references/xrpljs" },
{ label: "Python", href: "/docs/references/xrpl-py" },
{ label: "PHP", href: "/docs/references/xrpl-php" },
{ label: "Go", href: "/docs/references/xrpl-go" },
],
},
],
};
// Use Cases submenu data structure
export const useCasesSubmenuData: {
left: SubmenuItemWithChildren[];
right: SubmenuItemWithChildren[];
} = {
left: [
{
label: "Payments",
href: "/about/uses/payments",
icon: "payments",
children: [
{ label: "Direct XRP Payments", href: "/about/uses/direct-xrp-payments" },
{ label: "Cross-currency Payments", href: "/about/uses/cross-currency-payments" },
{ label: "Escrow", href: "/about/uses/escrow" },
{ label: "Checks", href: "/about/uses/checks" },
],
},
{
label: "Tokenization",
href: "/about/uses/tokenization",
icon: "tokenization",
children: [
{ label: "Stablecoin", href: "/about/uses/stablecoin" },
{ label: "NFT", href: "/about/uses/nft" },
],
},
],
right: [
{
label: "Credit",
href: "/about/uses/credit",
icon: "credit",
children: [
{ label: "Lending", href: "/about/uses/lending" },
{ label: "Collateralization", href: "/about/uses/collateralization" },
{ label: "Sustainability", href: "/about/uses/sustainability" },
],
},
{
label: "Trading",
href: "/about/uses/trading",
icon: "trading",
children: [
{ label: "DEX", href: "/about/uses/dex" },
{ label: "Permissioned Trading", href: "/about/uses/permissioned-trading" },
{ label: "AMM", href: "/about/uses/amm" },
],
},
],
};
// Community submenu data structure
export const communitySubmenuData: {
left: SubmenuItem[];
right: SubmenuItem[];
} = {
left: [
{
label: "Community",
href: "/community",
icon: "community",
children: [
{ label: "Events", href: "/community/events" },
{ label: "News", href: "/blog", active: true },
{ label: "Blog", href: "/blog" },
{ label: "Marketplace", href: "/community/marketplace" },
{ label: "Partner Connect", href: "/community/partner-connect" },
],
},
{ label: "Funding", href: "/community/developer-funding", icon: "code_samples" },
],
right: [
{
label: "Contribute",
href: "/resources/contribute-documentation",
icon: "client_lib",
children: [
{ label: "Ecosystem Map", href: "/community/ecosystem-map" },
{ label: "Bug Bounty", href: "/community/bug-bounty" },
{ label: "Research", href: "/community/research" },
],
},
{ label: "Creators", href: "/community/ambassadors", icon: "learn" },
],
};
// Network submenu data
export const networkSubmenuData: NetworkSubmenuSection[] = [
{
label: "Resources",
href: "/docs/concepts/networks-and-servers",
icon: "resources",
children: [
{ label: "Validators", href: "/docs/concepts/networks-and-servers/validators" },
{ label: "Governance", href: "/docs/concepts/networks-and-servers/governance", active: true },
{ label: "XRPL Roadmap", href: "/docs/concepts/networks-and-servers/xrpl-roadmap" },
],
patternColor: 'lilac',
},
{
label: "Insights",
href: "/docs/concepts/networks-and-servers/insights",
icon: "insights",
children: [
{ label: "Explorer", href: "https://livenet.xrpl.org" },
{ label: "Data Dashboard", href: "/docs/concepts/networks-and-servers/data-dashboard" },
{ label: "Amendment Voting Status", href: "/docs/concepts/networks-and-servers/amendments" },
],
patternColor: 'green',
},
];

View File

@@ -1,19 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { IconButton } from "./IconButton";
import { hamburgerIcon } from "../constants/icons";
interface HamburgerButtonProps {
onClick?: () => void;
}
/**
* Hamburger Menu Button Component.
* Mobile-only button that opens the mobile menu.
*/
export function HamburgerButton({ onClick }: HamburgerButtonProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return <IconButton icon={hamburgerIcon} ariaLabel={translate("Open menu")} className="bds-navbar__hamburger" onClick={onClick} />;
}

View File

@@ -1,28 +0,0 @@
interface IconButtonProps {
/** The icon image source */
icon: string;
/** Accessible label for the button */
ariaLabel: string;
/** Optional click handler */
onClick?: () => void;
/** CSS class name for styling variants */
className?: string;
}
/**
* Unified Icon Button component.
* Used for search, mode toggle, hamburger menu, and other icon-only buttons.
*/
export function IconButton({ icon, ariaLabel, onClick, className = "bds-navbar__icon" }: IconButtonProps) {
return (
<button
type="button"
className={className}
aria-label={ariaLabel}
onClick={onClick}
>
<img src={icon} alt="" />
</button>
);
}

View File

@@ -1,85 +0,0 @@
import * as React from "react";
import { useLanguagePicker, useThemeHooks } from "@redocly/theme/core/hooks";
interface LanguageDropdownProps {
isOpen: boolean;
onClose: () => void;
}
/**
* Language Dropdown Component.
* Displays available language options in a dropdown menu.
* Based on Figma design: dark background with rounded corners.
*/
export function LanguageDropdown({ isOpen, onClose }: LanguageDropdownProps) {
const { currentLocale, locales, setLocale } = useLanguagePicker();
const { useL10n, useTranslate } = useThemeHooks();
const { changeLanguage } = useL10n();
const { translate } = useTranslate();
const dropdownRef = React.useRef<HTMLDivElement>(null);
// Handle clicking outside to close
React.useEffect(() => {
if (!isOpen) return;
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
// Check if click was on the language pill (parent trigger)
const target = event.target as HTMLElement;
if (!target.closest('.bds-navbar__lang-pill')) {
onClose();
}
}
};
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleEscape);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('keydown', handleEscape);
};
}, [isOpen, onClose]);
if (!isOpen || locales.length < 2) {
return null;
}
const handleLanguageSelect = (localeCode: string) => {
setLocale(localeCode);
changeLanguage(localeCode);
onClose();
};
return (
<div
ref={dropdownRef}
className="bds-lang-dropdown"
role="menu"
aria-label={translate("Language selection")}
>
{locales.map((locale) => {
const isActive = locale.code === currentLocale?.code;
return (
<button
key={locale.code}
type="button"
role="menuitem"
className={`bds-lang-dropdown__item ${isActive ? 'bds-lang-dropdown__item--active' : ''}`}
onClick={() => handleLanguageSelect(locale.code)}
aria-current={isActive ? 'true' : undefined}
>
{locale.name || locale.code}
</button>
);
})}
</div>
);
}

View File

@@ -1,53 +0,0 @@
import { useLanguagePicker, useThemeHooks } from "@redocly/theme/core/hooks";
import { globeIcon, chevronDown } from "../constants/icons";
interface LanguagePillProps {
onClick?: () => void;
isOpen?: boolean;
}
/**
* Get short display name for a locale code.
* e.g., "en-US" -> "En", "ja" -> "日本語"
*/
function getLocaleShortName(code: string | undefined): string {
if (!code) return "En";
// Map locale codes to short display names
const shortNames: Record<string, string> = {
"en-US": "En",
"en": "En",
"ja": "日本語",
};
return shortNames[code] || code.substring(0, 2).toUpperCase();
}
/**
* Language Pill Button Component.
* Shows current language and opens language selector.
*/
export function LanguagePill({ onClick, isOpen }: LanguagePillProps) {
const { currentLocale } = useLanguagePicker();
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const displayName = getLocaleShortName(currentLocale?.code);
return (
<button
type="button"
className={`bds-navbar__lang-pill ${isOpen ? 'bds-navbar__lang-pill--open' : ''}`}
aria-label={translate("Select language")}
aria-expanded={isOpen}
aria-haspopup="menu"
onClick={onClick}
>
<img src={globeIcon} alt="" className="bds-navbar__lang-pill-icon" />
<span className="bds-navbar__lang-pill-text">
<span>{displayName}</span>
<img src={chevronDown} alt="" className="bds-navbar__lang-pill-chevron" />
</span>
</button>
);
}

View File

@@ -1,19 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { IconButton } from "./IconButton";
import { modeToggleIcon } from "../constants/icons";
interface ModeToggleButtonProps {
onClick?: () => void;
}
/**
* Mode Toggle Button Component.
* Icon button that toggles between light and dark mode.
*/
export function ModeToggleButton({ onClick }: ModeToggleButtonProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return <IconButton icon={modeToggleIcon} ariaLabel={translate("Toggle color mode")} onClick={onClick} />;
}

View File

@@ -1,46 +0,0 @@
import * as React from "react";
import { SearchButton } from "./SearchButton";
import { ModeToggleButton } from "./ModeToggleButton";
import { LanguagePill } from "./LanguagePill";
import { LanguageDropdown } from "./LanguageDropdown";
interface NavControlsProps {
onSearch?: () => void;
}
/**
* Nav Controls Component.
* Right side of the navbar containing search, mode toggle, and language selector.
*/
export function NavControls({ onSearch }: NavControlsProps) {
const [isLanguageDropdownOpen, setIsLanguageDropdownOpen] = React.useState(false);
const handleModeToggle = () => {
// Toggle between light and dark theme
const newTheme = document.documentElement.classList.contains("dark") ? "light" : "dark";
window.localStorage.setItem("user-prefers-color", newTheme);
document.body.style.transition = "background-color .2s ease";
document.documentElement.classList.remove("dark", "light");
document.documentElement.classList.add(newTheme);
};
const handleLanguageClick = () => {
setIsLanguageDropdownOpen(!isLanguageDropdownOpen);
};
const handleLanguageDropdownClose = () => {
setIsLanguageDropdownOpen(false);
};
return (
<div className="bds-navbar__controls">
<SearchButton onClick={onSearch} />
<ModeToggleButton onClick={handleModeToggle} />
<div className="bds-navbar__lang-wrapper">
<LanguagePill onClick={handleLanguageClick} isOpen={isLanguageDropdownOpen} />
<LanguageDropdown isOpen={isLanguageDropdownOpen} onClose={handleLanguageDropdownClose} />
</div>
</div>
);
}

View File

@@ -1,19 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { IconButton } from "./IconButton";
import { searchIcon } from "../constants/icons";
interface SearchButtonProps {
onClick?: () => void;
}
/**
* Search Button Component.
* Icon button that triggers the search modal.
*/
export function SearchButton({ onClick }: SearchButtonProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return <IconButton icon={searchIcon} ariaLabel={translate("Search")} onClick={onClick} />;
}

View File

@@ -1,11 +0,0 @@
// Unified base component
export { IconButton } from './IconButton';
// Specific button implementations (use IconButton internally)
export { NavControls } from './NavControls';
export { SearchButton } from './SearchButton';
export { ModeToggleButton } from './ModeToggleButton';
export { LanguagePill } from './LanguagePill';
export { LanguageDropdown } from './LanguageDropdown';
export { HamburgerButton } from './HamburgerButton';

View File

@@ -1,30 +0,0 @@
import * as React from "react";
interface ChevronIconProps {
expanded: boolean;
}
/**
* Chevron Icon Component for Mobile Accordion
*/
export function ChevronIcon({ expanded }: ChevronIconProps) {
return (
<svg
className={`bds-mobile-menu__chevron ${expanded ? 'bds-mobile-menu__chevron--expanded' : ''}`}
width="13"
height="8"
viewBox="0 0 13 8"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 1L6.5 6.5L12 1"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,14 +0,0 @@
import * as React from "react";
/**
* Close Icon Component for Mobile Menu
*/
export function CloseIcon() {
return (
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="7" y1="7" x2="21" y2="21" stroke="#141414" strokeWidth="2" strokeLinecap="round" />
<line x1="21" y1="7" x2="7" y2="21" stroke="#141414" strokeWidth="2" strokeLinecap="round" />
</svg>
);
}

View File

@@ -1,56 +0,0 @@
interface ArrowIconProps {
className?: string;
color?: string;
/**
* When true, the horizontal line has a class for CSS animation (parent links).
* When false, the full arrow is shown without animation class (child links).
*/
animated?: boolean;
}
/**
* Unified Arrow Icon component.
* - For parent links (animated=true): horizontal line animates away on hover
* - For child links (animated=false): full static arrow
*/
export function ArrowIcon({ className, color = "currentColor", animated = true }: ArrowIconProps) {
return (
<svg
className={className}
width="15"
height="14"
viewBox="0 0 26 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
{/* Chevron part */}
<path
d="M14.0019 1.00191L24.0015 11.0015L14.0019 21.001"
stroke={color}
strokeWidth="2"
strokeMiterlimit="10"
strokeLinecap="round"
/>
{/* Horizontal line */}
<path
d="M23.999 10.999H0"
stroke={color}
strokeWidth="2"
strokeMiterlimit="10"
strokeLinecap="round"
className={animated ? "arrow-horizontal" : undefined}
/>
</svg>
);
}
// Backwards-compatible aliases
export const SubmenuArrow = (props: Omit<ArrowIconProps, 'animated'>) => (
<ArrowIcon {...props} animated={true} />
);
export const SubmenuChildArrow = (props: Omit<ArrowIconProps, 'animated'>) => (
<ArrowIcon {...props} animated={false} />
);

View File

@@ -1,6 +0,0 @@
// Re-export all icon components
// Unified arrow icon with backwards-compatible aliases
export { ArrowIcon, SubmenuArrow, SubmenuChildArrow } from './SubmenuArrow';
export { CloseIcon } from './CloseIcon';
export { ChevronIcon } from './ChevronIcon';

View File

@@ -1,13 +0,0 @@
// Main Navbar component
export { Navbar } from './Navbar';
// Re-export types
export * from './types';
// Re-export components for advanced usage
export * from './components';
export * from './controls';
export * from './submenus';
export * from './mobile-menu';
export * from './icons';

View File

@@ -1,215 +0,0 @@
import * as React from "react";
import { useThemeHooks, useLanguagePicker } from "@redocly/theme/core/hooks";
import { BdsLink } from "../../../../shared/components/Link/Link";
import { CloseIcon, ChevronIcon } from "../icons";
import { xrpSymbolBlack, globeIcon, chevronDown, modeToggleIcon, searchIcon } from "../constants/icons";
import { navItems } from "../constants/navigation";
import { MobileMenuContent, type MobileMenuKey } from "./MobileMenuContent";
interface MobileMenuProps {
isOpen: boolean;
onClose: () => void;
onSearch?: () => void;
}
/**
* Mobile Menu Component.
* Full-screen slide-out menu for mobile devices.
*/
export function MobileMenu({ isOpen, onClose, onSearch }: MobileMenuProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const [expandedItem, setExpandedItem] = React.useState<string | null>("Develop");
// Handle body scroll lock
React.useEffect(() => {
if (isOpen) {
document.body.classList.add('bds-mobile-menu-open');
} else {
document.body.classList.remove('bds-mobile-menu-open');
}
return () => {
document.body.classList.remove('bds-mobile-menu-open');
};
}, [isOpen]);
const toggleAccordion = (item: string) => {
setExpandedItem(expandedItem === item ? null : item);
};
const handleSearch = () => {
if (onSearch) {
onSearch();
}
onClose();
};
const handleModeToggle = () => {
const newTheme = document.documentElement.classList.contains("dark") ? "light" : "dark";
window.localStorage.setItem("user-prefers-color", newTheme);
document.body.style.transition = "background-color .2s ease";
document.documentElement.classList.remove("dark", "light");
document.documentElement.classList.add(newTheme);
};
const renderAccordionContent = (label: string) => {
// All nav items with submenus use the unified MobileMenuContent
const validKeys: MobileMenuKey[] = ['Develop', 'Use Cases', 'Community', 'Network'];
if (validKeys.includes(label as MobileMenuKey)) {
return <MobileMenuContent menuKey={label as MobileMenuKey} />;
}
return null;
};
return (
<div className={`bds-mobile-menu ${isOpen ? 'bds-mobile-menu--open' : ''}`}>
{/* Header */}
<div className="bds-mobile-menu__header">
<BdsLink href="/" className="bds-navbar__logo" aria-label={translate("XRP Ledger Home")} onClick={onClose} variant="inline">
<img src={xrpSymbolBlack} alt={translate("XRP Ledger")} className="bds-navbar__logo-symbol" style={{ width: 33, height: 28 }} />
</BdsLink>
<button
type="button"
className="bds-mobile-menu__close"
aria-label={translate("Close menu")}
onClick={onClose}
>
<CloseIcon />
</button>
</div>
{/* Content */}
<div className="bds-mobile-menu__content">
<div className="bds-mobile-menu__accordion">
{navItems.map((item) => (
<React.Fragment key={item.label}>
<button
type="button"
className="bds-mobile-menu__accordion-header"
onClick={() => item.hasSubmenu ? toggleAccordion(item.label) : null}
aria-expanded={expandedItem === item.label}
>
{item.hasSubmenu ? (
<>
<span>{translate(item.labelTranslationKey, item.label)}</span>
<ChevronIcon expanded={expandedItem === item.label} />
</>
) : (
<BdsLink
href={item.href}
onClick={onClose}
variant="inline"
style={{
display: 'flex',
width: '100%',
justifyContent: 'space-between',
alignItems: 'center',
color: 'inherit',
textDecoration: 'none'
}}
>
<span>{translate(item.labelTranslationKey, item.label)}</span>
<ChevronIcon expanded={false} />
</BdsLink>
)}
</button>
{item.hasSubmenu && (
<div
className={`bds-mobile-menu__accordion-content ${
expandedItem === item.label ? 'bds-mobile-menu__accordion-content--expanded' : ''
}`}
>
{renderAccordionContent(item.label)}
</div>
)}
</React.Fragment>
))}
</div>
</div>
{/* Footer */}
<MobileMenuFooter
onModeToggle={handleModeToggle}
onSearch={handleSearch}
/>
</div>
);
}
interface MobileMenuFooterProps {
onModeToggle: () => void;
onSearch: () => void;
}
/**
* Get short display name for a locale code.
*/
function getLocaleShortName(code: string | undefined): string {
if (!code) return "En";
const shortNames: Record<string, string> = {
"en-US": "En",
"en": "En",
"ja": "日本語",
};
return shortNames[code] || code.substring(0, 2).toUpperCase();
}
function MobileMenuFooter({ onModeToggle, onSearch }: MobileMenuFooterProps) {
const { currentLocale, locales, setLocale } = useLanguagePicker();
const { useL10n, useTranslate } = useThemeHooks();
const { changeLanguage } = useL10n();
const { translate } = useTranslate();
const [isLangOpen, setIsLangOpen] = React.useState(false);
const displayName = getLocaleShortName(currentLocale?.code);
const handleLanguageSelect = (localeCode: string) => {
setLocale(localeCode);
changeLanguage(localeCode);
setIsLangOpen(false);
};
return (
<div className="bds-mobile-menu__footer">
<div className="bds-mobile-menu__lang-wrapper">
<button
type="button"
className={`bds-mobile-menu__lang-pill ${isLangOpen ? 'bds-mobile-menu__lang-pill--open' : ''}`}
aria-label={translate("Select language")}
aria-expanded={isLangOpen}
onClick={() => setIsLangOpen(!isLangOpen)}
>
<img src={globeIcon} alt="" className="bds-mobile-menu__lang-pill-icon" />
<span className="bds-mobile-menu__lang-pill-text">
<span>{displayName}</span>
<img src={chevronDown} alt="" className="bds-mobile-menu__lang-pill-chevron" />
</span>
</button>
{isLangOpen && locales.length >= 2 && (
<div className="bds-lang-dropdown bds-lang-dropdown--mobile" role="menu">
{locales.map((locale) => {
const isActive = locale.code === currentLocale?.code;
return (
<button
key={locale.code}
type="button"
role="menuitem"
className={`bds-lang-dropdown__item ${isActive ? 'bds-lang-dropdown__item--active' : ''}`}
onClick={() => handleLanguageSelect(locale.code)}
>
{locale.name || locale.code}
</button>
);
})}
</div>
)}
</div>
<button type="button" className="bds-mobile-menu__footer-icon" aria-label={translate("Toggle color mode")} onClick={onModeToggle}>
<img src={modeToggleIcon} alt="" />
</button>
<button type="button" className="bds-mobile-menu__footer-icon" aria-label={translate("Search")} onClick={onSearch}>
<img src={searchIcon} alt="" />
</button>
</div>
);
}

View File

@@ -1,47 +0,0 @@
import { MobileMenuSection } from "./MobileMenuSection";
import { developSubmenuData, useCasesSubmenuData, communitySubmenuData, networkSubmenuData } from "../constants/navigation";
import type { SubmenuItem, SubmenuItemWithChildren, NetworkSubmenuSection } from "../types";
export type MobileMenuKey = 'Develop' | 'Use Cases' | 'Community' | 'Network';
interface MobileMenuContentProps {
/** Which menu section to render */
menuKey: MobileMenuKey;
}
/** Get flattened menu items based on menu key */
function getMenuItems(menuKey: MobileMenuKey): (SubmenuItem | SubmenuItemWithChildren | NetworkSubmenuSection)[] {
switch (menuKey) {
case 'Develop':
return [...developSubmenuData.left, ...developSubmenuData.right];
case 'Use Cases':
return [...useCasesSubmenuData.left, ...useCasesSubmenuData.right];
case 'Community':
return [...communitySubmenuData.left, ...communitySubmenuData.right];
case 'Network':
return networkSubmenuData;
}
}
/**
* Unified Mobile Menu Content component.
* Renders accordion content for any menu section.
*/
export function MobileMenuContent({ menuKey }: MobileMenuContentProps) {
const items = getMenuItems(menuKey);
return (
<div className="bds-mobile-menu__tier-list">
{items.map((item) => (
<MobileMenuSection key={item.label} item={item} />
))}
</div>
);
}
// Backwards-compatible named exports
export const MobileMenuDevelopContent = () => <MobileMenuContent menuKey="Develop" />;
export const MobileMenuUseCasesContent = () => <MobileMenuContent menuKey="Use Cases" />;
export const MobileMenuCommunityContent = () => <MobileMenuContent menuKey="Community" />;
export const MobileMenuNetworkContent = () => <MobileMenuContent menuKey="Network" />;

View File

@@ -1,54 +0,0 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { SubmenuArrow, SubmenuChildArrow } from "../icons";
import { walletIcons } from "../constants/icons";
import { hasChildren, type SubmenuItem } from "../types";
interface MobileMenuSectionProps {
item: SubmenuItem;
}
/**
* Reusable mobile menu section component.
* Renders a parent link with icon, and optionally child links.
*/
export function MobileMenuSection({ item }: MobileMenuSectionProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const itemHasChildren = hasChildren(item);
return (
<React.Fragment>
<a href={item.href} className="bds-mobile-menu__tier1 bds-mobile-menu__parent-link">
<span className="bds-mobile-menu__icon">
<img src={walletIcons[item.icon]} alt="" />
</span>
<span className="bds-mobile-menu__link bds-mobile-menu__link--bold">
{translate(item.label)}
<span className="bds-mobile-menu__arrow">
<SubmenuArrow />
</span>
</span>
</a>
{itemHasChildren && (
<div className="bds-mobile-menu__tier2">
{item.children.map((child) => (
<a
key={child.label}
href={child.href}
className="bds-mobile-menu__sublink"
target={child.href.startsWith('http') ? '_blank' : undefined}
rel={child.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{translate(child.label)}
<span className="bds-mobile-menu__sublink-arrow">
<SubmenuChildArrow />
</span>
</a>
))}
</div>
)}
</React.Fragment>
);
}

View File

@@ -1,16 +0,0 @@
// Main mobile menu component
export { MobileMenu } from './MobileMenu';
// Unified content component with backwards-compatible aliases
export {
MobileMenuContent,
MobileMenuDevelopContent,
MobileMenuUseCasesContent,
MobileMenuCommunityContent,
MobileMenuNetworkContent,
type MobileMenuKey
} from './MobileMenuContent';
// Reusable section component
export { MobileMenuSection } from './MobileMenuSection';

View File

@@ -1,16 +0,0 @@
import { Submenu } from "./Submenu";
interface CommunitySubmenuProps {
isActive: boolean;
isClosing: boolean;
onClose?: () => void;
}
/**
* Desktop Community Submenu Component.
* Wrapper for unified Submenu component with 'community' variant.
*/
export function CommunitySubmenu({ isActive, isClosing, onClose }: CommunitySubmenuProps) {
return <Submenu variant="community" isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}

View File

@@ -1,16 +0,0 @@
import { Submenu } from "./Submenu";
interface DevelopSubmenuProps {
isActive: boolean;
isClosing: boolean;
onClose?: () => void;
}
/**
* Desktop Develop Submenu Component.
* Wrapper for unified Submenu component with 'develop' variant.
*/
export function DevelopSubmenu({ isActive, isClosing, onClose }: DevelopSubmenuProps) {
return <Submenu variant="develop" isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}

View File

@@ -1,16 +0,0 @@
import { Submenu } from "./Submenu";
interface NetworkSubmenuProps {
isActive: boolean;
isClosing: boolean;
onClose?: () => void;
}
/**
* Desktop Network Submenu Component.
* Wrapper for unified Submenu component with 'network' variant.
*/
export function NetworkSubmenu({ isActive, isClosing, onClose }: NetworkSubmenuProps) {
return <Submenu variant="network" isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}

View File

@@ -1,290 +0,0 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { SubmenuSection } from "./SubmenuSection";
import { ArrowIcon } from "../icons";
import { walletIcons, resourcesPurplePattern, insightsGreenPattern, darkInsightsGreenPattern, darkLilacPattern } from "../constants/icons";
import { developSubmenuData, useCasesSubmenuData, communitySubmenuData, networkSubmenuData } from "../constants/navigation";
import type { SubmenuItem, SubmenuItemWithChildren, NetworkSubmenuSection } from "../types";
export type SubmenuVariant = 'develop' | 'use-cases' | 'community' | 'network';
interface SubmenuProps {
/** Which submenu variant to render */
variant: SubmenuVariant;
/** Whether this submenu is currently active (visible) */
isActive: boolean;
/** Whether this submenu is in closing animation */
isClosing: boolean;
/** Callback when submenu should close (e.g., Escape key) */
onClose?: () => void;
}
/** Get submenu data based on variant */
function getSubmenuData(variant: SubmenuVariant) {
switch (variant) {
case 'develop': return developSubmenuData;
case 'use-cases': return useCasesSubmenuData;
case 'community': return communitySubmenuData;
case 'network': return networkSubmenuData;
}
}
/** Get CSS modifier class for variant */
function getVariantClass(variant: SubmenuVariant): string {
if (variant === 'develop') return '';
return `bds-submenu--${variant}`;
}
/**
* Get all focusable elements within a container
*/
function getFocusableElements(container: HTMLElement | null): HTMLElement[] {
if (!container) return [];
return Array.from(
container.querySelectorAll<HTMLElement>('a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])')
);
}
/**
* Find the next nav item button after the current expanded one
*/
function getNextNavItem(): HTMLElement | null {
const navItems = document.querySelectorAll<HTMLElement>('.bds-navbar__item');
const currentIndex = Array.from(navItems).findIndex(item =>
item.getAttribute('aria-expanded') === 'true'
);
if (currentIndex >= 0 && currentIndex < navItems.length - 1) {
return navItems[currentIndex + 1];
}
// If at the last nav item, go to the first control button (search, etc.)
const controls = document.querySelector<HTMLElement>('.bds-navbar__controls button, .bds-navbar__controls a');
return controls;
}
/**
* Unified Submenu component.
* Handles all submenu variants (develop, use-cases, community, network).
* ARIA compliant with full keyboard navigation support.
*/
export function Submenu({ variant, isActive, isClosing, onClose }: SubmenuProps) {
const submenuRef = React.useRef<HTMLDivElement>(null);
// Handle keyboard events for accessibility
const handleKeyDown = React.useCallback((event: KeyboardEvent) => {
if (!isActive) return;
if (event.key === 'Escape') {
event.preventDefault();
onClose?.();
// Return focus to the trigger button
const triggerButton = document.querySelector<HTMLButtonElement>(
`.bds-navbar__item[aria-expanded="true"]`
);
triggerButton?.focus();
}
// Handle Tab at end of submenu - move to next nav item
if (event.key === 'Tab' && !event.shiftKey) {
const activeSubmenu = document.querySelector<HTMLElement>('.bds-submenu--active');
const focusableElements = getFocusableElements(activeSubmenu);
const lastFocusable = focusableElements[focusableElements.length - 1];
if (document.activeElement === lastFocusable) {
event.preventDefault();
onClose?.();
const nextItem = getNextNavItem();
nextItem?.focus();
}
}
// Handle Shift+Tab at start of submenu - move back to trigger button
if (event.key === 'Tab' && event.shiftKey) {
const activeSubmenu = document.querySelector<HTMLElement>('.bds-submenu--active');
const focusableElements = getFocusableElements(activeSubmenu);
const firstFocusable = focusableElements[0];
if (document.activeElement === firstFocusable) {
event.preventDefault();
onClose?.();
// Return focus to the trigger button
const triggerButton = document.querySelector<HTMLButtonElement>(
`.bds-navbar__item[aria-expanded="true"]`
);
triggerButton?.focus();
}
}
}, [isActive, onClose]);
// Add keyboard event listener when submenu is active
React.useEffect(() => {
if (isActive) {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}
}, [isActive, handleKeyDown]);
// Network submenu needs special handling for theme-aware patterns
if (variant === 'network') {
return <NetworkSubmenuContent isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}
const data = getSubmenuData(variant);
const classNames = [
'bds-submenu',
getVariantClass(variant),
isActive ? 'bds-submenu--active' : '',
isClosing ? 'bds-submenu--closing' : '',
].filter(Boolean).join(' ');
// Standard two-column layout
const leftItems = 'left' in data ? data.left : [];
const rightItems = 'right' in data ? data.right : [];
return (
<div
ref={submenuRef}
className={classNames}
role="menu"
aria-hidden={!isActive}
>
<div className="bds-submenu__left">
{leftItems.map((item: SubmenuItem | SubmenuItemWithChildren) => (
<SubmenuSection key={item.label} item={item} />
))}
</div>
<div className="bds-submenu__right">
{rightItems.map((item: SubmenuItem | SubmenuItemWithChildren) => (
<SubmenuSection key={item.label} item={item} />
))}
</div>
</div>
);
}
/** Network submenu with theme-aware pattern images */
function NetworkSubmenuContent({ isActive, isClosing, onClose }: { isActive: boolean; isClosing: boolean; onClose?: () => void }) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
// Start with null to indicate "not yet determined" - avoids hydration mismatch
// by ensuring server and client both render the same initial state
const [isDarkMode, setIsDarkMode] = React.useState<boolean | null>(null);
// Handle keyboard events for accessibility
const handleKeyDown = React.useCallback((event: KeyboardEvent) => {
if (!isActive) return;
if (event.key === 'Escape') {
event.preventDefault();
onClose?.();
// Return focus to the trigger button
const triggerButton = document.querySelector<HTMLButtonElement>(
`.bds-navbar__item[aria-expanded="true"]`
);
triggerButton?.focus();
}
// Handle Tab at end of submenu - move to next nav item
if (event.key === 'Tab' && !event.shiftKey) {
const activeSubmenu = document.querySelector<HTMLElement>('.bds-submenu--active');
const focusableElements = getFocusableElements(activeSubmenu);
const lastFocusable = focusableElements[focusableElements.length - 1];
if (document.activeElement === lastFocusable) {
event.preventDefault();
onClose?.();
const nextItem = getNextNavItem();
nextItem?.focus();
}
}
// Handle Shift+Tab at start of submenu - move back to trigger button
if (event.key === 'Tab' && event.shiftKey) {
const activeSubmenu = document.querySelector<HTMLElement>('.bds-submenu--active');
const focusableElements = getFocusableElements(activeSubmenu);
const firstFocusable = focusableElements[0];
if (document.activeElement === firstFocusable) {
event.preventDefault();
onClose?.();
// Return focus to the trigger button
const triggerButton = document.querySelector<HTMLButtonElement>(
`.bds-navbar__item[aria-expanded="true"]`
);
triggerButton?.focus();
}
}
}, [isActive, onClose]);
// Add keyboard event listener when submenu is active
React.useEffect(() => {
if (isActive) {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}
}, [isActive, handleKeyDown]);
React.useEffect(() => {
const checkTheme = () => {
setIsDarkMode(document.documentElement.classList.contains('dark'));
};
checkTheme();
const observer = new MutationObserver(checkTheme);
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
return () => observer.disconnect();
}, []);
// Default to light mode patterns until client-side detection runs
const patternImages = React.useMemo(() => ({
lilac: isDarkMode === true ? darkLilacPattern : resourcesPurplePattern,
green: isDarkMode === true ? darkInsightsGreenPattern : insightsGreenPattern,
}), [isDarkMode]);
const classNames = [
'bds-submenu',
'bds-submenu--network',
isActive ? 'bds-submenu--active' : '',
isClosing ? 'bds-submenu--closing' : '',
].filter(Boolean).join(' ');
return (
<div className={classNames} role="menu" aria-hidden={!isActive}>
{networkSubmenuData.map((section: NetworkSubmenuSection) => (
<div key={section.label} className="bds-submenu__section">
<a href={section.href} className="bds-submenu__tier1 bds-submenu__parent-link">
<span className="bds-submenu__icon">
<img src={walletIcons[section.icon]} alt="" />
</span>
<span className="bds-submenu__link bds-submenu__link--bold">
{translate(section.label)}
<span className="bds-submenu__arrow">
<ArrowIcon animated />
</span>
</span>
</a>
<div className="bds-submenu__network-content">
<div className="bds-submenu__tier2">
{section.children.map((child) => (
<a
key={child.label}
href={child.href}
className="bds-submenu__sublink"
target={child.href.startsWith('http') ? '_blank' : undefined}
rel={child.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{translate(child.label)}
<span className="bds-submenu__sublink-arrow">
<ArrowIcon animated={false} />
</span>
</a>
))}
</div>
<div className="bds-submenu__pattern-container">
<img src={patternImages[section.patternColor]} alt="" className="bds-submenu__pattern" />
</div>
</div>
</div>
))}
</div>
);
}

View File

@@ -1,70 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { ArrowIcon } from "../icons";
import { walletIcons } from "../constants/icons";
import { hasChildren, type SubmenuItem, type SubmenuItemWithChildren, type SubmenuItemBase } from "../types";
interface SubmenuSectionProps {
/** The menu item data */
item: SubmenuItem | SubmenuItemWithChildren | SubmenuItemBase;
/** Whether to render children links (default: true) */
showChildren?: boolean;
}
/**
* Unified submenu section component.
* Renders a parent link with icon, and optionally child links if the item has them.
*
* Usage:
* - For items that may or may not have children: <SubmenuSection item={item} />
* - For parent-only rendering: <SubmenuSection item={item} showChildren={false} />
*/
export function SubmenuSection({ item, showChildren = true }: SubmenuSectionProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const itemHasChildren = hasChildren(item as SubmenuItem);
const shouldShowChildren = showChildren && itemHasChildren;
return (
<div className="bds-submenu__section">
<a href={item.href} className="bds-submenu__tier1 bds-submenu__parent-link">
<span className="bds-submenu__icon">
<img src={walletIcons[item.icon]} alt="" />
</span>
<span className="bds-submenu__link bds-submenu__link--bold">
{translate(item.label)}
<span className="bds-submenu__arrow">
<ArrowIcon animated />
</span>
</span>
</a>
{shouldShowChildren && (
<div className="bds-submenu__tier2">
{(item as SubmenuItemWithChildren).children.map((child) => (
<a
key={child.label}
href={child.href}
className="bds-submenu__sublink"
target={child.href.startsWith('http') ? '_blank' : undefined}
rel={child.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{translate(child.label)}
<span className="bds-submenu__sublink-arrow">
<ArrowIcon animated={false} />
</span>
</a>
))}
</div>
)}
</div>
);
}
// Backwards-compatible aliases (all use the unified SubmenuSection)
export const SubmenuParentOnly = ({ item }: { item: SubmenuItemBase }) => (
<SubmenuSection item={item} showChildren={false} />
);
export const SubmenuWithChildren = ({ item }: { item: SubmenuItemWithChildren }) => (
<SubmenuSection item={item} showChildren={true} />
);

View File

@@ -1,16 +0,0 @@
import { Submenu } from "./Submenu";
interface UseCasesSubmenuProps {
isActive: boolean;
isClosing: boolean;
onClose?: () => void;
}
/**
* Desktop Use Cases Submenu Component.
* Wrapper for unified Submenu component with 'use-cases' variant.
*/
export function UseCasesSubmenu({ isActive, isClosing, onClose }: UseCasesSubmenuProps) {
return <Submenu variant="use-cases" isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}

View File

@@ -1,12 +0,0 @@
// Unified submenu component
export { Submenu, type SubmenuVariant } from './Submenu';
// Variant-specific wrappers (all use Submenu internally)
export { DevelopSubmenu } from './DevelopSubmenu';
export { UseCasesSubmenu } from './UseCasesSubmenu';
export { CommunitySubmenu } from './CommunitySubmenu';
export { NetworkSubmenu } from './NetworkSubmenu';
// Reusable submenu section component
export { SubmenuSection, SubmenuParentOnly, SubmenuWithChildren } from './SubmenuSection';

View File

@@ -1,42 +0,0 @@
// Types for submenu data structures
export interface SubmenuChild {
label: string;
href: string;
active?: boolean;
}
export interface SubmenuItemBase {
label: string;
href: string;
icon: string;
}
export interface SubmenuItemWithChildren extends SubmenuItemBase {
children: SubmenuChild[];
}
export type SubmenuItem = SubmenuItemBase | SubmenuItemWithChildren;
// Network submenu section with decorative images
export interface NetworkSubmenuSection {
label: string;
href: string;
icon: string;
children: SubmenuChild[];
patternColor: 'lilac' | 'green';
}
// Nav item type
export interface NavItem {
label: string;
labelTranslationKey: string;
href: string;
hasSubmenu: boolean;
}
// Type guard to check if item has children
export function hasChildren(item: SubmenuItem): item is SubmenuItemWithChildren {
return 'children' in item && Array.isArray((item as SubmenuItemWithChildren).children);
}

View File

@@ -25,7 +25,7 @@ export function XRPLCard(props: XRPLCardProps) {
<p className="card-text">{props.body}</p>
)}
</div>
{/* <div className="card-footer">&nbsp;</div> */}
<div className="card-footer">&nbsp;</div>
</Link>
)
}

View File

@@ -1,178 +0,0 @@
# Color System Migration Summary
**Date:** October 21, 2025
**Source:** [XRPL.org Design Tokens - Figma](https://figma.com/design/zRyhXG4hRP3Lk3B6Owr3eo/XRPL.org-Design-Tokens)
## Migration Strategy: Clean Migration
The old 10-level color scale (100-1000) has been completely migrated to a new 5-level scale (100-500). All references in the codebase have been updated, and backward compatibility aliases have been removed for a clean implementation.
**Mapping Applied:**
```
Old System → New System
100 → 100 (lightest)
200 → 100
300 → 200
400 → 200
500 → 300 (mid-tone, default)
600 → 300
700 → 400
800 → 400
900 → 500 (darkest)
1000 → 500
```
**Migration Approach:**
1. All color usages (600-1000) were found and replaced with their new equivalents (300-500)
2. Backward compatibility aliases were removed from `_colors.scss`
3. Only 100-500 design tokens remain for each color family
## Color Families Updated
### Primary Colors
#### Gray (Neutral) ⏸️ NOT UPDATED
- **Status:** Original values retained - design tokens not ready
- **Current values:** #FCFCFD, #F5F5F7, #E0E0E1, #C1C1C2, #A2A2A4, #838386, #454549, #343437, #232325, #111112
- **Note:** Gray/Neutral design tokens will be migrated in a future update
#### Green ✅
- **New Design Tokens:** #EAFCF1, #70EE97, #21E46B, #0DAA3E, #078139
- **Variables:** `$green-100` through `$green-500` only
- **Migrated:** 14 file references updated
- **Special:** `$apex-2023-green` (#00FF76) retained
#### Lilac (Primary) ✅ *replaces blue-purple*
- **New Design Tokens:** #F2EDFF, #D9CAFF, #C0A7FF, #7649E3, #5429A1
- **Variables:** `$lilac-100` through `$lilac-500` only
- **Legacy aliases:** `$blue-purple-100` through `$blue-purple-500` map to lilac (600-900 removed)
- **Migrated:** 16 file references updated
- **Note:** This is a new color name in the design system
### Secondary Colors
#### Red ✅ *NEW*
- **New Design Tokens:** #FDECE7, #F27A66, #F0643A, #DA4518, #A22514
- **Variables:** `$red-100` through `$red-500` only
- **Note:** This is a completely new color family added to the design system
#### Pink ✅ *replaces magenta*
- **New Design Tokens:** #FDF1F4, #F2B5C3, #F18DA5, #FF577F, #DC466F
- **Variables:** `$pink-100` through `$pink-500` only
- **Legacy aliases:** `$magenta-100` through `$magenta-500` map to pink (600-900 removed)
- **Migrated:** 7 file references updated
#### Blue ✅
- **New Design Tokens:** #EDF4FF, #93BFF1, #428CFF, #0179E7, #0A4DC0
- **Variables:** `$blue-100` through `$blue-500` only
- **Migrated:** 8 file references updated
- **Special:** `$accent-blue-90` (#001133) retained
#### Yellow ✅
- **New Design Tokens:** #F3F1EB, #E6F1A7, #DBF15E, #E1DB26, #D4C02D
- **Variables:** `$yellow-100` through `$yellow-500` only
- **Migrated:** 11 file references updated
## Colors Retained (No Design Token Replacement)
### Orange
- **Status:** Legacy values retained
- **Values:** #FFEEE5, #FFCCB2, #FFAA80, #FF884B, #FF6719, #E54D00, #B23C00, #802B00, #4C1A00
- **Reason:** No corresponding design token in new system
### Red-purple
- **Status:** Legacy values retained
- **Values:** #FBE5FF, #F2B2FF, #EA80FF, #E24CFF, #D919FF, #C000E5, #9500B2, #6B0080, #40004C
- **Reason:** No corresponding design token in new system
### Special Event Colors
- `$apex-2023-green: #00FF76`
- `$token-2049-purple: #410bb9`
- `$accent-blue-90: #001133`
## Bootstrap & Component Colors
All Bootstrap theme variables remain functional:
- `$primary``$purple` (now `$lilac-400`)
- `$secondary``$gray-200`
- `$success``$green-500`
- `$info``$blue-500`
- `$warning``$yellow-500`
- `$danger``$magenta-500` (now `$pink-500`)
## Breaking Changes
**Removed Variables:**
- All color variables from 600-1000 have been removed for: Green, Blue, Lilac, Pink, Red, Yellow
- `$blue-purple-600` through `$blue-purple-900` removed (use 100-500)
- `$magenta-600` through `$magenta-900` removed (use 100-500)
**No Impact:**
- All usages in the codebase have been updated
- Legacy color name aliases maintained (100-500 only):
- `$blue-purple-100` through `$blue-purple-500` → maps to `$lilac-*`
- `$magenta-100` through `$magenta-500` → maps to `$pink-*`
## Color Name Changes
| Old Name | New Name | Reason |
|----------|----------|--------|
| `blue-purple-*` | `lilac-*` | Design system rebranding |
| `magenta-*` | `pink-*` | Design system rebranding |
| N/A | `red-*` | New color family added |
## Usage Recommendations
### Current Best Practices
Use the new 5-level design tokens (100-500):
```scss
// Primary colors
color: $gray-300; // Gray (not yet migrated - still uses old values)
color: $green-300; // Default green
color: $lilac-400; // Primary purple
// Secondary colors
color: $red-300; // Default red
color: $pink-300; // Default pink
color: $blue-300; // Default blue
color: $yellow-300; // Default yellow
```
### Legacy Aliases Still Available
```scss
// These legacy names work (100-500 only)
color: $blue-purple-400; // Same as $lilac-400
color: $magenta-300; // Same as $pink-300
```
## Files Modified
- `styles/_colors.scss` - Complete color system update
## Validation Status
✅ All SCSS variables resolve correctly
✅ No linter errors
✅ Bootstrap theme colors functional
✅ All old color references (600-1000) removed from codebase
✅ Special event colors preserved
⏸️ Gray/Neutral colors - pending future update
## Migration Statistics
**Files Updated:** 11 SCSS files
- `styles/_colors.scss` - Color definitions cleaned up
- `styles/light/_light-theme.scss` - 11 color references updated
- `styles/_status-labels.scss` - 39 color references updated
- `styles/_diagrams.scss` - 6 color references updated
- `styles/_code-tabs.scss` - 2 color references updated
- `styles/_content.scss` - 1 color reference updated
- `styles/_buttons.scss` - 7 color references updated
- `styles/_pages.scss` - 3 color references updated
- `styles/_blog.scss` - 2 color references updated
- `styles/_feedback.scss` - 2 color references updated
- `styles/_rpc-tool.scss` - 1 color reference updated
- `styles/_landings.scss` - 1 color reference updated
**Total Color References Updated:** 75+ instances

View File

@@ -1,154 +0,0 @@
# CSS Optimization - Implementation Summary
## ✅ Successfully Completed
The CSS build pipeline has been modernized with industry-standard optimization tools, resulting in significant performance improvements.
## Results
### Bundle Size Improvements
\`\`\`
=== CSS Bundle Comparison ===
Master (Bootstrap 4):
Uncompressed: 405.09 KB
Gzipped: 63.44 KB
This Branch BEFORE Optimization (Bootstrap 5):
Uncompressed: 486.64 KB
Gzipped: 71.14 KB
This Branch AFTER Optimization (Bootstrap 5 + PurgeCSS):
Uncompressed: 280.92 KB ✅ 42% smaller
Gzipped: 43.32 KB ✅ 39% smaller (network transfer)
\`\`\`
### Key Improvements
| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| **Network Transfer (Gzipped)** | 71.14 KB | 43.32 KB | **39% smaller** |
| **Uncompressed Size** | 486.64 KB | 280.92 KB | **42% smaller** |
| **CSS Selectors** | 5,423 | 2,681 | **51% fewer** |
| **DevTools Filter Performance** | ~60 seconds | <1 second | **98% faster** |
### Real-World Impact
- **Page Load:** 40% faster CSS download on 3G connections
- **Developer Experience:** DevTools CSS filtering is now instant (<1s vs 60s)
- **Bandwidth Savings:** ~28 KB less per page load
- **Maintainability:** Modern tooling with source maps in development
## What Was Implemented
### 1. Modern Build Pipeline
- **Upgraded Sass** from 1.26.10 (2020) 1.93.2 (latest)
- **Added PostCSS** with optimization plugins:
- **PurgeCSS** - Removes unused CSS selectors
- **Autoprefixer** - Browser compatibility
- **cssnano** - Advanced minification
### 2. Build Scripts
```json
{
"scripts": {
"build-css": "Production build with full optimization",
"build-css-dev": "Development build with source maps",
"build-css-watch": "Watch mode for continuous compilation",
"analyze-css": "Bundle analysis tool"
}
}
```
### 3. PurgeCSS Configuration
- Scans all `.tsx`, `.md`, `.yaml`, `.html` files for class names
- Intelligent safelist for dynamically-added classes
- Preserves Bootstrap JS components, CodeMirror, custom tools
- Only runs in production (dev builds are fast)
### 4. CSS Analysis Tool
Created `scripts/analyze-css.js` to monitor:
- Bundle size and composition
- Bootstrap component usage
- Optimization opportunities
- Before/after comparisons
## Files Created/Modified
### New Files
- `postcss.config.cjs` - PostCSS and PurgeCSS configuration
- `scripts/analyze-css.js` - CSS bundle analysis tool
- `CSS-OPTIMIZATION.md` - Comprehensive optimization guide
- `CSS-OPTIMIZATION-SUMMARY.md` - This summary
### Modified Files
- `package.json` - Updated dependencies and build scripts
- `styles/README.md` - Updated build documentation
### Configuration Files
All configuration files include extensive inline documentation explaining decisions and patterns.
## Usage
### For Production
```bash
npm run build-css # Full optimization
npm run analyze-css # Check results
```
### For Development
```bash
npm run build-css:dev # Fast build with source maps
npm run build-css:watch # Auto-rebuild on changes
```
## Backward Compatibility
**No breaking changes** - All existing styles are preserved
Visual appearance is identical
All Bootstrap components still work
Dynamic classes are safelisted
## Documentation
- **`styles/README.md`** - Build process and troubleshooting
- **`CSS-OPTIMIZATION.md`** - Detailed implementation guide
- **`postcss.config.cjs`** - Inline configuration documentation
## Maintenance
### Adding New Styles
1. Create `_component.scss` file
2. Import in `xrpl.scss`
3. Add dynamic classes to safelist if needed
4. Test: `npm run build-css:dev` and `npm run build-css`
5. Analyze: `npm run analyze-css`
### Troubleshooting Missing Styles
If styles are missing in production:
1. Check if class is added dynamically
2. Add pattern to safelist in `postcss.config.cjs`
3. Rebuild with `npm run build-css`
## Next Steps (Optional Future Optimizations)
1. **Code Splitting** - Separate vendor CSS from custom styles
2. **Critical CSS** - Extract above-the-fold styles
3. **Bootstrap Customization** - Import only needed components
4. **CSS Modules** - Component-scoped styles for React pages
## Conclusion
The CSS optimization is complete and working perfectly. The bundle size has been reduced by 42% (uncompressed) and 39% (gzipped), resulting in faster page loads and dramatically improved developer experience.
**Status: ✅ Ready for Production**
---
*Last Updated: October 2025*

View File

@@ -1,381 +0,0 @@
# CSS Optimization Guide
## Overview
This document describes the CSS optimization implementation for the XRPL Dev Portal, including the rationale, implementation details, performance improvements, and maintenance guidelines.
## The Problem
### Before Optimization
The dev portal was serving a **486 KB** minified CSS bundle that included:
- **Entire Bootstrap 5.3.8 framework** (~200+ KB)
- Thousands of unused CSS selectors
- No tree-shaking or dead code elimination
- All styles loaded on every page, regardless of usage
- **1-minute lag** in Chrome DevTools when filtering CSS
#### Impact
- **Developer Experience:** DevTools filter took 60+ seconds to respond
- **Page Performance:** 486 KB CSS downloaded on every page load
- **Build Process:** Outdated Sass 1.26.10 (from 2020)
- **Debugging:** No source maps, even in development
### Analysis Results
Initial analysis showed:
```
Bundle Size: 486.64 KB
Total Selectors: 5,423
Unique Selectors: 4,678
Bootstrap Component Usage:
- Pagination: 998 usages
- Cards: 428 usages
- Grid System: 253 usages
- ...but also...
- Toast: 8 usages
- Spinner: 8 usages
- Accordion: 0 usages (unused!)
```
## The Solution
### Modern Build Pipeline
Implemented a three-stage optimization pipeline:
```
SCSS → Sass Compiler → PostCSS → Optimized CSS
├─ PurgeCSS (removes unused)
├─ Autoprefixer (adds vendor prefixes)
└─ cssnano (minifies)
```
### Key Technologies
1. **Sass (latest)** - Modern SCSS compilation with better performance
2. **PostCSS** - Industry-standard CSS processing
3. **PurgeCSS** - Intelligent unused CSS removal
4. **Autoprefixer** - Browser compatibility
5. **cssnano** - Advanced minification
## Implementation
### 1. Dependency Upgrades
```json
{
"devDependencies": {
"sass": "^1.93.2", // was 1.26.10
"postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"@fullhuman/postcss-purgecss": "^7.0.2",
"autoprefixer": "^10.4.21",
"cssnano": "^7.1.1"
}
}
```
### 2. Build Scripts
Created separate development and production builds:
```json
{
"scripts": {
"build-css": "Production build with full optimization",
"build-css:dev": "Development build with source maps",
"build-css:watch": "Watch mode for continuous compilation",
"analyze-css": "node scripts/analyze-css.js"
}
}
```
**Production Build:**
- ✅ Full PurgeCSS optimization
- ✅ Minified and compressed
- ✅ Autoprefixed
- ❌ No source maps
**Development Build:**
- ✅ Source maps for debugging
- ✅ Autoprefixed
- ❌ No PurgeCSS (faster builds)
- ❌ Not minified (readable)
### 3. PurgeCSS Configuration
Created `postcss.config.cjs` with intelligent safelist:
```javascript
// Content paths - scan these for class names
content: [
'./**/*.tsx',
'./**/*.md',
'./**/*.yaml',
'./**/*.html',
'./static/js/**/*.js',
]
// Safelist - preserve these classes
safelist: {
standard: [
'html', 'body', 'light', 'dark',
/^show$/, /^active$/, /^disabled$/,
],
deep: [
/dropdown-menu/, /modal-backdrop/,
/cm-/, /CodeMirror/, // Third-party
/rpc-tool/, /websocket/, // Custom components
],
}
```
**Safelist Strategy:**
- **Standard:** State classes added by JavaScript
- **Deep:** Component patterns (keeps parent and children)
- **Greedy:** Attribute-based matching
### 4. Analysis Tool
Created `scripts/analyze-css.js` to track optimization:
- Bundle size metrics
- Selector counts
- Bootstrap component usage
- Custom pattern detection
- Optimization recommendations
## Results
### Performance Improvements
| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| **Bundle Size (Uncompressed)** | 486.64 KB | 280.92 KB | **42% smaller** |
| **Bundle Size (Gzipped)** | 71.14 KB | 43.32 KB | **39% smaller** |
| **Total Selectors** | 5,423 | 2,681 | **51% fewer** |
| **Unique Selectors** | 4,678 | 2,167 | **54% fewer** |
| **DevTools Filter** | ~60 seconds | <1 second | **98% faster** |
| **Download Time (3G)** | ~2.0s | ~1.2s | **40% faster** |
**Note:** Gzipped size is what actually gets transmitted over the network, representing the real-world bandwidth savings.
### Bootstrap Component Optimization
| Component | Before | After | Reduction |
|-----------|--------|-------|-----------|
| Pagination | 998 | 831 | 17% |
| Cards | 428 | 306 | 29% |
| Grid System | 253 | 94 | 63% |
| Badge | 253 | 0 | 100% (unused) |
| Navbar | 171 | 78 | 54% |
| Buttons | 145 | 77 | 47% |
| Forms | 179 | 70 | 61% |
### Developer Experience
**Before:**
```
Build time: 5-10 seconds
DevTools CSS filter: 60 seconds
Debugging: No source maps
```
**After:**
```
Production build: 8-12 seconds
Development build: 3-5 seconds (no PurgeCSS)
DevTools CSS filter: <1 second
Debugging: Source maps in dev mode
```
## Maintenance
### Adding New Styles
When adding new component styles:
1. **Create the SCSS file:**
```scss
// styles/_my-component.scss
.my-component {
// styles here
}
```
2. **Import in xrpl.scss:**
```scss
@import "_my-component.scss";
```
3. **If using dynamic classes, update safelist:**
```javascript
// postcss.config.cjs
deep: [
/my-component/, // Keeps all .my-component-* classes
]
```
4. **Test both builds:**
```bash
npm run build-css:dev # Test development build
npm run build-css # Test production build
npm run analyze-css # Check bundle size impact
```
### Troubleshooting Missing Styles
If styles are missing after a production build:
1. **Identify the missing class:**
```bash
# Search for class usage in codebase
grep -r "missing-class" .
```
2. **Check if it's dynamically added:**
- Bootstrap JavaScript components
- React state-based classes
- Third-party library classes
3. **Add to PurgeCSS safelist:**
```javascript
// postcss.config.cjs
safelist: {
deep: [
/missing-class/, // Preserve this pattern
],
}
```
4. **Rebuild and verify:**
```bash
npm run build-css
npm run analyze-css
```
### Monitoring Bundle Size
Run the analysis tool regularly:
```bash
npm run analyze-css
```
**Watch for:**
- Bundle size > 350 KB (indicates regression)
- Components with 0 usages (can be removed from Bootstrap import)
- Significant selector count increases
### Future Optimizations
Potential next steps for further optimization:
1. **Code Splitting**
- Split vendor CSS (Bootstrap) from custom styles
- Lazy-load page-specific styles
- Critical CSS extraction
2. **Bootstrap Customization**
- Import only needed Bootstrap components
- Remove unused variables and mixins
- Custom Bootstrap build
3. **Component-Level CSS**
- CSS Modules for page components
- CSS-in-JS for dynamic styles
- Scoped styles per route
4. **Advanced Compression**
- Brotli compression (88% ratio vs 76% gzip)
- CSS splitting by media queries
- HTTP/2 server push for critical CSS
## Migration Notes
### Breaking Changes
**None** - This optimization is backward-compatible. All existing classes and styles are preserved.
### Testing Checklist
When testing the optimization:
- [ ] Homepage loads correctly
- [ ] Documentation pages display properly
- [ ] Blog posts render correctly
- [ ] Dev tools (RPC tool, WebSocket tool) function
- [ ] Navigation menus work
- [ ] Dropdowns and modals open correctly
- [ ] Forms are styled properly
- [ ] Code syntax highlighting works
- [ ] Print styles work
- [ ] Light/dark theme switching works
### Rollback Procedure
If issues are found:
1. **Temporarily revert to old build:**
```bash
# In package.json, change build-css to:
"build-css": "sass --load-path styles/scss styles/xrpl.scss ./static/css/devportal2024-v1.css --style compressed --no-source-map"
```
2. **Rebuild:**
```bash
npm run build-css
```
3. **Report the issue** with:
- Missing class names
- Page where issue appears
- Expected vs actual behavior
## Resources
### Documentation
- [PurgeCSS Documentation](https://purgecss.com/)
- [PostCSS Documentation](https://postcss.org/)
- [Sass Documentation](https://sass-lang.com/)
- [Bootstrap Customization](https://getbootstrap.com/docs/5.3/customize/sass/)
### Tools
- `npm run build-css` - Production build
- `npm run build-css:dev` - Development build
- `npm run build-css:watch` - Watch mode
- `npm run analyze-css` - Bundle analysis
### Files
- `styles/README.md` - Build process documentation
- `postcss.config.cjs` - PostCSS and PurgeCSS configuration
- `scripts/analyze-css.js` - Bundle analysis tool
- `package.json` - Build scripts
## Conclusion
This optimization reduces the CSS bundle by 42% (486 KB 281 KB), dramatically improving both developer experience and end-user performance. The implementation uses industry-standard tools and maintains full backward compatibility while providing a foundation for future optimizations.
**Key Takeaways:**
- 42% smaller uncompressed CSS bundle (486 KB 281 KB)
- 39% smaller gzipped bundle (71 KB 43 KB network transfer)
- 98% faster DevTools filtering (60s <1s)
- Modern build tooling (Sass + PostCSS + PurgeCSS)
- Source maps in development mode
- Backward compatible - no breaking changes
- Well documented and maintainable
---
*Last updated: October 2025*
*Contributors: CSS Optimization Initiative*

View File

@@ -0,0 +1,7 @@
{
"id": 2,
"command": "account_objects",
"account": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"ledger_index": "validated",
"type": "escrow"
}

View File

@@ -0,0 +1,7 @@
{
"id": 5,
"command": "account_objects",
"account": "rfztBskAVszuS3s5Kq7zDS74QtHrw893fm",
"ledger_index": "validated",
"type": "escrow"
}

View File

@@ -0,0 +1,26 @@
{
"id": 2,
"status": "success",
"type": "response",
"result": {
"account": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"account_objects": [
{
"Account": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"Amount": "10000",
"CancelAfter": 559913895,
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"FinishAfter": 559892324,
"Flags": 0,
"LedgerEntryType": "Escrow",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "4756C22BBB7FC23D9081FDB180806939D6FEBC967BE0EC2DB95B166AF9C086E9",
"PreviousTxnLgrSeq": 2764813,
"index": "7243A9750FA4BE3E63F75F6DACFD79AD6B6C76947F6BDC46CD0F52DBEEF64C89"
}
],
"ledger_hash": "82F24FFA72AED16F467BBE79D387E92FDA39F29038B26E79464CDEDFB506E366",
"ledger_index": 2764826,
"validated": true
}
}

View File

@@ -0,0 +1,60 @@
{
"id": 5,
"result": {
"account": "rfztBskAVszuS3s5Kq7zDS74QtHrw893fm",
"account_objects": [{
"Account": "rafD3taonqdnVpaxCCT6sjnScZUeFGf1JG",
"Amount": "250",
"Destination": "rfztBskAVszuS3s5Kq7zDS74QtHrw893fm",
"DestinationNode": "0000000000000000",
"FinishAfter": 570672000,
"Flags": 0,
"LedgerEntryType": "Escrow",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "A0951691DF3BCBEEB3108F2229A702D078BBBF848268BC601E59B68A2E390AAC",
"PreviousTxnLgrSeq": 4602906,
"index": "2BF3226ACCA8FF7ACB7201F20A701F51D8666A2FA2FBFBE6A05C9161F9228A18"
}, {
"Account": "rfztBskAVszuS3s5Kq7zDS74QtHrw893fm",
"Amount": "250",
"Destination": "r9gyNNzhMtfwZara61u3ycfMLdkTpKJZHX",
"DestinationNode": "0000000000000000",
"FinishAfter": 570672000,
"Flags": 0,
"LedgerEntryType": "Escrow",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "463D5A3CF09F4890B8471027F80414B3B438E6907425B71DC324D7118E90A107",
"PreviousTxnLgrSeq": 4603003,
"index": "35462CDC28AD830B29D101E8307AF5B6BFBC262F1BDCCA7EB45D1CA3F8B44F53"
}, {
"Account": "r9gyNNzhMtfwZara61u3ycfMLdkTpKJZHX",
"Amount": "250",
"Destination": "rfztBskAVszuS3s5Kq7zDS74QtHrw893fm",
"DestinationNode": "0000000000000000",
"FinishAfter": 570672000,
"Flags": 0,
"LedgerEntryType": "Escrow",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "08C9B20AC9EB191238038A108CC4CBBC0243672484B466FB42DED0A7DF6A31A1",
"PreviousTxnLgrSeq": 4602954,
"index": "A7B0983A1B53D92278E21499064A4F8BBE08CB8D14DB6BBBA8F688AB1D3FDA45"
}, {
"Account": "rfztBskAVszuS3s5Kq7zDS74QtHrw893fm",
"Amount": "250",
"Destination": "rafD3taonqdnVpaxCCT6sjnScZUeFGf1JG",
"DestinationNode": "0000000000000000",
"FinishAfter": 570672000,
"Flags": 0,
"LedgerEntryType": "Escrow",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "F4778F528AB3CB945BDB88036EF9FE6C0E899F1629D9E51129E3B93CD488395A",
"PreviousTxnLgrSeq": 4602977,
"index": "F99A4DDADDDF623908C9A048170AB107AFF78684AB8F3110E9F00BBBC606ABD2"
}],
"ledger_hash": "1D4850035F175CA6F1CD5CE3B53C01AA83E4F086C13085E4FBC1EEFCCB345A9B",
"ledger_index": 4603176,
"validated": true
},
"status": "success",
"type": "response"
}

View File

@@ -0,0 +1,5 @@
{
"id": 4,
"command": "ledger",
"ledger_index": "validated"
}

View File

@@ -0,0 +1,5 @@
{
"id": 4,
"command": "ledger",
"ledger_index": "validated"
}

View File

@@ -0,0 +1,19 @@
{
"id": 1,
"status": "success",
"type": "response",
"result": {
"ledger": {
# ... (trimmed) ...
"close_time": 560302643,
"close_time_human": "2017-Oct-02 23:37:23",
"close_time_resolution": 10,
# ... (trimmed) ...
},
"ledger_hash": "668F0647A6F3CC277496245DBBE9BD2E3B8E70E7AA824E97EF3237FE7E1EE3F2",
"ledger_index": 2906341,
"validated": true
}
}

View File

@@ -0,0 +1,28 @@
{
"id": 4,
"status": "success",
"type": "response",
"result": {
"ledger": {
"accepted": true,
"account_hash": "3B5A8FF5334F94F4D3D09F236F9D1B4C028FCAE30948ACC986D461DDEE1D886B",
"close_flags": 0,
"close_time": 557256670,
"close_time_human": "2017-Aug-28 17:31:10",
"close_time_resolution": 10,
"closed": true,
"hash": "A999223A80174A7CB39D766B625C9E476F24AD2F15860A712CD029EE5ED1C320",
"ledger_hash": "A999223A80174A7CB39D766B625C9E476F24AD2F15860A712CD029EE5ED1C320",
"ledger_index": "1908253",
"parent_close_time": 557256663,
"parent_hash": "6A70C5336ACFDA05760D827776079F7A544D2361CFD5B21BD55A92AA20477A61",
"seqNum": "1908253",
"totalCoins": "99997280690562728",
"total_coins": "99997280690562728",
"transaction_hash": "49A51DFB1CAB2F134D93D5D1C5FF55A15B12DA36DAF9F5862B17C47EE966647D"
},
"ledger_hash": "A999223A80174A7CB39D766B625C9E476F24AD2F15860A712CD029EE5ED1C320",
"ledger_index": 1908253,
"validated": true
}
}

View File

@@ -0,0 +1,11 @@
{
"id": 5,
"command": "submit",
"secret": "s████████████████████████████",
"tx_json": {
"Account": "rhgdnc82FwHFUKXp9ZcpgwXWRAxKf5Buqp",
"TransactionType": "EscrowCancel",
"Owner": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"OfferSequence": 1
}
}

View File

@@ -0,0 +1,13 @@
{
"id": 1,
"command": "submit",
"secret": "s████████████████████████████",
"tx_json": {
"Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB",
"TransactionType": "EscrowCreate",
"Amount": "100000",
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Condition": "A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD810120",
"CancelAfter": 556927412
}
}

View File

@@ -0,0 +1,12 @@
{
"id": 2,
"command": "submit",
"secret": "s████████████████████████████",
"tx_json": {
"Account": "rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG",
"TransactionType": "EscrowCreate",
"Amount": "10000",
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"FinishAfter": 557020800
}
}

View File

@@ -0,0 +1,14 @@
{
"id": 4,
"command": "submit",
"secret": "s████████████████████████████",
"tx_json": {
"Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB",
"TransactionType": "EscrowFinish",
"Owner": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB",
"OfferSequence": 5,
"Condition": "A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD810120",
"Fulfillment": "A0228020D280D1A02BAD0D2EBC0528B92E9BF37AC3E2530832C2C52620307135156F1048",
"Fee": "500"
}
}

View File

@@ -0,0 +1,11 @@
{
"id": 5,
"command": "submit",
"secret": "s████████████████████████████",
"tx_json": {
"Account": "rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG",
"TransactionType": "EscrowFinish",
"Owner": "rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG",
"OfferSequence": 1
}
}

View File

@@ -0,0 +1,23 @@
{
"id": 5,
"status": "success",
"type": "response",
"result": {
"engine_result": "tesSUCCESS",
"engine_result_code": 0,
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
"tx_blob": "1200042280000000240000000320190000000168400000000000000A7321027FB1CF34395F18901CD294F77752EEE25277C6E87A224FC7388AA7EF872DB43D74473045022100AC45749FC4291F7811B2D8AC01CA04FEE38910CB7216FB0C5C0AEBC9C0A95F4302203F213C71C00136A0ADC670EFE350874BCB2E559AC02059CEEDFB846685948F2B81142866B7B47574C8A70D5E71FFB95FFDB18951427B82144E87970CD3EA984CF48B1AA6AB6C77DC4AB059FC",
"tx_json": {
"Account": "rhgdnc82FwHFUKXp9ZcpgwXWRAxKf5Buqp",
"Fee": "10",
"Flags": 2147483648,
"OfferSequence": 1,
"Owner": "r3wN3v2vTUkr5qd6daqDc2xE4LSysdVjkT",
"Sequence": 3,
"SigningPubKey": "027FB1CF34395F18901CD294F77752EEE25277C6E87A224FC7388AA7EF872DB43D",
"TransactionType": "EscrowCancel",
"TxnSignature": "3045022100AC45749FC4291F7811B2D8AC01CA04FEE38910CB7216FB0C5C0AEBC9C0A95F4302203F213C71C00136A0ADC670EFE350874BCB2E559AC02059CEEDFB846685948F2B",
"hash": "65F36C5514153D94F0ADE5CE747061A5E70B73B56B4C66DA5040D99CAF252831"
}
}
}

View File

@@ -0,0 +1,25 @@
{
"id": 1,
"status": "success",
"type": "response",
"result": {
"engine_result": "tesSUCCESS",
"engine_result_code": 0,
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
"tx_blob": "120001228000000024000000052024213209B46140000000000186A068400000000000000A732103E498E35BC1E109C5995BD3AB0A6D4FFAB61B853C8F6010FABC5DABAF34478B61744730450221008AC8BDC2151D5EF956197F0E6E89A4F49DEADC1AC38367870E444B1EA8D88D97022075E31427B455DFF87F0F22B849C71FC3987A91C19D63B6D0242E808347EC8A8F701127A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD81012081149A2AA667E1517EFA8A6B552AB2EDB859A99F26B283144B4E9C06F24296074F7BC48F92A97916C6DC5EA9",
"tx_json": {
"Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB",
"Amount": "100000",
"CancelAfter": 556927412,
"Condition": "A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD810120",
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "10",
"Flags": 2147483648,
"Sequence": 5,
"SigningPubKey": "03E498E35BC1E109C5995BD3AB0A6D4FFAB61B853C8F6010FABC5DABAF34478B61",
"TransactionType": "EscrowCreate",
"TxnSignature": "30450221008AC8BDC2151D5EF956197F0E6E89A4F49DEADC1AC38367870E444B1EA8D88D97022075E31427B455DFF87F0F22B849C71FC3987A91C19D63B6D0242E808347EC8A8F",
"hash": "E22D1F6EB006CAD35E0DBD3B4F3748427055E4C143EBE95AA6603823AEEAD324"
}
}
}

View File

@@ -0,0 +1,24 @@
{
"id": 2,
"status": "success",
"type": "response",
"result": {
"engine_result": "tesSUCCESS",
"engine_result_code": 0,
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
"tx_blob": "1200012280000000240000000120252133768061400000000000271068400000000000000A732103C3555B7339FFDDB43495A8371A3A87B4C66B67D49D06CB9BA1FDBFEEB57B6E437446304402203C9AA4C21E1A1A7427D41583283E7A513DDBDD967B246CADD3B2705D858A7A8E02201BEA7B923B18910EEB9F306F6DE3B3F53549BBFAD46335B62B4C34A6DCB4A47681143EEB46C355B04EE8D08E8EED00F422895C79EA6A83144B4E9C06F24296074F7BC48F92A97916C6DC5EA9",
"tx_json": {
"Account": "rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG",
"Amount": "10000",
"Destination": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"Fee": "10",
"FinishAfter": 557020800,
"Flags": 2147483648,
"Sequence": 1,
"SigningPubKey": "03C3555B7339FFDDB43495A8371A3A87B4C66B67D49D06CB9BA1FDBFEEB57B6E43",
"TransactionType": "EscrowCreate",
"TxnSignature": "304402203C9AA4C21E1A1A7427D41583283E7A513DDBDD967B246CADD3B2705D858A7A8E02201BEA7B923B18910EEB9F306F6DE3B3F53549BBFAD46335B62B4C34A6DCB4A476",
"hash": "55B2057332F8999208C43BA1E7091B423A16E5ED2736C06300B4076085205263"
}
}
}

View File

@@ -0,0 +1,25 @@
{
"id": 4,
"status": "success",
"type": "response",
"result": {
"engine_result": "tesSUCCESS",
"engine_result_code": 0,
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
"tx_blob": "120002228000000024000000062019000000056840000000000001F4732103E498E35BC1E109C5995BD3AB0A6D4FFAB61B853C8F6010FABC5DABAF34478B617446304402207DE4EA9C8655E75BA01F96345B3F62074313EB42C15D9C4871E30F02202D2BA50220070E52AD308A31AC71E33BA342F31B68D1D1B2A7A3A3ED6E8552CA3DCF14FBB2701024A0228020D280D1A02BAD0D2EBC0528B92E9BF37AC3E2530832C2C52620307135156F1048701127A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD81012081149A2AA667E1517EFA8A6B552AB2EDB859A99F26B282149A2AA667E1517EFA8A6B552AB2EDB859A99F26B2",
"tx_json": {
"Account": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB",
"Condition": "A0258020E24D9E1473D4DF774F6D8E089067282034E4FA7ECACA2AD2E547953B2C113CBD810120",
"Fee": "500",
"Flags": 2147483648,
"Fulfillment": "A0228020D280D1A02BAD0D2EBC0528B92E9BF37AC3E2530832C2C52620307135156F1048",
"OfferSequence": 5,
"Owner": "rEhw9vD98ZrkY4tZPvkZst5H18RysqFdaB",
"Sequence": 6,
"SigningPubKey": "03E498E35BC1E109C5995BD3AB0A6D4FFAB61B853C8F6010FABC5DABAF34478B61",
"TransactionType": "EscrowFinish",
"TxnSignature": "304402207DE4EA9C8655E75BA01F96345B3F62074313EB42C15D9C4871E30F02202D2BA50220070E52AD308A31AC71E33BA342F31B68D1D1B2A7A3A3ED6E8552CA3DCF14FBB2",
"hash": "0E88368CAFC69A722ED829FAE6E2DD3575AE9C192691E60B5ACDF706E219B2BF"
}
}
}

View File

@@ -0,0 +1,23 @@
{
"id": 5,
"status": "success",
"type": "response",
"result": {
"engine_result": "tesSUCCESS",
"engine_result_code": 0,
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
"tx_blob": "1200022280000000240000000220190000000168400000000000000A732103C3555B7339FFDDB43495A8371A3A87B4C66B67D49D06CB9BA1FDBFEEB57B6E4374473045022100923B91BA4FD6450813F5335D71C64BA9EB81304A86859A631F2AD8571424A46502200CCE660D36781B84634C5F23619EB6CFCCF942709F54DCCF27CF6F499AE78C9B81143EEB46C355B04EE8D08E8EED00F422895C79EA6A82143EEB46C355B04EE8D08E8EED00F422895C79EA6A",
"tx_json": {
"Account": "rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG",
"Fee": "10",
"Flags": 2147483648,
"OfferSequence": 1,
"Owner": "rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG",
"Sequence": 2,
"SigningPubKey": "03C3555B7339FFDDB43495A8371A3A87B4C66B67D49D06CB9BA1FDBFEEB57B6E43",
"TransactionType": "EscrowFinish",
"TxnSignature": "3045022100923B91BA4FD6450813F5335D71C64BA9EB81304A86859A631F2AD8571424A46502200CCE660D36781B84634C5F23619EB6CFCCF942709F54DCCF27CF6F499AE78C9B",
"hash": "41856A742B3CAF307E7B4D0B850F302101F0F415B785454F7501E9960A2A1F6B"
}
}
}

Some files were not shown because too many files have changed in this diff Show More