diff --git a/docs/.vuepress/sidebar.ts b/docs/.vuepress/sidebar.ts index 07c74c365da..04b08a6d31e 100644 --- a/docs/.vuepress/sidebar.ts +++ b/docs/.vuepress/sidebar.ts @@ -360,7 +360,17 @@ export const getSidebar = (locale: string) => }, { text: "Stellar & Soroban", - link: `${locale}/quickstart/quickstart_chains/stellar.md`, + collapsible: true, + children: [ + { + text: "Combined Example", + link: `${locale}/quickstart/quickstart_chains/stellar.md`, + }, + { + text: "Soroban Contracts", + link: `${locale}/quickstart/quickstart_chains/stellar-soroban.md`, + }, + ], }, ], }, diff --git a/docs/quickstart/quickstart_chains/stellar-soroban.md b/docs/quickstart/quickstart_chains/stellar-soroban.md new file mode 100644 index 00000000000..dd632812de2 --- /dev/null +++ b/docs/quickstart/quickstart_chains/stellar-soroban.md @@ -0,0 +1,254 @@ +# Soroban Smart Contract Quick Start + +The aim of this quick start guide is to provide a brief introduction to Soroban through the hello world smart contract SubQuery indexer. The sample smart contract is designed as a simple incrementer, allowing users to input a specific value. It serves as an effective way to quickly understand the workings of Soroban through a hands-on example in the real world. + +::: tip Note +The final code of this project can be found [here](https://github.com/subquery/stellar-subql-starter/tree/main/Stellar/soroban-greeter-contract). +::: + +## Preparations + +For this tutorial, we'll utilise a streamlined version of the hello world smart contract available [here](https://github.com/stellar/soroban-dapps-challenge/blob/greeter/contracts/hello-world/src/lib.rs). We'll streamline the code by retaining only the `increment()` function. The resulting smart contract code will be as follows: + +```rust +#![no_std] +use soroban_sdk::{contract, contractimpl, symbol_short, Env, Symbol}; + +// const MESSAGE: Symbol = symbol_short!("MESSAGE"); +const COUNT: Symbol = symbol_short!("COUNT"); +const LAST_INCR: Symbol = symbol_short!("LAST_INCR"); + +// (attribute macro) Marks a type as being the type that contract functions are attached for. +#[contract] +pub struct HelloContract; + +// (attribute macro) Exports the publicly accessible functions to the Soroban environment. +#[contractimpl] +/// Implementation of the HelloContract. +impl HelloContract { + + /// Increments the count by the specified value. + /// + /// # Arguments + /// + /// * `env` - The environment object. + /// * `incr` - The value to increment the count by. + /// + /// # Returns + /// + /// The updated count. + pub fn increment(env: Env, incr: u32) -> u32 { + // Get the current count. + let mut count = env.storage().instance().get(&COUNT).unwrap_or(0); + + // Increment the count. + count += incr; + + // Save the count. + env.storage().instance().set(&COUNT, &count); + env.storage().instance().set(&LAST_INCR, &incr); + + // Emit an event. + env.events() + .publish((COUNT, symbol_short!("increment")), count); + + // Return the count to the caller. + count + } +} +``` + +We've deployed and published the smart contract multiple times for better illustration. The transactions involving the increment function calls can be found in the following list. Ensure to explore the details, accessible under the "Events" tab: + +- [https://futurenet.stellarchain.io/transactions/8cd93444c3f4f37bc38dc4701c649e45081656965b4a94b73a4de8b3cf04cc8b](https://futurenet.stellarchain.io/transactions/8cd93444c3f4f37bc38dc4701c649e45081656965b4a94b73a4de8b3cf04cc8b) +- [https://futurenet.stellarchain.io/transactions/8cd93444c3f4f37bc38dc4701c649e45081656965b4a94b73a4de8b3cf04cc8b](https://futurenet.stellarchain.io/transactions/8cd93444c3f4f37bc38dc4701c649e45081656965b4a94b73a4de8b3cf04cc8b) +- [https://futurenet.stellarchain.io/transactions/6a7ac207f3909d92d5fee04e6fb7d78640a7a7f65424a9b4d54c13d6ffcc9e58](https://futurenet.stellarchain.io/transactions/6a7ac207f3909d92d5fee04e6fb7d78640a7a7f65424a9b4d54c13d6ffcc9e58) +- [https://futurenet.stellarchain.io/transactions/8e288bcba7ce4e6a8b275acfea40c6787d65db27eb16d8bef559d8be3bbcef65](https://futurenet.stellarchain.io/transactions/8e288bcba7ce4e6a8b275acfea40c6787d65db27eb16d8bef559d8be3bbcef65) +- [https://futurenet.stellarchain.io/transactions/aa26158526795f728ff0744246b080ded772be88f7af817255782993e8809d20](https://futurenet.stellarchain.io/transactions/aa26158526795f728ff0744246b080ded772be88f7af817255782993e8809d20) +- [https://futurenet.stellarchain.io/transactions/205b9778c159387d2b6b70f3c39c3876d79a3496c22eab4a2c9dafac17a0fa56](https://futurenet.stellarchain.io/transactions/205b9778c159387d2b6b70f3c39c3876d79a3496c22eab4a2c9dafac17a0fa56) + +Now, let's move on to configuring the indexer. + + + +Remove all existing entities and update the `schema.graphql` file as follows, here you can see we are indexing a single datapoint - increment. + +```graphql +type Increment @entity { + id: ID! + newValue: BigInt! +} +``` + + + +```ts +import { Increment } from "../types"; +``` + + + +**Since you only need to index the events, the only handler type is need is an events handler:** + +```ts +dataSources: [ + { + kind: StellarDatasourceKind.Runtime, + startBlock: 831474, + mapping: { + file: "./dist/index.js", + handlers: [ + { + handler: "handleEvent", + kind: StellarHandlerKind.Event, + filter: { + topics: [ + "COUNT", // Topic signature(s) for the events, there can be up to 4 + ], + contractId: "CAQFKAS47DF6RBKABRLDZ5O4XJIH2DQ3RMNHFPOSGLFI6KMSSIUIGQJ6" + } + }, + ], + }, + }, + ], +``` + +Here is a straightforward event handler. We establish additional filters, specifically for the topic name and contract ID, to ensure that we handle only events with a specific topic from a particular smart contract. + + + +Next, let’s proceed ahead with the Mapping Function’s configuration. + + + +Navigate to the default mapping function in the `src/mappings` directory. Update the `mappingHandler.ts` file as follows (**note the additional imports**): + +```ts +import { Increment } from "../types"; +import { SorobanEvent } from "@subql/types-stellar"; + +export async function handleEvent(event: SorobanEvent): Promise { + logger.warn(event.transaction.hash.toString()); + if (event.type.toString() == "contract") { + logger.warn(JSON.stringify(event.value)); + const increment = Increment.create({ + id: event.transaction.hash, + newValue: BigInt( + JSON.parse(JSON.stringify(event.value))["_value"].toString() + ), + }); + await increment.save(); + } +} +``` + +Here's a brief explanation. First, the necessary types are imported. Then, a handler `handleEvent` that takes a `SorobanEvent` as a parameter is defined. Once the handler is triggered, it uses the logger to warn about the hash of the associated transaction. As a next step it checks if the event type is `contract`: if it is a contract event, log the event's value, extract the relevant information from the event value (`increment` value), and create an object of entity `Increment` with the extracted information. Finally, it saves the `Increment` object to the data store. + + + + + + + + + +In the following query, we will retrieve all stored objects and promptly conduct their aggregation. Specifically, we will compute the frequency of function calls grouped by a parameter in each call. + +```graphql +query { + increments { + nodes { + id + newValue + } + groupedAggregates(groupBy: [NEW_VALUE]) { + distinctCount { + id + } + keys + } + } +} +``` + +You will see the result similar to below: + +```json +{ + "data": { + "increments": { + "nodes": [ + { + "id": "8cd93444c3f4f37bc38dc4701c649e45081656965b4a94b73a4de8b3cf04cc8b", + "newValue": "7" + }, + { + "id": "205b9778c159387d2b6b70f3c39c3876d79a3496c22eab4a2c9dafac17a0fa56", + "newValue": "1975" + }, + { + "id": "3cf049dfaa35bfabe7574b192b691e95f462418952bf5d7028f5ffc06852eef6", + "newValue": "32" + }, + { + "id": "aa26158526795f728ff0744246b080ded772be88f7af817255782993e8809d20", + "newValue": "975" + }, + { + "id": "6a7ac207f3909d92d5fee04e6fb7d78640a7a7f65424a9b4d54c13d6ffcc9e58", + "newValue": "125" + }, + { + "id": "8e288bcba7ce4e6a8b275acfea40c6787d65db27eb16d8bef559d8be3bbcef65", + "newValue": "275" + } + ], + "groupedAggregates": [ + { + "distinctCount": { + "id": "1" + }, + "keys": ["7"] + }, + { + "distinctCount": { + "id": "1" + }, + "keys": ["32"] + }, + { + "distinctCount": { + "id": "1" + }, + "keys": ["125"] + }, + { + "distinctCount": { + "id": "1" + }, + "keys": ["275"] + }, + { + "distinctCount": { + "id": "1" + }, + "keys": ["975"] + }, + { + "distinctCount": { + "id": "1" + }, + "keys": ["1975"] + } + ] + } + } +} +``` + +::: tip Note +The final code of this project can be found [here](https://github.com/subquery/stellar-subql-starter/tree/main/Stellar/soroban-greeter-contract). +::: + + diff --git a/docs/quickstart/quickstart_chains/stellar.md b/docs/quickstart/quickstart_chains/stellar.md index 2985a0ce6ca..b449b32b477 100644 --- a/docs/quickstart/quickstart_chains/stellar.md +++ b/docs/quickstart/quickstart_chains/stellar.md @@ -1,4 +1,4 @@ -# Stellar & Soroban Quick Start +# Stellar & Soroban (Combined) Quick Start The goal of this quick start guide is to give a quick intro to all features of our Stellar and Soroban indexer. The example project indexes all soroban transfer events on Stellar's Futurenet. It also indexes all account payments including credits and debits - it's a great way to quickly learn how SubQuery works on a real world hands-on example. @@ -10,9 +10,7 @@ Now, let's move forward and update these configurations. The final code of this project can be found [here](https://github.com/subquery/stellar-subql-starter/tree/main/Stellar/soroban-futurenet-starter). ::: -## 1. Update Your GraphQL Schema File - -The `schema.graphql` file determines the shape of the data that you are using SubQuery to index, hence it's a great place to start. The shape of your data is defined in a GraphQL Schema file with various [GraphQL entities](../../build/graphql.md). + Remove all existing entities and update the `schema.graphql` file as follows, here you can see we are indexing a variety of datapoints, including accounts, transfers, credit, debits, and payments. @@ -58,43 +56,17 @@ type Debit @entity { } ``` -Since we have a [one-to-many relationship](../../build/graphql.md#one-to-many-relationships), we define a foreign keys using `from: Account!` syntax. - -::: warning Important -When you make any changes to the schema file, please ensure that you regenerate your types directory. -::: - -::: code-tabs -@tab:active yarn - -```shell -yarn codegen -``` - -@tab npm + -```shell -npm run-script codegen +```ts +import { Account, Credit, Debit, Payment, Transfer } from "../types"; ``` -::: - -You will find the generated models in the `/src/types/models` directory. - -Check out the [GraphQL Schema](../../build/graphql.md) documentation to get in-depth information on `schema.graphql` file. + Now that you have made essential changes to the GraphQL Schema file, let’s move forward to the next file. -## 2. Update Your Project Manifest File - -The Project Manifest (`project.ts`) file works as an entry point to your Stellar and Soroban projects. It defines most of the details on how SubQuery will index and transform the chain data. For Stellar/Soroban, there are three types of mapping handlers (and you can have more than one in each project): - -- [BlockHandler](../../build/manifest/stellar.md#mapping-handlers-and-filters): On each and every block, run a mapping function -- [TransactionHandlers](../../build/manifest/stellar.md#mapping-handlers-and-filters): On each and every Stellar/Soroban transaction that matches optional filter criteria, run a mapping function -- [OperationHandler](../../build/manifest/stellar.md#mapping-handlers-and-filters): On each and every Stellar operation action that matches optional filter criteria, run a mapping function -- [EffectHandler](../../build/manifest/stellar.md#mapping-handlers-and-filters): On each and every Stellar effect action that matches optional filter criteria, run a mapping function -- [OperationHandler](../../build/manifest/stellar.md#mapping-handlers-and-filters): On each and every Stellar operation action that matches optional filter criteria, run a mapping function -- [EventHandler](../../build/manifest/stellar.md#mapping-handlers-and-filters): On each and every Soroban event action that matches optional filter criteria, run a mapping function + Note that the manifest file has already been set up correctly and doesn’t require significant changes, but you need to update the datasource handlers. @@ -151,13 +123,11 @@ Note that the manifest file has already been set up correctly and doesn’t requ The above code indicates that you will be running a `handleOperation` mapping function whenever there is `payment` Stellar operation made. Additionally we run the `handleCredit`/`handleDebit` mapping functions whenever there are Stellar effects made of the respective types. Finally, we have a Soroban event handler, which looks for any smart contract events that match the provided topic filters, in this case it runs `handleEvent` whenever a event wth the `transfer` topic is detected. -Check out our [Manifest File](../../build/manifest/stellar.md) documentation to get more information about the Project Manifest (`project.ts`) file. + Next, let’s proceed ahead with the Mapping Function’s configuration. -## 3. Add a Mapping Function - -Mapping functions define how chain data is transformed into the optimised GraphQL entities that we previously defined in the `schema.graphql` file. + Follow these steps to add a mapping function: @@ -180,7 +150,7 @@ import { AccountCredited, AccountDebited } from "stellar-sdk/lib/types/effects"; import { Horizon } from "stellar-sdk"; export async function handleOperation( - op: StellarOperation, + op: StellarOperation ): Promise { logger.info(`Indexing operation ${op.id}, type: ${op.type}`); @@ -201,13 +171,13 @@ export async function handleOperation( } export async function handleCredit( - effect: StellarEffect, + effect: StellarEffect ): Promise { logger.info(`Indexing effect ${effect.id}, type: ${effect.type}`); const account = await checkAndGetAccount( effect.account, - effect.ledger.sequence, + effect.ledger.sequence ); const credit = Credit.create({ @@ -221,13 +191,13 @@ export async function handleCredit( } export async function handleDebit( - effect: StellarEffect, + effect: StellarEffect ): Promise { logger.info(`Indexing effect ${effect.id}, type: ${effect.type}`); const account = await checkAndGetAccount( effect.account, - effect.ledger.sequence, + effect.ledger.sequence ); const debit = Debit.create({ @@ -271,7 +241,7 @@ export async function handleEvent(event: SorobanEvent): Promise { async function checkAndGetAccount( id: string, - ledgerSequence: number, + ledgerSequence: number ): Promise { let account = await Account.get(id.toLowerCase()); if (!account) { @@ -293,9 +263,7 @@ For the `handleCredit` and `handleDebit` mapping functions, the functions receiv The `handleEvent` mapping function is for Soroban smart contracts, and the payload of data is stored as an array of properties. -Check out our [Mappings](../../build/mapping/stellar.md) documentation to get more information on mapping functions. - - + @@ -304,33 +272,21 @@ Check out our [Mappings](../../build/mapping/stellar.md) documentation to get mo ```graphql -{ - query { - transfers(first: 5, orderBy: VALUE_DESC) { - totalCount - nodes { - id - date - ledger - toId - fromId - value - } +query { + credits { + totalCount + nodes { + id + amount + accountId } - accounts(first: 5, orderBy: SENT_TRANSFERS_COUNT_DESC) { - nodes { - id - sentTransfers(first: 5, orderBy: LEDGER_DESC) { - totalCount - nodes { - id - toId - value - } - } - firstSeenLedger - lastSeenLedger - } + } + debits { + totalCount + nodes { + id + amount + accountId } } } @@ -339,7 +295,32 @@ Check out our [Mappings](../../build/mapping/stellar.md) documentation to get mo You will see the result similar to below: ```json - +{ + "data": { + "query": { + "debits": { + "totalCount": 1, + "nodes": [ + { + "id": "0002576954607800321-0000000002", + "amount": "10000.0000000", + "accountId": "GAIH3ULLFQ4DGSECF2AR555KZ4KNDGEKN4AFI4SU2M7B43MGK3QJZNSR" + } + ] + }, + "credits": { + "totalCount": 1, + "nodes": [ + { + "id": "0002576924543029249-0000000002", + "amount": "9999.9999900", + "accountId": "GAIH3ULLFQ4DGSECF2AR555KZ4KNDGEKN4AFI4SU2M7B43MGK3QJZNSR" + } + ] + } + } + } +} ``` ::: tip Note diff --git a/docs/quickstart/snippets/stellar-codegen.md b/docs/quickstart/snippets/stellar-codegen.md new file mode 100644 index 00000000000..4d7fe2e497c --- /dev/null +++ b/docs/quickstart/snippets/stellar-codegen.md @@ -0,0 +1,5 @@ +SubQuery simplifies and ensures type-safety when working with GraphQL entities, smart contracts, events, transactions, operation and effects. + + + +You can conveniently import all these types: diff --git a/docs/quickstart/snippets/stellar-handlers.md b/docs/quickstart/snippets/stellar-handlers.md new file mode 100644 index 00000000000..a1ab29d82bc --- /dev/null +++ b/docs/quickstart/snippets/stellar-handlers.md @@ -0,0 +1,6 @@ +- [BlockHandler](../../build/manifest/stellar.md#mapping-handlers-and-filters): On each and every block, run a mapping function +- [TransactionHandlers](../../build/manifest/stellar.md#mapping-handlers-and-filters): On each and every Stellar/Soroban transaction that matches optional filter criteria, run a mapping function +- [OperationHandler](../../build/manifest/stellar.md#mapping-handlers-and-filters): On each and every Stellar operation action that matches optional filter criteria, run a mapping function +- [EffectHandler](../../build/manifest/stellar.md#mapping-handlers-and-filters): On each and every Stellar effect action that matches optional filter criteria, run a mapping function +- [OperationHandler](../../build/manifest/stellar.md#mapping-handlers-and-filters): On each and every Stellar operation action that matches optional filter criteria, run a mapping function +- [EventHandler](../../build/manifest/stellar.md#mapping-handlers-and-filters): On each and every Soroban event action that matches optional filter criteria, run a mapping function diff --git a/docs/quickstart/snippets/stellar-manifest-intro.md b/docs/quickstart/snippets/stellar-manifest-intro.md new file mode 100644 index 00000000000..fc08887423c --- /dev/null +++ b/docs/quickstart/snippets/stellar-manifest-intro.md @@ -0,0 +1,19 @@ + + + + +For Stellar, there are several types of mapping handlers (and you can have more than one in each project): + + + + + + + + + +For Stellar, there are several types of mapping handlers (and you can have more than one in each project): + + + + diff --git a/docs/quickstart/snippets/stellar-manifest-note.md b/docs/quickstart/snippets/stellar-manifest-note.md new file mode 100644 index 00000000000..d3593a283d2 --- /dev/null +++ b/docs/quickstart/snippets/stellar-manifest-note.md @@ -0,0 +1,4 @@ +::: tip Note + +Check out our [Manifest File](../../build/manifest/stellar.md) documentation to get more information about the Project Manifest (`project.ts`) file. +::: diff --git a/docs/quickstart/snippets/stellar-mapping-note.md b/docs/quickstart/snippets/stellar-mapping-note.md new file mode 100644 index 00000000000..60baf664b22 --- /dev/null +++ b/docs/quickstart/snippets/stellar-mapping-note.md @@ -0,0 +1,3 @@ +::: tip Note +Check out our [Mappings](../../build/mapping/stellar.md) documentation to get more information on mapping functions. +:::