diff --git a/docs/api/sails-js/client-generation.md b/docs/api/sails-js/client-generation.md index 48b1c70..0b6d6a6 100644 --- a/docs/api/sails-js/client-generation.md +++ b/docs/api/sails-js/client-generation.md @@ -8,97 +8,269 @@ import TabItem from '@theme/TabItem'; # Client Generation -One of the key features of the `sails-js` library is its ability to generate working client code from IDL files. In particular for smaller projects or for application testing this approach offers a quick way for developers to start interacting with their application on Vara Network. +The `sails-js-cli` is a command-line tool designed to generate TypeScript client libraries from Sails IDL files. It automates the creation of fully functional client libraries based on the interfaces defined in the Sails framework, streamlining development and ensuring consistency between client and on-chain programs. -## Generation with `sails-js-cli` +## Installation -The `sails-js-cli` is a command-line tool designed to generate TypeScript client libraries from Sails IDL files. It automates the process of creating fully functional client libraries based on the interfaces defined in the Sails framework, streamlining development and ensuring consistency between client and on-chain programs. - -### Installation -To install the `sails-js-cli` package globally, use the following command: +To install the `sails-js-cli` package globally, run the following command: ```bash npm install -g sails-js-cli ``` -Alternatively, you can run the command without installing the package by using `npx`: +Alternatively, you can use `npx` to run the command without installing the package: ```bash -npx sails-js-cli command +npx sails-js-cli command ...args ``` -### Usage +## Generating a TypeScript Client Library Using an IDL File + To generate a TypeScript client library, run the following command: ```bash sails-js generate path/to/sails.idl -o path/to/out/dir ``` +If you want to avoid global installation, use `npx`: + +```bash +npx sails-js-cli generate path/to/sails.idl -o path/to/out/dir +``` + To generate only the `lib.ts` file without the full project structure, use the `--no-project` flag: ```bash sails-js generate path/to/sails.idl -o path/to/out/dir --no-project ``` -## Using a Generated Client Library +React hooks generation is available via the `--with-hooks` flag: + +```bash +sails-js generate path/to/sails.idl -o path/to/out/dir --with-hooks +``` + +## Using the Generated Library + +### Creating an Instance + +First, connect to the chain using `@gear-js/api`: + +```javascript +import { GearApi } from '@gear-js/api'; + +const api = await GearApi.create(); +``` + +Next, import the `Program` class from the generated file and create an instance: + +```javascript +import { Program } from './lib'; + +const program = new Program(api); + +// If the program is already deployed, provide its ID +const programId = '0x...'; +const program = new Program(api, programId); +``` + +The `Program` class includes all the functions defined in the IDL file. + +## Methods of the `Program` Class + +The `Program` class contains several types of methods: + +- Query methods +- Message methods +- Constructor methods +- Event subscription methods + +### Query Methods + +Query methods are used to query the program's state. These methods accept the required arguments for the function call and return the result. Additionally, they accept optional parameters: `originAddress` (the caller's account address, defaulting to a zero address if not provided), `value` (an optional amount of tokens for function execution), and `atBlock` (to query the program state at a specific block). + +```javascript +const alice = '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d'; +const result = await program.serviceName.queryFnName(arg1, arg2, alice); +console.log(result); +``` + +### Message Methods + +Message methods are used to send messages to the program. These methods accept the required arguments for sending the message and return a [Transaction Builder](../README.md#transaction-builder), which contains methods for building and sending the transaction. + +```javascript +const transaction = program.serviceName.functionName(arg1, arg2); + +// ## Set the account sending the message + +// Using a KeyringPair instance +import { Keyring } from '@polkadot/api'; +const keyring = new Keyring({ type: 'sr25519' }); +const pair = keyring.addFromUri('//Alice'); +transaction.withAccount(pair); + +// Or using an address and signer options (common in frontend applications with connected wallets) +import { web3FromSource, web3Accounts } from '@polkadot/extension-dapp'; +const allAccounts = await web3Accounts(); +const account = allAccounts[0]; +const injector = await web3FromSource(account.meta.source); +transaction.withAccount(account.address, { signer: injector.signer }); + +// ## Set the value of the message +transaction.withValue(BigInt(10 * 1e12)); // 10 VARA + +// ## Calculate gas +// Optionally, you can provide two arguments: +// The first argument, `allowOtherPanics`, either allows or forbids panics in other programs (default: false). +// The second argument, `increaseGas`, is the percentage to increase the gas limit (default: 0). +await transaction.calculateGas(); + +// Alternatively, use `withGas` to set the gas limit manually: +transaction.withGas(100000n); + +// ## Send the transaction +// `signAndSend` returns the message ID, block hash, and a `response` function for retrieving the program's response. +const { msgId, blockHash, response } = await transaction.signAndSend(); +const result = await response(); +console.log(result); +``` + +### Constructor Methods + +Constructor methods, postfixed with `CtorFromCode` and `CtorFromCodeId` in the `Program` class, are used to deploy the program on the chain. These methods accept either the Wasm code bytes or the code ID and return a [Transaction Builder](../README.md#transaction-builder) just like message methods. + +```javascript +const code = fs.readFileSync('path/to/program.wasm'); +// Or use the fetch function to retrieve the code in frontend environments +const transaction = program.newCtorFromCode(code); + +// The same methods as message methods can be used to build and send the transaction. +``` + +### Event Subscription Methods + +Event subscription methods allow subscribing to specific events emitted by the program. + +```javascript +program.subscribeToSomeEvent((data) => { + console.log(data); +}); +``` + +## React Hooks + +Generating the library with the `--with-hooks` flag creates custom React hooks that simplify interaction with a `sails-js` program. These hooks are wrappers around the generic hooks provided by the `@gear-js/react-hooks` library. They are generated based on the `Program` class in `lib.ts`, tailored to the specific types and names derived from it. + +Refer to the `@gear-js/react-hooks` [README](https://github.com/gear-tech/gear-js/tree/main/utils/gear-hooks#sails) on GitHub for more details on these hooks. + +### `useProgram` + +Initializes the program with the provided parameters. + +```jsx +import { useProgram } from './hooks'; + +const { data: program } = useProgram({ id: '0x...' }); +``` + +### `useSend` for `serviceName` and `functionName` Transaction -The generated library consists of a `program` class, which represents the Sails application and handles initialization and deployment and one additional class for each service of the application. +Sends a transaction to a specified service and function. -### The `program` Class -The `program` class initializes the connection to the Sails program. It manages the program's ID and provides methods to deploy the program to Vara Network. The constructor accepts a `GearApi` instance for interacting with Vara Network and an optional `programId` representing the address of the deployed application. -```js -constructor(public api: GearApi, public programId?: `0x${string}`) { ... } +```jsx +import { useProgram, useSendAdminMintTransaction } from './hooks'; + +const { data: program } = useProgram({ id: '0x...' }); +const { sendTransaction } = useSendAdminMintTransaction({ program }); ``` -The `newCtorFromCode` method creates a `TransactionBuilder` to deploy the program using the provided code bytes, setting the `programId` upon deployment. -```js -newCtorFromCode(code: Uint8Array | Buffer): TransactionBuilder { ... } + +### `usePrepare` for `serviceName` and `functionName` Transaction + +Prepares a transaction for a specified service and function. + +```jsx +import { useProgram, usePrepareAdminMintTransaction } from './hooks'; + +const { data: program } = useProgram({ id: '0x...' }); +const { prepareTransaction } = usePrepareAdminMintTransaction({ program }); ``` -Similarly, the `newCtorFromCodeId` method deploys the program using an existing `codeId` on Vara Network and sets the `programId` after deployment. -```js -newCtorFromCodeId(codeId: `0x${string}`): TransactionBuilder { ... } + +### `use` for `serviceName` and `functionName` Query + +Queries a specified service and function. + +```jsx +import { useProgram, useErc20BalanceOfQuery } from './hooks'; + +const { data: program } = useProgram({ id: '0x...' }); +const { data } = useErc20BalanceOfQuery({ program, args: ['0x...'] }); ``` -### Example: Querying a Sails Application +### `use` for `serviceName` and `functionName` Event + +Subscribes to events from a specified service and event. + +```jsx +import { useProgram, useAdminMintedEvent } from './hooks'; + +const { data: program } = useProgram({ id: '0x...' }); +const { data } = useAdminMintedEvent({ program, onData: (value) => console.log(value) }); +``` + +## Example: The Demo Project + +The following example demonstrates how the generated `lib.ts` can be used in conjunction with `@gear-js/api` to upload a WASM binary of an application to the chain, create and submit a service command to the deployed application, and receive its response. This example is part of the Sails GitHub repository and can be viewed [here](https://github.com/gear-tech/sails/tree/master/js/example). Although the project includes multiple services, this example focuses on interacting with the `pingPong` service. The relevant portion of the IDL looks like this: + +```rust +service PingPong { + Ping : (input: str) -> result (str, str); +}; +``` + +The script first initializes the `GearApi` and creates a keyring to retrieve Alice’s account using the `@polkadot/api` library, which is used for signing transactions. It then reads the compiled WASM application and deploys it to the network using `newCtorFromCode` from the generated `lib.ts`, which creates a `TransactionBuilder` for constructing the deployment transaction. After deployment, the script interacts with the program by invoking the `ping` function from the `pingPong` service, constructing and sending a new transaction. Finally, the program's reply is awaited and logged to the console. + +```jsx +import { GearApi } from '@gear-js/api'; +import { Keyring } from '@polkadot/api'; +import { Program } from './lib.js'; +import { readFileSync } from 'fs'; -The following code snippet shows how to instantiate a `program` object using a `GearApi` instance and issuing a call to a simple query as implemented by the `varaApp` service. +const main = async () => { + const api = await GearApi.create(); + const keyring = new Keyring({ type: 'sr25519', ss58Format: 137 }); - - - ```js - import { GearApi } from '@gear-js/api'; - import { program } from './lib'; // Import Program from lib.ts + const alice = keyring.addFromUri('//Alice'); - async function main() { - const api = await GearApi.create({ providerAddress: 'wss://testnet.vara.network' }); + const program = new Program(api); - // Use the Program class - const programId = '0x'; - const prgm = new program(api, programId); + // Deploy the program - // Access varaApp and call getSomething - const alice = 'kGkLEU3e3XXkJp2WK4eNpVmSab5xUNL9QtmLPh8QfCL2EgotW'; - const result = await prgm.varaApp.getSomething(alice); - - console.log(result); - } - main().catch(console.error); - ``` - - - ```rust - constructor { - New : (); - }; + const code = readFileSync('../../target/wasm32-unknown-unknown/release/demo.opt.wasm'); - service VaraApp { - DoSomething : () -> str; - query getSomething : () -> str; - }; - ``` - - + const ctorBuilder = await program.newCtorFromCode(code, null, null).withAccount(alice).calculateGas(); + const { blockHash, msgId, txHash } = await ctorBuilder.signAndSend(); + console.log( + `\nProgram deployed. \n\tprogram id ${program.programId}, \n\tblock hash: ${blockHash}, \n\ttx hash: ${txHash}, \n\tinit message id: ${msgId}`, + ); + // Call the program + const pingBuilder = await program.pingPong.ping('ping').withAccount(alice).calculateGas(); + const { blockHash: blockHashPing, msgId: msgIdPing, txHash: txHashPing, response } = await pingBuilder.signAndSend(); + console.log( + `\nPing message sent. \n\tBlock hash: ${blockHashPing}, \n\ttx hash: ${txHashPing}, \n\tmessage id: ${msgIdPing}`, + ); + const reply = await response(); + console.log(`\nProgram replied: \n\t${JSON.stringify(reply)}`); +}; +main() + .then(() => process.exit(0)) + .catch((error) => { + console.log(error); + process.exit(1); + }); +``` \ No newline at end of file diff --git a/docs/api/sails-js/overview.md b/docs/api/sails-js/overview.md index f69fafb..2539e22 100644 --- a/docs/api/sails-js/overview.md +++ b/docs/api/sails-js/overview.md @@ -4,7 +4,18 @@ sidebar_label: Library Overview --- # Library Overview -## Parse IDL + +The `sails-js` library workflow begins by creating a `Sails` object through parsing a provided IDL description. This process maps the interface defined by the IDL to corresponding objects in the `Sails` instance, including: + +- Constructors +- Services +- Functions (referred to as Commands in the Sails framework) +- Queries +- Events + +The library also offers methods for decoding information from payloads and encoding data to be sent to a program via transactions. The `TransactionBuilder` class facilitates the building and sending of these transactions to the blockchain. For further details, refer to the [Transaction Builder](#transaction-builder) section. + +## Parsing an IDL Description ```javascript import { Sails } from 'sails-js'; @@ -22,6 +33,7 @@ The `sails` object now contains all the constructors, services, functions, and e To send messages, create programs, and subscribe to events using `Sails`, you need to connect to the chain using `@gear-js/api` and set the `GearApi` instance using the `setApi` method. + ```javascript import { GearApi } from '@gear-js/api'; @@ -29,7 +41,7 @@ const api = await GearApi.create(); sails.setApi(api); ``` - +## The `Sails` Class ### Constructors The `sails.ctors` property contains an object with all the constructors available in the IDL file. The keys are the constructor names, and each value is an object with the following properties: @@ -54,8 +66,6 @@ To get a constructor object, use `sails.ctors.ConstructorName`. The `fromCode` and `fromCodeId` methods return an instance of the `TransactionBuilder` class, which can be used to build and send the transaction to the chain. -For more information, check the [Transaction Builder](#transaction-builder) section. - ### Services The `sails.services` property contains an object with all the services available in the IDL file. The keys are the service names, and each value is an object with the following properties: @@ -90,8 +100,6 @@ It's necessary to provide a program ID so that the function can be called. You c sails.setProgramId('0x...'); ``` -For more information about the `TransactionBuilder` class, refer to the [Transaction Builder](#transaction-builder) section. - To create a transaction for a function call, you can do the following: ```javascript diff --git a/docs/api/sails-js/transaction-builder.md b/docs/api/sails-js/transaction-builder.md index ca2ab4d..7e8edd6 100644 --- a/docs/api/sails-js/transaction-builder.md +++ b/docs/api/sails-js/transaction-builder.md @@ -9,123 +9,126 @@ The `TransactionBuilder` class is used to build and send transactions to the cha Use the `.programId` property to get the ID of the program that is used in the transaction. -### Methods to Build and Send the Transaction +## Methods to Build and Send the Transaction +### `withAccount` -- **`withAccount`** - Sets the account that is sending the message. +The `withAccount` method sets the account that is sending the message. It accepts either a `KeyringPair` instance or an address with `signerOptions`. - *This method accepts either a `KeyringPair` instance or an address with `signerOptions`.* +**Example using a `KeyringPair` instance:** - **Example using a `KeyringPair` instance:** +```jsx +import { Keyring } from '@polkadot/api'; +const keyring = new Keyring({ type: 'sr25519' }); +const pair = keyring.addFromUri('//Alice'); - ```javascript - import { Keyring } from '@polkadot/api'; - const keyring = new Keyring({ type: 'sr25519' }); - const pair = keyring.addFromUri('//Alice'); +// Set the account using the KeyringPair instance +transaction.withAccount(pair); +``` - // Set the account using the KeyringPair instance - transaction.withAccount(pair); - ``` +**Example using an address and signer options (commonly used on the frontend with a connected wallet):** - **Example using an address and signer options (commonly used on the frontend with a connected wallet):** +```jsx +// This case is mostly used on the frontend with a connected wallet. +import { web3FromSource, web3Accounts } from '@polkadot/extension-dapp'; - ```javascript - // This case is mostly used on the frontend with a connected wallet. - import { web3FromSource, web3Accounts } from '@polkadot/extension-dapp'; +// Retrieve all accounts from the wallet extension +const allAccounts = await web3Accounts(); +const account = allAccounts[0]; - // Retrieve all accounts from the wallet extension - const allAccounts = await web3Accounts(); - const account = allAccounts[0]; +// Get the injector for the account's source +const injector = await web3FromSource(account.meta.source); - // Get the injector for the account's source - const injector = await web3FromSource(account.meta.source); +// Set the account address and signer in the transaction +transaction.withAccount(account.address, { signer: injector.signer }); +``` - // Set the account address and signer in the transaction - transaction.withAccount(account.address, { signer: injector.signer }); - ``` +### `withValue` -- **`withValue`** - Sets the value (amount) to be sent with the message. +The `withValue` method sets the value (amount) to be sent with the message. - **Example:** +**Example:** - ```javascript - // Set the value to 10 VARA - transaction.withValue(BigInt(10 * 1e12)); - ``` +```jsx +// Set the value to 10 VARA +transaction.withValue(BigInt(10 * 1e12)); +``` -- **`calculateGas`** - Calculates the gas limit of the transaction. +### `calculateGas` +The `calculateGas` method calculates the gas limit of the transaction. Optionally, you can provide two arguments: +- The first argument, `allowOtherPanics`, determines whether panics in other programs are allowed to be triggered. It is set to `false` by default. +- The second argument, `increaseGas`, is the percentage to increase the gas limit. It is set to `0` by default. - Optionally, you can provide two arguments: +**Example:** - - The first argument, `allowOtherPanics`, determines whether panics in other programs are allowed to be triggered. It is set to `false` by default. - - The second argument, `increaseGas`, is the percentage to increase the gas limit. It is set to `0` by default. +```jsx +// Calculate gas limit with default options +await transaction.calculateGas(); - **Example:** +// Calculate gas limit allowing other panics and increasing gas limit by 10% +await transaction.calculateGas(true, 10); +``` - ```javascript - // Calculate gas limit with default options - await transaction.calculateGas(); +### `withGas` - // Calculate gas limit allowing other panics and increasing gas limit by 10% - await transaction.calculateGas(true, 10); - ``` +The `withGas` method manually sets the gas limit of the transaction. This can be used instead of the `calculateGas` method. -- **`withGas`** - Manually sets the gas limit of the transaction. This can be used instead of the `calculateGas` method. +**Example:** - **Example:** +```jsx +// Set the gas limit manually +transaction.withGas(100_000_000_000n); +``` - ```javascript - // Set the gas limit manually - transaction.withGas(100_000_000_000n); - ``` +### `withVoucher` -- **`withVoucher`** - Sets the voucher ID to be used for the transaction. +The `withVoucher` method sets the voucher ID to be used for the transaction. - **Example:** +**Example:** - ```javascript - const voucherId = '0x...'; // Replace with actual voucher ID - transaction.withVoucher(voucherId); - ``` +```jsx +const voucherId = '0x...'; // Replace with actual voucher ID +transaction.withVoucher(voucherId); +``` -- **`transactionFee`** - Retrieves the transaction fee. +### `transactionFee` - **Example:** +The `transactionFee` method retrieves the transaction fee. - ```javascript - const fee = await transaction.transactionFee(); - console.log('Transaction fee:', fee.toString()); - ``` +**Example:** -- **`signAndSend`** - Sends the transaction to the chain. - - This method returns an object containing: +```jsx +const fee = await transaction.transactionFee(); +console.log('Transaction fee:', fee.toString()); +``` +### `signAndSend` +The `signAndSend` method sends the transaction to the chain. This method returns an object containing: - `msgId`: the ID of the sent message. - `txHash`: the transaction hash. - `blockHash`: the hash of the block in which the message is included. - `isFinalized`: a function to check if the transaction is finalized. - `response`: a function that can be used to get the response from the program. - The `response` function returns a promise with the response from the program. If an error occurs during message execution, the promise will be rejected with the error message. +The `response` function returns a promise with the response from the program. If an error occurs during message execution, the promise will be rejected with the error message. - **Example:** +**Example:** - ```javascript - const { msgId, blockHash, txHash, response, isFinalized } = await transaction.signAndSend(); +```jsx +const { msgId, blockHash, txHash, response, isFinalized } = await transaction.signAndSend(); - console.log('Message ID:', msgId); - console.log('Transaction hash:', txHash); - console.log('Block hash:', blockHash); +console.log('Message ID:', msgId); +console.log('Transaction hash:', txHash); +console.log('Block hash:', blockHash); - // Check if the transaction is finalized - const finalized = await isFinalized; - console.log('Is finalized:', finalized); +// Check if the transaction is finalized +const finalized = await isFinalized; +console.log('Is finalized:', finalized); - // Get the response from the program - try { - const result = await response(); - console.log('Program response:', result); - } catch (error) { - console.error('Error executing message:', error); - } - ``` \ No newline at end of file +// Get the response from the program +try { + const result = await response(); + console.log('Program response:', result); +} catch (error) { + console.error('Error executing message:', error); +} +``` \ No newline at end of file