Skip to content

Latest commit

 

History

History

js

Overview

This directory contains libraries for interacting with programs built using the Sails framework and generating TypeScript client libraries from Sails IDL.

sails-js - A library that can be used both independently and in generated clients to interact with programs via IDL files.

sails-js-cli - CLI tool to generate TypeScript client libraries from Sails IDL files.

sails-js-parser - Parser library for IDL files, used to generate AST (utilized by the other two libraries).

sails-js-types - Library with types used across libraries.

sails-js-util - Utility functions used across libraries.

Installation

The sails-js library requires the @gear-js/api and @polkadot/api packages to be installed.

To install sails-js, run the following command:

npm install sails-js

Usage

Library

Parse IDL

import { Sails } from 'sails-js';
import { SailsIdlParser } from 'sails-js-parser';

const parser = await SailsIdlParser.new();
const sails = new Sails(parser);

const idl = '<idl content>';

sails.parseIdl(idl);

sails object contains all the constructors, services, functions and events available in the IDL file. To send messages, create programs and subscribe to events using Sails it's necessary to connect to the chain using @gear-js/api and set GearApi instance using setApi method.

import { GearApi } from '@gear-js/api';

const api = await GearApi.create();

sails.setApi(api);

Constructors

sails.ctors property contains an object with all the constructors available in the IDL file. The key of the object is the name of the constructor and the value is an object with the following properties:

{
  args: Array<{name: string, type: string}>, // array of arguments with their names and scale codec types
  encodePayload: (...args: any): HexString, // function to encode the payload
  decodePayload: (bytes: HexString): any, // function to decode the payload
  fromCode: (code: Uint8Array | Buffer, ...args: unkonwn[]): TransactionBuilder, // function to create a transaction builder to deploy the program using code bytes
  fromCodeId: (codeId: string, ...args: unknown[]): TransactionBuilder // function to create a transaction builder to deploy the program using code id
}

To get the constructor object use sails.ctors.ConstructorName

fromCode and fromCodeId methods return an instance of TransactionBuilder class that can be used to build and send the transaction to the chain. Check the Transaction builder section for more information.

Services

sails.services property contains an object with all the services available in the IDL file. The key of the object is the name of the service and the value is an object with the following properties:

{
  functions: Record<string, SailsServiceFunc>, // object with all the functions available in the service
  queries: Record<string, SailsServiceQuery>, // object with all the queries available in the service
  events: Record<string, SailsServiceEvent>, // object with all the events available in the service
}

To get the service object use sails.services.ServiceName

Functions

sails.services.ServiceName.functions property contains an object with all the functions from the IDL file that can be used to send messages to the program. The key of the object is the name of the function and the value can be used either as a function that accepts function arguments and returns instance of TransactionBuilder class or as an object with the following properties:

{
  args: Array<{name: string, type: string}>, // array of arguments with their names and scale codec types
  returnType: any, // scale codec definition of the return type
  encodePayload: (...args: any): Uint8Array, // function to encode the payload
  decodePayload: (bytes: Uint8Array): any, // function to decode the payload
  decodeResult: (result: Uint8Array): any // function to decode the result
}

It's necessary to provide program id so that the function can be called. It can be done using .setProgramId method of the Sails class

sails.setProgramId('0x...');

Check the Transaction builder section for more information about the TransactionBuilder class.

const transaction = sails.services.ServiceName.functions.FunctionName(arg1, arg2);

Queries

sails.services.ServiceName.queries property contains an object with all the queries from the IDL file that can be used to read the program state. The key of the object is the name of the function. The value includes the same properties as described in the Functions section above. Note that the function returns the result of the query, not the transaction builder.

The query function accepts 3 more arguments in addition to arguments from the IDL:

  • originAddress - the address of the account that is calling the function
  • value - (optional, default 0) the amount of tokens that are sent to the function
  • atBlock - (optional) the block at which the query is executed
const alice = 'kGkLEU3e3XXkJp2WK4eNpVmSab5xUNL9QtmLPh8QfCL2EgotW';
// functionArg1, functionArg2 are the arguments of the query function from the IDL file
const result = await sails.services.ServiceName.queries.QueryName(alice, null, null, functionArg1, functionArg2);

