diff --git a/_code-samples/verify-credential/js/README.md b/_code-samples/verify-credential/js/README.md index e447a0ba2de..ab620d3dc30 100644 --- a/_code-samples/verify-credential/js/README.md +++ b/_code-samples/verify-credential/js/README.md @@ -6,27 +6,34 @@ Quick install & usage: ```sh npm install +node ./verify_credential.js ``` -`verify_credential.js` can also be used as a commandline utility. Full usage statement: - -```sh -$ ./verify_credential.js -h - -Usage: verify-credential [options] [issuer] [subject] [credential_type] - -Verify an XRPL credential - -Arguments: - issuer Credential issuer address as base58 (default: - "rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS") - subject Credential subject (holder) address as base58 (default: - "rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA") - credential_type Credential type as string. (default: "my_credential") - -Options: - -b, --binary Use binary (hexadecimal) for credential_type - -n, --network {devnet,testnet,mainnet} Use the specified network for lookup (default: "devnet") - -q, --quiet Don't print log messages - -h, --help display help for command +The output should look something like this: + +```text +Looking up credential... +{ + "command": "ledger_entry", + "credential": { + "subject": "rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA", + "issuer": "rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS", + "credential_type": "6D795F63726564656E7469616C" + }, + "ledger_index": "validated" +} +Found credential: +{ + "CredentialType": "6D795F63726564656E7469616C", + "Flags": 65536, + "Issuer": "rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS", + "IssuerNode": "0", + "LedgerEntryType": "Credential", + "PreviousTxnID": "7D1257779E2D298C07C7E0C73CD446534B143FBD1F13DB268A878E40FD153B9A", + "PreviousTxnLgrSeq": 803275, + "Subject": "rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA", + "SubjectNode": "0", + "index": "9603F0E204A8B1C61823625682EB0ECE98A4ECF22FF46CD4845FA9BFA3606B24" +} +Credential is valid. ``` diff --git a/_code-samples/verify-credential/js/package.json b/_code-samples/verify-credential/js/package.json index 7072c025367..1282784be01 100644 --- a/_code-samples/verify-credential/js/package.json +++ b/_code-samples/verify-credential/js/package.json @@ -1,18 +1,9 @@ { - "name": "issuer_service", - "version": "1.0.0", - "description": "", - "main": "verify_credential.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC", + "name": "verify_credentials", + "version": "2.0.0", + "description": "Sample code showing how to check if a credential on the XRPL exists and is valid.", "type": "module", "dependencies": { - "commander": "^13.1.0", - "winston": "^3.17.0", "xrpl": "^4.2.5" } } diff --git a/_code-samples/verify-credential/js/verify_credential.js b/_code-samples/verify-credential/js/verify_credential.js index cbb251f6c6e..753f32f10f3 100755 --- a/_code-samples/verify-credential/js/verify_credential.js +++ b/_code-samples/verify-credential/js/verify_credential.js @@ -1,184 +1,68 @@ -#!/usr/bin/env node - -import { Command } from "commander"; -import { Client, rippleTimeToISOTime, convertStringToHex } from "xrpl"; -import winston from "winston"; - -// Set up logging -------------------------------------------------------------- -// Use WARNING by default in case verify_credential is called from elsewhere. -const logger = winston.createLogger({ - level: "warn", - transports: [new winston.transports.Console()], - format: winston.format.simple(), -}); - -// Define an error to throw when XRPL lookup fails unexpectedly -class XRPLLookupError extends Error { - constructor(error) { - super("XRPL look up error"); - this.name = "XRPLLookupError"; - this.body = error; - } +import { Client, rippleTimeToISOTime, convertStringToHex } from "xrpl" + +const client = new Client("wss://s.devnet.rippletest.net:51233") +await client.connect() + +const subject_address = "rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA" +const issuer_address = "rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS" +const credential_type = convertStringToHex("my_credential").toUpperCase() + +// Look up Credential ledger entry -------------------------------------------- +const ledgerEntryRequest = { + command: "ledger_entry", + credential: { + subject: subject_address, + issuer: issuer_address, + credential_type: credential_type, + }, + ledger_index: "validated", } - -const CREDENTIAL_REGEX = /^[0-9A-F]{2,128}$/; -// See https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/credential#credential-flags -// to learn more about the lsfAccepted flag. -const LSF_ACCEPTED = 0x00010000; - -async function verifyCredential(client, issuer, subject, credentialType, binary=false) { - /** - * Check whether an XRPL account holds a specified credential, - * as of the most recently validated ledger. - * Parameters: - * client - Client for interacting with rippled servers. - * issuer - Address of the credential issuer, in base58. - * subject - Address of the credential holder/subject, in base58. - * credentialType - Credential type to check for as a string, - * which will be encoded as UTF-8 (1-128 characters long). - * binary - Specifies that the credential type is provided in hexadecimal format. - * You must provide the credential_type as input. - * Returns True if the account holds the specified, valid credential. - * Returns False if the credential is missing, expired, or not accepted. - */ - - // Encode credentialType as uppercase hex, if needed - let credentialTypeHex = ""; - if (binary) { - credentialTypeHex = credentialType.toUpperCase(); +console.log("Looking up credential...") +console.log(JSON.stringify(ledgerEntryRequest, null, 2)) + +let xrplResponse +try { + xrplResponse = await client.request(ledgerEntryRequest) +} catch (err) { + if (err.data?.error === "entryNotFound") { + console.error("Credential was not found") } else { - credentialTypeHex = convertStringToHex(credentialType).toUpperCase(); - logger.info(`Encoded credential_type as hex: ${credentialTypeHex}`); - } - - if (credentialTypeHex.length % 2 !== 0 || !CREDENTIAL_REGEX.test(credentialTypeHex)) { - // Hexadecimal is always 2 chars per byte, so an odd length is invalid. - throw new Error("Credential type must be 128 characters as hexadecimal."); - } - - // Perform XRPL lookup of Credential ledger entry -------------------------- - const ledgerEntryRequest = { - command: "ledger_entry", - credential: { - subject: subject, - issuer: issuer, - credential_type: credentialTypeHex, - }, - ledger_index: "validated", - }; - logger.info("Looking up credential..."); - logger.info(JSON.stringify(ledgerEntryRequest, null, 2)); - - let xrplResponse; - try { - xrplResponse = await client.request(ledgerEntryRequest); - } catch (err) { - if (err.data?.error === "entryNotFound") { - logger.info("Credential was not found"); - return false; - } else { - // Other errors, for example invalidly specified addresses. - throw new XRPLLookupError(err?.data || err); - } - } - - const credential = xrplResponse.result.node; - logger.info("Found credential:"); - logger.info(JSON.stringify(credential, null, 2)); - - // Check if the credential has been accepted --------------------------- - if (!(credential.Flags & LSF_ACCEPTED)) { - logger.info("Credential is not accepted."); - return false - } - - // Confirm that the credential is not expired ------------------------------ - if (credential.Expiration) { - const expirationTime = rippleTimeToISOTime(credential.Expiration); - logger.info(`Credential has expiration: ${expirationTime}`); - logger.info("Looking up validated ledger to check for expiration."); - - let ledgerResponse; - try { - ledgerResponse = await client.request({ - command: "ledger", - ledger_index: "validated", - }); - } catch (err) { - throw new XRPLLookupError(err?.data || err); - } - - const closeTime = rippleTimeToISOTime(ledgerResponse.result.ledger.close_time); - logger.info(`Most recent validated ledger is: ${closeTime}`); - - if (new Date(closeTime) > new Date(expirationTime)) { - logger.info("Credential is expired."); - return false; - } + console.error(err) } - - // Credential has passed all checks --------------------------------------- - logger.info("Credential is valid."); - return true; + process.exit(1) } +const credential = xrplResponse.result.node +console.log("Found credential:") +console.log(JSON.stringify(credential, null, 2)) -// Commandline usage ----------------------------------------------------------- -async function main() { - // Websocket URLs of public servers - const NETWORKS = { - devnet: "wss://s.devnet.rippletest.net:51233", - testnet: "wss://s.altnet.rippletest.net:51233", - mainnet: "wss://xrplcluster.com/", - }; - - - // Parse arguments --------------------------------------------------------- - let result = false - const program = new Command(); - program - .name("verify-credential") - .description("Verify an XRPL credential") - .argument("[issuer]", "Credential issuer address as base58", "rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS") - .argument("[subject]", "Credential subject (holder) address as base58", "rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA") - .argument("[credential_type]", "Credential type as string.", "my_credential") - .option("-b, --binary", "Use binary (hexadecimal) for credential_type") - .option( - `-n, --network {${Object.keys(NETWORKS)}}`, - "Use the specified network for lookup", - (value) => { - if (!Object.keys(NETWORKS).includes(value)) { - throw new Error(`Must be one of: ${Object.keys(NETWORKS)}`); - } - return value; - }, - "devnet" - ) - .option("-q, --quiet", "Don't print log messages") - // Call verify_credential with appropriate args ---------------------------- - .action(async (issuer, subject, credentialType, options) => { - const client = new Client(NETWORKS[options.network]); - await client.connect(); +// Check if the credential has been accepted ---------------------------------- +const lsfAccepted = 0x00010000 +if (!(credential.Flags & lsfAccepted)) { + console.log("Credential is not accepted.") + process.exit(2) +} - // Use INFO level by default when called from the commandline. - if (!options.quiet) { logger.level = "info" } +// Confirm that the credential is not expired --------------------------------- +if (credential.Expiration) { + const expirationTime = rippleTimeToISOTime(credential.Expiration) + console.log(`Credential has expiration: ${expirationTime}`) + console.log("Looking up validated ledger to check for expiration.") - // Commander.js automatically sets options.binary to a boolean: - // - If you provide -b or --binary on the command line then options.binary = true - // - If you do not provide it then options.binary = false - result = await verifyCredential(client, issuer, subject, credentialType, options.binary); + const ledgerResponse = await client.request({ + command: "ledger", + ledger_index: "validated", + }) - await client.disconnect(); - }); - await program.parseAsync(process.argv); + const closeTime = rippleTimeToISOTime(ledgerResponse.result.ledger.close_time) + console.log(`Most recent validated ledger was at: ${closeTime}`) - // Return a nonzero exit code if credential verification failed ----------- - if (!result) { - process.exit(1); + if (new Date(closeTime) > new Date(expirationTime)) { + console.log("Credential is expired.") + process.exit(3) } } -main().catch((err) => { - console.error("Fatal startup error:", err); - process.exit(1); -}); +// Credential has passed all checks ------------------------------------------- +console.log("Credential is valid.") +client.disconnect() diff --git a/redirects.yaml b/redirects.yaml index 018d10ead08..f72f92c17d9 100644 --- a/redirects.yaml +++ b/redirects.yaml @@ -1,3 +1,6 @@ +/resources/contribute-documentation/tutorial-structure/: + to: /resources/contribute-documentation/tutorial-template/ + type: 301 /docs/infrastructure/installation/rippled-1-3-migration-instructions/: to: /docs/infrastructure/installation/ type: 301 diff --git a/resources/contribute-documentation/tutorial-guidelines.md b/resources/contribute-documentation/tutorial-guidelines.md index f98f6b16e3e..b8848b7e04e 100644 --- a/resources/contribute-documentation/tutorial-guidelines.md +++ b/resources/contribute-documentation/tutorial-guidelines.md @@ -1,84 +1,138 @@ --- -html: tutorial-guidelines.html -parent: contribute-documentation.html seo: description: Learn how this site's tutorials are structured and guidelines for contributing quality tutorials. --- # Tutorial Guidelines -We are creating a modular tutorial framework that allows developers to learn how transactions and requests work on the XRP Ledger. Developers can review the modules to learn about business solutions, and potentially repurpose the scripts in their own applications. +We are creating a repository of tutorials and functional code samples that show how various features of the XRP Ledger work. Developers and large language models (LLMs) can use these tutorials and their associated code samples to learn about business solutions, and copy or adapt the scripts for use in their own applications. +The guidelines on this page don't need to be strictly enforced. It's OK to diverge from them in cases where you have good reason to. -# Rationale +## Rationale -What a developer wants comes down to two things: +The main purpose of tutorials is to provide **sample code** alongside natural-language text to further explain what the code does. This serves multiple purposes: -1. Sample code snippets they can copy and paste into their own applications. -2. Complete API reference documentation. +- Developers can copy and paste the sample code into their own applications. +- Large language models (LLMs) can use this as training data to generate high-quality code for use with the XRP Ledger. -Keep the conceptual information to a minimum – only the information necessary to complete the tutorial. For background or deeper understanding, provide links to the conceptual topics at the end of the tutorial, if needed. +Conceptual information is sometimes necessary, but tutorials are not the place to discuss concepts at length. A tutorial should include a few sentences throughout to help provide context for the action occurring in the code, and should link out to concept and reference pages to provide background reading. -Modular tutorials follow Malcolm Knowles’ six assumptions for designing adult learning: +LLMs are increasingly being used in software development. To assist users of these tools, we would like to provide many working code samples that demonstrate best practices and follow consistent structure and style. Tutorials that have descriptions of what the code does alongside matching code snippets help LLMs develop the correct associations between terms used in natural language and programming language, hopefully leading to more accurate results from code generation. -1. Adults need to know why they need to learn something. -2. Adults need to build on their experience. -3. Adults have a need to feel responsible for their learning. -4. Adults are ready to learn if training solves an immediate problem. -5. Adults want their training to be problem focused. -6. Adults learn best when motivation comes intrinsically. +## Recommended Tutorial Structure -Add into that Ralph Smedley’s quote, “We learn best in moments of enjoyment.” Taking a lighter touch helps to relax the learner so that the material flows into their brain with less resistance. +See [Tutorial Template](./tutorial-template.md) for the typical headers and contents expected for a new tutorial. +## Sample Code Guidelines -# Sample Code vs. Tasks vs. Concepts vs. Tutorials +Sample code is well commented scripts, snippets, or applications that illustrate common usage patterns and best practices. Advanced users can typically scan the example and use it immediately without a formal tutorial. Not every piece of sample code needs to be associated with a tutorial, but most tutorials will have a piece of sample code that serves as the basis for that tutorial. -To date, there have been some blurred lines where different types of documentation show up as _Tutorials_. Here are some comparisons that help define the distinction. +The XRPL.org maintainers are generally committed to providing sample code in both **JavaScript** and **Python** using the official client libraries for those languages. The site may occasionally provide more, depending on what the community needs and provides. This site is open-source, so if you want to maintain examples in other languages, feel free to volunteer! +However, due to the security concerns of using third party libraries, it may take a while to accept contributions in other programming languages. -## Sample Code +### Folder Layout -Sample code is well commented snippets or applications that illustrate best practices for implementing a feature of the API. Sample code is modular and reusable with little customization required. +Sample code should be provided in the `_code-samples/` folder at the top of this website's source repository, with separate subfolders by programming language. There should be a `README.md` file for each code sample _above_ the language folders describing what the code sample does in general, _and_ a `README.md` in each programming language folder describing how to install and run that code sample specifically. -Sample code is desirable, because advanced users can typically scan the example and use it immediately without a formal tutorial. It can also be used by others as a basis for tutorials. Sample code developers can focus on what they do well, while technical writers and support personnel can use the samples to create quality training materials. +For example: +``` +- _code_samples/issue-credentials/ + - js/ + - README.md + - issue-credential.js + - package.json + - py/ + - README.md + - issue_credential.py + - requirements.txt + - README.md +``` -## Tasks +This information is used to populate the [Code Samples](/resources/code-samples/) page. The outer readme's header is the title of the code sample on that page, and the first paragraph is the description. -Tasks are step-by-step instructions for how to accomplish a specific result. For example, “Installing rippled on a Red Hat Linux Server.” Task documentation is not intended to be particularly educational. It frequently describes tasks that are only performed one time per implementation, or maintenance tasks that always follow a familiar pattern. Tasks provide troubleshooting guidance, since there are likely variables that the user must adjust based on the specifics of their use case. +### Comments +Comments are an important part of readable, accurate code, but don't go overboard. The text of a tutorial may be translated into other languages such as Japanese, while the sample code is kept the same, so don't include any critical information _only_ in the comments. -## Concepts +Use comments to separate out logical sections of the sample code. You can these comments as markers so that the `{% code-snippet ... %}` Markdoc component shows only the relevant section at a time in a tutorial. -Conceptual information describes elements of the API, how they work, and when to use them. If a tutorial requires lengthy explanations before or during the programming tasks, consider how you might separate the exposition into a new topic, or link to existing topics that set the proper context. +### Sample Code Types -For example, three paragraphs of context and a single line of code would be a concept, not a tutorial. +Sample code can take many forms, such as the following: +- **Sample Application** - A fully functional program that accepts user input to perform tasks with some flexibility. It may have a graphical user interface or only a commandline interface. Complete sample code for different stages of application development may be provided, but is not recommended because it's more work than it's worth. +- **Script** - A simple program that performs a predetermined set of tasks, often using hard-coded values, with minimal branching. These scripts are not expected to perform robust error handling; they typically exit when an error occurs. +- **Snippet** - A self-contained function or piece of code that demonstrates the best practices for doing one thing, but is not a complete program. Snippets are most likely to _not_ have an associated tutorial. -## Tutorials +All three types of sample code have their time and place. However, for most of this website's tutorials, _scripts_ are preferred for the following reasons: -Tutorials begin with sample code that illustrates best practices for implementing a feature. They take the developer step-by-step through the development process, explaining the purpose of each block of code. +- They demonstrate the relevant, XRPL-specific functionality with minimal distractions. +- You can run a script to demonstrate that it works and is accurate. +- Scripts are easier to create and have a lesser maintenance burden than more complex apps. -Tutorials further combine a number of features to work together to solve a business problem. They describe the straightforward sunny day path to complete a task. Then, the tutorial might suggest modifications that let the developer try several different scenarios. Due to their focus on a certain limited scope of behavior, tutorials should not require extensive troubleshooting information. +### JSON-RPC / WebSocket / Commandline Examples as Sample Code +Some legacy tutorials show example requests and responses using WebSocket, JSON-RPC APIs, or the `rippled` commandline. These are not recommended, for the following reasons: -## Use Cases +- People (or LLMs) who copy these formats tend to end up submitting their secret keys to public servers, which is extremely insecure. +- Many tutorials involve steps where you need application logic that can't be represented in API requests/responses. You end up with code in other programming languages anyway, or pseudocode, or just steps that are missing examples of how to do critical work. +- The API references already provide examples in these formats. -Use cases describe how to pull together multiple features to create a practical application that solves a business problem. They provide context and assistance with the decision making process, then provide links to the appropriate topics for each step of implementation. +If you do have good reason to provide commandline, WebSocket, or JSON-RPC examples, show both the request and an example response in separate code blocks. +### Dependencies -# Tutorial Components +Dependencies can be a source of maintenance burden, because you need to stay up-to-date with security fixes and breaking changes to the dependencies. On the other hand, reimplementing common utilities in every code sample is its own maintenance burden, and it's even worse to "roll your own" security-sensitive code. Some users may be working on codebases that are locked into competing/incompatible dependencies, which can make it harder to adapt your code to their situation; the more dependencies you have, the more likely this is to occur. -This section describes the elements of the modular tutorials used on XRPL.org. +Some guidelines: +1. Prefer standard library functions to third-party libraries, even if they're not quite as convenient to use. + - Use third-party libraries when they're _significantly_ more convenient than the standard library. For example, [even Python's official documentation recommends using the Requests lib instead of `urllib.request`](https://docs.python.org/3/library/urllib.request.html#module-urllib.request). + - When updating old code samples, look for cases where dependencies can be eliminated because the standard library has grown to encompass functionality that previously needed a library. +2. Implement your own functions when they're small and not security-sensitive; use libraries for complex or security-sensitive functions. +3. Prefer widely-used, actively maintained libraries. -## Sample Application +### General Guidelines and Best Practices -XRPL tutorial code samples are modular in nature. For example, Script 1 demonstrates how to create a test account, access the XRP Ledger, and transfer XRP between accounts. Any further samples can reuse the functions in Script 1. +The following guidelines apply for XRP Ledger code samples regardless of language: -Create a new script with the specific, minimal function code required to demonstrate the practical solution to a business problem. The examples should be incremental, with just enough behaviors to illustrate a business process. +- Don't hardcode secret keys, even example keys that don't hold real money. Instead, do any of the following: + - Fund a new account using the faucet. + - Prompt the user to paste the seed of the account they want to use. + - Read the secret key from an environment variable. +- Use `client` as the name for the API client instance. +- Print output to the console, especially before doing any network operations such as calling API methods or submitting transactions. +- Use the client library's "submit and wait" function when sending transactions. Autofill, sign, and submit the transaction all in one call. +- Use tabs for code samples even if you only have a code sample in one language. +- When making WebSocket/JSON-RPC API calls, use the latest API version and the `validated` ledger. -For example, the first NFT tutorial shows how to mint, retrieve, and burn an NFT. The next tutorial shows how to create and accept a sell offer, and create and accept a buy offer. +### Language-specific Guidelines -Don’t focus too much on the UX of the application, unless the look and feel is pertinent to the topic. Use the standard CSS file with the look and feel for all of the tutorials. +{% tabs %} -Reuse the code from other modules when possible. There might be situations where you need to modify the behavior from an earlier module. You can either overload the function name or modify the module and save it with a different name. +{% tab label="JavaScript" %} +JavaScript code samples should: + +- Use xrpl.js as the XRPL client library. +- Provide a `package.json` file that specifies `"type": "module"` and any relevant dependencies. +- Use **ES Module** syntax such as `import { Client } from "xrpl"`, not Common JS syntax such as `require("xrpl")`. +- Use `await` instead of `.done(...)` or `.then(...)` +- Follow [**JavaScript Standard Style**](https://standardjs.com). +- Be compatible with Node.js versions that are currently in maintenance (security) support. + - Preferably, be compatible with most widely-used web browsers too. +- When writing JSON objects to the console, use `JSON.stringify(example_object, null, 2)` so that Node.js doesn't skip the interesting inner parts of the object. +{% /tab %} + +{% tab label="Python" %} +Python code samples should: + +- Use xrpl-py as the XRPL client library +- Provide a `requirements.txt` file with relevant dependencies. +- Use the `JsonRpcClient` unless asynchronous functionality is needed. +- Follow [**Black Style**](https://black.readthedocs.io/en/stable/). +- Be compatible with Python versions that are currently in maintenance (security) support. +{% /tab %} + +{% /tabs %} diff --git a/resources/contribute-documentation/tutorial-structure.md b/resources/contribute-documentation/tutorial-structure.md deleted file mode 100644 index 0a06af4edba..00000000000 --- a/resources/contribute-documentation/tutorial-structure.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -html: tutorial-structure.html -parent: contribute-documentation.html -seo: - description: A summary of the parts of a standard tutorial. ---- -# Tutorial Structure - -Each XRP Ledger tutorial follows the same format. - -1. A brief description of the features illustrated in the tutorial. -2. Prerequisites for running the code, if needed, or links to the sample code. -3. Usage examples of the features in the tutorial. -4. A code walkthrough of the sample application, highlighting unique elements in the scripts. -5. See Also, with links to conceptual information or good tutorials to try as a next step. - -Separate setup (prerequisites) from usage from code development. These are each different activities that engage different areas of the brain. Trying to think of all three elements at once leads to confusion and headaches. - -## Description - -![Description](/docs/img/tut-struct1.png) - -List what the sample demonstrates. If possible, each example should describe the steps to accomplish specific related tasks. (For example, create a NFT Sell Offer, Accept a Sell Offer, Delete a Sell Offer.) There should be enough conceptual information to understand what the tutorial illustrates, with links to additional conceptual information, if needed. - -## Prerequisites - -![Prerequisites](/docs/img/tut-struct2.png) - -Provide links to any required software and to all of the example code needed to run the tutorial. If necessary, give simple instructions for using third-party tools, but provide a link to the source website for the customer to do a deeper dive at their leisure. - -## Usage Example - -![Usage](/docs/img/tut-struct3.png) - -Start by providing a finished, working example of the tutorial application. This is an opportunity for immediate success working with the software to solve a problem. - -Use screenshots for each step of the tutorial – these allow the user to understand the tutorial without having to run the code themselves. Of course we _want_ them to run the code, but this gives them a choice. - -Describe the sunny day scenario. The application should run without problems if there is an uninterrupted connection to the internet. Don’t provide a lot of troubleshooting information, unless it’s pertinent to the tutorial. - -## Code Walkthrough - -![Code Walkthrough](/docs/img/tut-struct4.png) - -Walk through the code, one chunk at a time. Don’t belabor topics that have been discussed in earlier examples. Provide sample code, but don’t provide exhaustive explanations for how to program underlying platforms like HTML syntax unless there is something unique to the implementation. - -An important thing to emphasize is that every interaction with the XRPL is either a transaction or a request, and that all transactions and requests are essentially the same. The sample code we provide shows how to prepare the transaction or request, and how to process the returned results. Knowing how to submit and respond to one transaction or request gives a pretty good idea for how to submit and respond to any transaction or request. - -(Technically there is third category, similar to a request: a notification from a subscription stream. See [Subscription Methods](../../docs/references/http-websocket-apis/public-api-methods/subscription-methods/index.md).) - -## See Also - -![See Also](/docs/img/tut-struct5.png) - -At the end of the tutorial, provide links to additional resources, conceptual information, and any tutorials that would be a sensible next step in the learning journey. diff --git a/resources/contribute-documentation/tutorial-template.md b/resources/contribute-documentation/tutorial-template.md new file mode 100644 index 00000000000..7bc583a3170 --- /dev/null +++ b/resources/contribute-documentation/tutorial-template.md @@ -0,0 +1,163 @@ +--- +seo: + description: A summary of the parts of a standard tutorial. +--- +# Tutorial Template + +This tutorial demonstrates the structure of a **[tutorial](./tutorial-guidelines.md)** in the XRPL.org standard style, including: + +- Typical headings for a tutorial page +- Recommendations for code samples and usage +- Options and variations + +The template should begin with an intro that states what the tutorial is about. The intro should have one or more "backlinks" to the conceptual documentation for the core concepts/features that the tutorial demonstrates. You may also want to add a small amount of additional context of _why_ or _when_ you would want to do whatever this tutorial does. Don't go overboard—leave the details for concept or use case articles. + +## Goals + +This section defines the learning goals of the tutorial: +- Bullet points are a succinct way of outlining learning steps. +- If the tutorial includes a graphical interface, include a screenshot of the final product here. + +## Prerequisites + +Prerequisites can take several forms: + +- Knowledge and learning background, especially tutorials that this one builds on top of. +- Dev environment setup, especially basic depedencies such as your xrpl client library. + - Do not include dependencies that are specific to this tutorial here, because people tend to skim/gloss over this section. For dependencies specific to this tutorial, include them in the steps later. +- Specific on-chain structures that need to be in place in advance. For example, to trade against an AMM, the AMM must exist in the ledger. +- Amendments that need to be enabled for this tutorial. Use an amendment disclaimer component to show the Mainnet status of the amendment. + +## Source Code + +You can find the complete source code for this tutorial's examples in the {% repo-link path="resources/contribute-documentation/tutorial-structure.md" %}resources section of this website's repository{% /repo-link %}. + +{% admonition type="success" name="Tip" %} +Use the `{% repo-link ... %}` component to link to the source files; this component is designed to adjust based on the environment, so for example previews link to the code on the preview branch... although it doesn't fully work as of this writing (2025-08-11). +{% /admonition %} + +## Usage + +To test that the code runs properly, you can navigate to the repository top and start the local Redocly server as follows: + +```sh +npm run start +``` + +Then, navigate to to view the parsed version of this page. + +{% admonition type="info" name="Usage is optional" %} +You should include a Usage section in **sample app tutorials**. Provide a "Usage" section if this tutorial's sample code: +- Has a GUI with multiple buttons/inputs +- Is a commandline utility with multiple options/parameters +- Consists of multiple scripts that need to be run in a specific order + +If there's a user interface, you can also embed a video demonstrating usage of the sample app. + +For single-file scripts that perform a linear set of steps without user input, omit the Usage section. +{% /admonition %} + +## Steps + +Follow these steps to build a tutorial. Number the steps, because they help to orient readers to the structure and serve to identify the core part of the tutorial. + +If you change the number or order of steps, remember to update the step numbers and update any links or mentions of specific steps, too. + +### 1️1. Install dependencies + +Unlike the ones in Prerequisites, this step is for dependencies that are specific to this tutorial. They're here because people tend to gloss over the preamble parts and skip straight to the "meat" of the tutorial. + +{% tabs %} +{% tab label="JavaScript" %} +From the code sample folder, use npm to install dependencies: + +```sh +npm i +``` +{% /tab %} +{% tab label="Python" %} +From the code sample folder, use pip to install dependencies: + +```sh +pip install -r requirements.txt +``` +{% /tab %} +{% /tabs %} + +### 2. Connect and get account(s) + +Each step should have a heading in the imperative, in sentence case. Beneath the heading should be a sentence or two introducing the action being taken at this stage of the code sample in more detail. The code samples should be in tabs per programming language. + +Most code samples need at least one account. The first step should generally cover from the start of the file (including imports) through instantiating a client, connecting to a network, and deriving wallets and/or funding accounts via the faucet. + +{% tabs %} + +{% tab label="JavaScript" %} +{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" before="// Look up Credential" /%} +{% /tab %} + +{% /tabs %} + +{% admonition type="info" name="Code snippets and steps" %} +Each "step" of the tutorial should correspond to one code snippet (with tabs by programming language). There can be exceptions, for example if one programming language needs additional code in a separate place to make the same functionality work. +{% /admonition %} + +### 3. Check for on-ledger structures + +If a script depends on certain ledger data already existing (for example, you are supposed to create it with a different script), the script should have an explicit step to check for the existence of that data. You should also mention the requirement in the [**Prerequisites**](#prerequisites) section. + +{% tabs %} + +{% tab label="JavaScript" %} +{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Look up Credential" before="// Check if the credential has been accepted" /%} +{% /tab %} + +{% /tabs %} + +### 4. Add next step + +Each additional step should directly continue the code sample from the previous step without skipping anything, to the extent possible. + +{% tabs %} + +{% tab label="JavaScript" %} +{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Check if the credential has been accepted" before="// Confirm that the credential is not expired" /%} +{% /tab %} + +{% /tabs %} + +Optionally, you can provide additional text after the code snippet, such as an explanation of the expected output from this step, or details that you should note down for later. + +### 5. Use as many steps as necessary + +If the code snippet calls an API method, link to the relevant reference documentation. If you include the common links file, you can generally do this with an automatic link such as the `[ledger method][]`, which turns into the [ledger method][]. + +{% tabs %} + +{% tab label="JavaScript" %} +{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Confirm that the credential is not expired" before="// Credential has passed all checks" /%} +{% /tab %} + +{% /tabs %} + +{% admonition type="success" name="The right number of steps" %} +Most tutorials should have 3-7 steps. If the tutorial has fewer, maybe it doesn't need to be a tutorial, or maybe you should go into more detail. If it has more, consider splitting it into multiple tutorials. +{% /admonition %} + +### 6. Final step + +Use `{% code-snippet ... %}` tags instead of copy-paste to display the code samples, so that you don't have to manually keep the code in the doc synchronized with changes to the code sample. To facilitate this, use `from=` and `before=` strings based on unique comments in the code. The first code snippet should omit `from=` and the last should omit `before=`. + +{% tabs %} + +{% tab label="JavaScript" %} +{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Credential has passed all checks" /%} +{% /tab %} + +{% /tabs %} + +## See Also + +At the end of the tutorial, provide links to additional resources that would be a sensible next step in the learning journey. This could be more tutorials, use cases, or other pages. It's also a good idea to add links here to reference documentation for any API methods, transaction types, and ledger entries used in the tutorial—even though those links should be redundant with links scattered throughout the text of the tutorial. + +{% raw-partial file="/docs/_snippets/common-links.md" /%} diff --git a/sidebars.yaml b/sidebars.yaml index a176098a195..b0dd2e67094 100644 --- a/sidebars.yaml +++ b/sidebars.yaml @@ -748,5 +748,5 @@ - page: resources/contribute-documentation/documentation-translations.md - page: resources/contribute-documentation/creating-diagrams.md - page: resources/contribute-documentation/tutorial-guidelines.md - - page: resources/contribute-documentation/tutorial-structure.md + - page: resources/contribute-documentation/tutorial-template.md - page: resources/contribute-blog/index.md