console.log(result);

Events

sails.services.ServiceName.events property contains an object with all the events available in the IDL file. The key of the object is the name of the event and the value is an object with the following properties:

{
  type: any, // scale codec definition of the event
  is: (event: UserMessageSent), // function to check if the event is of the specific type
  decode: (data: Uint8Array): any // function to decode the event data
  subscribe: (callback: (data: any) => void | Promise<void>) => void // function to subscribe to the event
}

To subscribe to the event use subscribe method of the event object.

sails.services.ServiceName.events.EventName.subscribe((data) => {
  console.log(data);
});

Get function name and decode bytes

Use getServiceNamePrefix function to get the service name from the payload bytes. Use getFnNamePrefix method to get the function or event name from the payload bytes. Use sails.services.ServiceName.functions.FuncitonName.decodePayload method of the function object to decode the payload bytes of the send message. Use sails.services.ServiceName.functions.FuncitonName.decodeResult method of the function object to decode the result bytes of the received message.

import { getServiceNamePrefix, getFnNamePrefix } from 'sails-js';
const payloadOfSentMessage = '0x<some bytes>';
const serviceName = getServiceNamePrefix(payloadOfSentMessage);
const functionName = getFnNamePrefix(payloadOfSentMessage);
console.log(sails.services[serviceName].functions[functionName].decodeResult(payloadOfSentMessage));

const payloadOfReceivedMessage = '0x<some bytes>';
console.log(sails.service[serviceName].functions[functionName].decodePayload(payloadOfReceivedMessage));

The same approach can be used to encode/decode bytes of the contructor or event.

sails.ctors.ConstructorName.encodePayload(arg1, arg2);
sails.ctors.ConstructorName.decodePayload('<some bytes>');

sails.events.EventName.decode('<some bytes>')

Encode payload

Use sails.services.ServiceName.functions.FunctionName.encodePayload method of the function object to encode the payload for the specific function. The bytes returned by this method can be used to send the message to the program.

const payload = sails.functions.SomeFunction.encodePayload(arg1, arg2);

Transaction builder

TransactionBuilder class is used to build and send the transaction to the chain.

Use .programId property to get the id of the program that is used in the transaction.

Methods to build and send the transaction

  • withAccount - sets the account that is sending the message The method accepts either the KeyringPair instance or the address and signerOptions
import { Keyring } from '@polkadot/api';
const keyring = new Keyring({ type: 'sr25519' });
const pair = keyring.addFromUri('//Alice');
transaction.withAccount(pair)

// This case is mostly used on the frontend with connected wallet.
import { web3FromSource, web3Accounts } from '@polkadot/extension-dapp';
const allAccounts = await web3Accounts();
const account = allAccounts[0];
const injector = await web3FromSource(account.meta.source);
const signer = web3FromSource();
transaction.withAccount(account.address, { signer: injector.signer });
  • withValue - sets the value of the message
transaction.withValue(BigInt(10 * 1e12)); // 10 VARA
  • calculateGas - calculates the gas limit of the transaction Optionally you can provide 2 arguments.
    • The first argument allowOtherPanics either allows or forbids panics in other programs to be triggered. It's set to false by default.
    • The second argument increaseGas is percentage to increase the gas limit. It's set to 0 by default.
await transaction.calculateGas();
  • withGas - sets the gas limit of the transaction manually. Can be used instead of calculateGas method.
transaction.withGas(100_000_000_000n);
  • withVoucher - sets the voucher id to be used for the transaction
const voucherId = '0x...'
transaction.withVoucher(voucherId);
  • transactionFee - get the transaction fee
const fee = await transaction.transactionFee();
console.log(fee);
  • signAndSend - sends the transaction to the chain Returns the id of the sent message, transaction hash, the block hash in which the message is included, isFinalized to check if the transaction is finalized and response 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.
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('Is finalized:', await isFinalized);

const result = await response();
console.log(result);