Skip to content

Latest commit

 

History

History
670 lines (480 loc) · 26.9 KB

File metadata and controls

670 lines (480 loc) · 26.9 KB

Smart Contract Client Generator

Generate TypeScript/JavaScript code for interating with smart contracts and modules on the Concordium blockchain.

  • Functions for instantiating new smart contract instances from a module.
  • Functions for dry-running and calling entrypoints of the smart contract.
  • Functions for constructing parameters.
  • Parsing logged events, return values and error messages.
  • Structured types for parameter, logged events, return values and error messages.

The code is generated from deployable smart contract modules, meaning it can be done with any smart contract available locally and any smart contract deployed on chain.

Example usage of a generated client

An example of using a generated client for a token smart contract implementing the CIS-2 standard. In the example, a contract client is constructed and a transaction calling the transfer entrypoint of the smart contract. The parameter includes a transfer of 10 tokens from sender address to receiver address.

import * as SDK from "@concordium/web-sdk";
import * as MyContract from "./generated/my-contract.js"; // Code generated from a smart contract module.

const grpcClient = // Setup gRPC client code here ...
const contractAddress = // Address of the smart contract instance.
const signer = // Keys for signing an account transaction.
const sender = // The AccountAddress sending the tokens and signing the transaction.
const receiver = // The AccountAddress receiving the tokens.

// Create a client for 'my-contract' smart contract.
const contractClient = await MyContract.create(
    grpcClient,
    contractAddress
);

// Construct the parameter for the 'transfer' entrypoint. The structure of the parameter depends on the contract.
const parameter: MyContract.TransferParameter = [{
    tokenId: "",
    amount: 10,
    from: { type: 'Account', content: sender },
    to: { type: 'Account', content: receiver },
    data: "",
}];

// Send transaction for invoking the 'transfer' entrypoint of the smart contract.
const transactionHash = await MyContract.sendTransfer(contractClient, {
    senderAddress: sender,
    energy: SDK.Energy.create(12000) // The amount of energy needed will depend on the contract.
}, parameter, signer);

Install the package

Install the package, saving it to devDependencies:

npm

npm install --save-dev @concordium/ccd-js-gen

yarn

yarn add --dev @concordium/ccd-js-gen

pnpm

pnpm install --save-dev @concordium/ccd-js-gen

The package can also be used directly using npx, without first adding it as a dependency, however it is recommended to add it in package.json to keep track the exact version used when generating the code, as different version might produce different code.

Using the CLI

This package provides the ccd-js-gen command, which can be used from the commandline.

Options:

  • -m, --module <path> Path to the smart contract module.
  • -o, --out-dir <path> Directory to use for the generated code.
  • -t, --output-type <TypeScript|JavaScript|TypedJavaScript|Everything> The output file types for the generated code. Defaults to Everything.
  • -n, --ts-nocheck Generate @ts-nocheck annotations at the top of each typescript file.

Example

To generate smart contract clients into a directory generated from the smart contract module ./my-contract.wasm.v1:

ccd-js-gen --module ./my-contract.wasm.v1 --out-dir ./generated

For a dapp project, it is recommended to have this as part of a script in package.json.

Using the library

This package can be used programmatically as well.

import * as ccdJsGen from "@concordium/ccd-js-gen"

Generate from smart contract module file

To generate smart contract clients for a smart contract module file, either downloaded from the blockchain or build from the smart contract source code:

import * as ccdJsGen from "@concordium/ccd-js-gen"

const moduleFilePath = "./my-contract.wasm.v1"; // Path to smart contract module.
const outDirPath = "./generated"; // The directory to use for the generated files.

// Read the module and generate the smart contract clients.
console.log('Generating smart contract module clients.')
await ccdJsGen.generateContractClientsFromFile(moduleFilePath, outDirPath);
console.log('Code generation was successful.')

Generate from smart contract module on chain

To generate smart contract clients for a smart contract module on chain, you need access to the gRPC of a Concordium node. Use @concordium/web-sdk to download the smart contract module and use it to generate the clients.

import * as ccdJsGen from "@concordium/ccd-js-gen"
import * as SDK from "@concordium/web-sdk"

const outDirPath = "./generated"; // The directory to use for the generated files.
const outputModuleName = "wCCD-module"; // The name to give the output smart contract module.

const grpcClient = ...; // A Concordium gRPC client from @concordium/web-sdk.
const moduleRef = SDK.ModuleReference.fromHexString('<hex of module referecene>');

// Fetch the smart contract module source from chain.
const moduleSource = await grpcClient.getModuleSource(moduleRef);

// Generate the smart contract clients from module source.
console.info('Generating smart contract module clients.');
await ccdJsGen.generateContractClients(moduleSource, outputModuleName, outDirPath);
console.info('Code generation was successful.');

Using the generated client

The generator produces a file with functions for interacting with the smart contract module and files for each smart contract in the smart contract module used.

For example: generating clients for a smart contract module my-module containing the smart contracts my-contract-a and my-contract-b. into the directory ./generated produces the following structure:

generated/
├─ my-module.js        // Functions for interacting with the 'my-module' smart contract module on chain.
├─ my-module_my-contract-a.js    // Functions for interacting with the 'my-contract-a' smart contract.
└─ my-module_my-contract-b.js    // Functions for interacting with the 'my-contract-b' smart contract.

There might also be type declarations (<file>.d.ts) for TypeScript, depending on the provided options.

Generated module client

A file is produced with a client for interacting with the smart contract module. This provides functions for instantiating smart contract instances.

An example of importing a smart contract module generated from a my-module.wasm.v1 file:

import * as MyModule from "./generated/my-module.js";

The client type

The type representing the client for the smart contract module is accessable using MyModule.Type.

function create

Construct a module client for interacting with a smart contract module on chain. This function ensures the smart contract module is deployed on chain and throws an error otherwise.

Parameter: The function takes a gRPC client from '@concordium/web-sdk'.

const grpcClient = ...; // Concordium gRPC client from '@concordium/web-sdk'.
const myModule: MyModule.Type = await MyModule.create(grpcClient);

function createUnchecked

Construct a module client for interacting with a smart contract module on chain. This function skips the check of the smart contract module being deployed on chain and leaves it up to the caller to ensure this.

Parameter: The function takes a gRPC client from '@concordium/web-sdk'.

const grpcClient = ...; // Concordium gRPC client from '@concordium/web-sdk'.
const myModule: MyModule.Type = MyModule.createUnchecked(grpcClient);

To run the checks manually use await MyModule.checkOnChain(myModule);.

const moduleReference

Variable with the reference of the smart contract module.

const ref = MyModule.moduleReference;

function getModuleSource

Get the module source of the deployed smart contract module from chain.

const myModule: MyModule.Type = ...; // Generated module client
const moduleSource = await MyModule.getModuleSource(myModule);

type <ContractName>Parameter

Type representing the parameter for when instantiating a smart contract. The type is named <ContractName>Parameter where <ContractName> is the smart contract name in Pascal case.

This is only generated when the schema contains init-function parameter type.

function instantiate<ContractName>

For each smart contract in module a function for instantiating new instance is produced. These are named instantiate<ContractName> where <ContractName> is the smart contract name in Pascal case.

An example for a smart contract module containing a smart contract named my-contract, the function becomes instantiateMyContract.

Parameters

The function parameters are:

  • moduleClient The client of the on-chain smart contract module.

  • transactionMetadata Metadata related to constructing the transaction, such as energy and CCD amount to include.

    • senderAddress The address invoking this call.
    • energy The energy reserved for executing this transaction.
    • amount The amount of CCD included in the transaction. Defaults to 0.
    • expiry Expiry date of the transaction. Defaults to 5 minutes from when constructing the transaction.
  • parameter Parameter to provide the smart contract module for the instantiation.

    With schema type: If the schema contains type information for the parameter, a type for the parameter is generated and used for this function. The type is named <ContractName>Parameter where <ContractName> is the smart contract name in Pascal case.

    Without schema type: If no schema information is present, the function uses the generic Parameter from @concordium/web-sdk.

  • signer The keys to use for signing the transaction.

Returns: Promise with the hash of the transaction.

// Generated module client.
const myModule: MyModule.Type = await MyModule.create(grpcClient);
// The keys to use for signing the transaction.
const signer = ...;
// Transaction meta information.
const transactionMeta = {
  // The account address signing the transaction.
  senderAddress: SDK.AccountAddress.fromBase58("357EYHqrmMiJBmUZTVG5FuaMq4soAhgtgz6XNEAJaXHW3NHaUf"),
  // The amount of energy needed will depend on the contract.
  energy: SDK.Energy.create(12000),
};
// Parameter to pass the smart contract init-function. The structure depends on the contract.
const parameter = ...;
const transactionHash = await MyModule.instantiateMyContract(myModule, transactionMeta, parameter, signer);

function create<ContractName>ParameterWebWallet

For each smart contract in module a function for constructing the WebWallet formattet parameter for initializing a new instance is produced. This is to be used with the @concordium/wallet-connector package. These are named create<ContractName>ParameterWebWallet where <ContractName> is the smart contract name in Pascal case.

An example for a smart contract module containing a smart contract named my-contract, the function becomes createMyContractParameterWebWallet.

This is only generated when the schema contains contract initialization parameter type.

Parameter

The function parameter is:

  • parameter Parameter to provide the smart contract module for the instantiation.

Returns: Parameter for initializing a contract instance in the format used by @concordium/wallet-connector.

// Wallet connection from `@concordium/wallet-connector`.
const webWalletConnection: WalletConnection = ...;
// Parameter to pass the smart contract init-function. The structure depends on the contract.
const parameter: MyModule.MyContractParameter = ...;
// Convert the parameter into the format expected by the wallet.
const walletParameter = MyModule.createMyContractParameterWebWallet(parameter);
// Use wallet connection to sign and send the transaction.
const sender = ...;
const transactionHash = await webWalletConnection.signAndSendTransaction(
    sender,
    AccountTransactionType.InitContract,
    walletParameter
);

Generated contract client

For each of the smart contracts in the module a file is produced named after the smart contract. Each file contains functions for interacting with an instance of this smart contract.

An example of importing a smart contract contract client generated from a module containing a smart contract named my-contract:

import * as MyContract from "./generated/my-module_my-contract.js";

The contract client type

The type representing the client for the smart contract instance is accessable using MyContract.Type.

function create

Construct a client for interacting with a smart contract instance on chain. This function ensures the smart contract instance exists on chain, and that it is using a smart contract module with a matching reference.

Parameters:

  • grpcClient The function takes a gRPC client from @concordium/web-sdk.
  • contractAddress The contract address of the smart contract instance.
const grpcClient = ...; // Concordium gRPC client from '@concordium/web-sdk'.
const contractAddress = SDK.ContractAddress.create(...); // The address of the contract instance.
const myContract: MyContract.Type = await MyContract.create(grpcClient, contractAddress);

function createUnchecked

Construct a client for interacting with a smart contract instance on chain. This function skips the check ensuring the smart contract instance exists on chain, and that it is using a smart contract module with a matching reference, leaving it up to the caller to ensure this.

Parameters:

  • grpcClient The function takes a gRPC client from @concordium/web-sdk.
  • contractAddress The contract address of the smart contract instance.
const grpcClient = ...; // Concordium gRPC client from '@concordium/web-sdk'.
const contractAddress = SDK.ContractAddress.create(...); // The address of the contract instance.
const myContract: MyContract.Type = MyContract.createUnchecked(grpcClient, contractAddress);

To run the checks manually use await MyContract.checkOnChain(myContract);.

const moduleReference

Variable with the reference of the smart contract module used by this contract.

const ref = MyContract.moduleReference;

type Event

Type representing the structured event logged by this smart contract.

This is only generated when the schema contains contract event type.

function parseEvent

Parse a raw contract event logged by this contract into a structured representation.

This is only generated when the schema contains contract event type.

Parameter: event The contract event to parse.

Returns: The structured event of the Event type (see type above).

const rawContractEvent = ...; // The unparsed contract event from some transaction.
const event: MyContract.Event = MyContract.parseEvent(rawContractEvent);

type <EntrypointName>Parameter

Type representing the parameter of for an entrypoint. The type is named <EntrypointName>Parameter where <EntrypointName> is the name of the entrypoint in Pascal case.

This is only generated when the schema contains contract entrypoint parameter type.

function send<EntrypointName>

For each entrypoint of the smart contract a function for sending a transaction calling this entrypoint is produced. These are named send<EntrypointName> where <EntrypointName> is the name of the entrypoint in Pascal case.

An example for a smart contract with an entrypoint named launch-rocket, the function becomes sendLaunchRocket.

Parameters

The function parameters are:

  • contractClient The client of the smart contract instance.

  • transactionMetadata Metadata related to constructing the transaction, such as energy and CCD amount to include.

    • senderAddress The address invoking this call.
    • energy The energy reserved for executing this transaction.
    • amount The amount of CCD included in the transaction. Defaults to 0.
    • expiry Expiry date of the transaction. Defaults to 5 minutes from when constructing the transaction.
  • parameter Parameter to provide to the smart contract entrypoint.

    With schema type: If the schema contains type information for the parameter, a type for the parameter is generated and used for this function (see type above).

    Without schema type: If no schema information is present, the function uses the generic Parameter from @concordium/web-sdk.

  • signer The keys of to use for signing the transaction.

Returns: Promise with the hash of the transaction.

// Generated contract client.
const myContract: MyContract.Type = await MyContract.create(grpcClient, contractAddress);
// The keys to use for signing the transaction.
const signer = ...;
// Transaction meta information.
const transactionMeta = {
  // The account address signing the transaction.
  senderAddress: SDK.AccountAddress.fromBase58("357EYHqrmMiJBmUZTVG5FuaMq4soAhgtgz6XNEAJaXHW3NHaUf"),
  // The amount of energy needed will depend on the contract.
  energy: SDK.Energy.create(12000),
};
// Parameter to pass the smart contract entrypoint. The structure depends on the contract.
const parameter = ...;
// Send the transaction calling the `launch-rockets` entrypoint.
const transactionHash = await MyContract.sendLaunchRocket(myContract, transactionMeta, parameter, signer);

function dryRun<EntrypointName>

For each entrypoint of the smart contract a function for dry-running a transaction calling this entrypoint is produced. These are named dryRun<EntrypointName> where <EntrypointName> is the name of the entrypoint in Pascal case.

An example for a smart contract with an entrypoint named launch-rocket, the function becomes dryRunLaunchRocket.

Parameters

The function parameters are:

  • contractClient The client of the smart contract instance.

  • parameter Parameter to provide to the smart contract entrypoint.

    With schema type: If the schema contains type information for the parameter, a type for the parameter is generated and used for this function (see type above).

    Without schema type: If no schema information is present, the function uses the generic Parameter from @concordium/web-sdk.

  • invokeMetadata Optional transaction metadata object with the following optional properties:

    • invoker The address invoking this call, can be either an AccountAddress or ContractAddress. Defaults to an AccountAddress (Base58check encoding of 32 bytes with value zero).
    • amount The amount of CCD included in the transaction. Defaults to 0.
    • energy The energy reserved for executing this transaction. Defaults to max energy possible.
  • blockHash (optional) Provide to specify the block hash, for which the state will be used for dry-running. When not provided, the last finalized block is used.

Returns: Promise with the invoke result.

const myContract: MyContract.Type = ...; // Generated contract client.
const parameter = ...; // Parameter to pass the smart contract entrypoint.
// Transaction metadata for invoking.
const metadata = {
  // Amount of CCD to include in the transaction.
  amount: SDK.CcdAmount.fromCcd(10),
  // Invoker of the transaction
  invoker: SDK.AccountAddress.fromBase58("357EYHqrmMiJBmUZTVG5FuaMq4soAhgtgz6XNEAJaXHW3NHaUf")
};
const invokeResult = await MyContract.dryRunLaunchRocket(myContract, parameter, metadata);

type ReturnValue<EntrypointName>

Type representing the return value from a successful dry-run/invocation of an entrypoint. The type is named ReturnValue<EntrypointName> where <EntrypointName> is the name of the relevant entrypoint in Pascal case.

This is only generated when the schema contains entrypoint return value type.

function parseReturnValue<EntrypointName>

For each entrypoint of the smart contract a function for parsing the return value in a successful invocation/dry-running. These are named parseReturnValue<EntrypointName> where <EntrypointName> is the name of the entrypoint in Pascal case.

This is only generated when the schema contains entrypoint return value type.

An example for a smart contract with an entrypoint named launch-rocket, the function becomes parseReturnValueLaunchRocket.

Parameter: invokeResult The result from dry-running a transactions calling the entrypoint.

Returns: Undefined if the invocation was not successful otherwise the parsed return value of the type ReturnValue<EntrypointName>.

// Dry run the entrypoint
const invokeResult = await MyContract.dryRunLaunchRocket(myContract, invoker, parameter);

// Parse the return value
const returnValue: MyContract.ReturnValueLaunchRocket | undefined = parseReturnValueLaunchRocket(invokeResult);

type ErrorMessage<EntrypointName>

Type representing the error message from a rejected dry-run/invocation of an entrypoint. The type is named ErrorMessage<EntrypointName> where <EntrypointName> is the name of the relevant entrypoint in Pascal case.

This is only generated when the schema contains entrypoint error message type.

function parseErrorMessage<EntrypointName>

For each entrypoint of the smart contract a function for parsing the error message in a rejected invocation/dry-running. These are named parseErrorMessage<EntrypointName> where <EntrypointName> is the name of the entrypoint in Pascal case.

This is only generated when the schema contains entrypoint error message type.

An example for a smart contract with an entrypoint named launch-rocket, the function becomes parseErrorMessageLaunchRocket.

Parameter: invokeResult The result from dry-running a transactions calling some entrypoint.

Returns: Undefined if the invocation was not rejected, otherwise the parsed error message of the type ReturnValue<EntrypointName>.

// Dry run the entrypoint
const invokeResult = await MyContract.dryRunLaunchRocket(myContract, invoker, parameter);

// Parse the error message
const message: MyContract.ErrorMessageLaunchRocket | undefined = MyContract.parseErrorMessageLaunchRocket(invokeResult);

function create<EntrypointName>ParameterWebWallet

For each entrypoint of the smart contract a function for constructing the WebWallet formattet parameter is produced. This is to be used with the @concordium/wallet-connector package. These are named create<EntrypointName>ParameterWebWallet where <EntrypointName> is the entrypoint name in Pascal case.

This is only generated when the schema contains contract entrypoint parameter type.

An example for a smart contract with an entrypoint named launch-rocket, the function becomes createLaunchRocketParameterWebWallet.

Parameter

The function parameter is:

  • parameter Parameter to provide to the smart contract entrypoint.

Returns: Parameter for updating a contract instance in the format used by @concordium/wallet-connector.

// Wallet connection from `@concordium/wallet-connector`.
const webWalletConnection: WalletConnection = ...;
// Parameter to pass the smart contract init-function. The structure depends on the contract.
const parameter: MyContract.LaunchRocketParameter = ...;
// Convert the parameter into the format expected by the wallet.
const walletParameter = MyContract.createLaunchRocketParameterWebWallet(parameter);
// Use wallet connection to sign and send the transaction.
const sender = ...;
const transactionHash = await webWalletConnection.signAndSendTransaction(
    sender,
    AccountTransactionType.Update,
    walletParameter
);

Development

This section describes how to setup and start developing this package.

Setup

To be able to develop ccd-js-gen make sure to have:

After cloning this repository makes sure to do the following:

  • Initialize git submodules recursively, can be done using git submodules update --init --recursive.
  • Install dependencies by running yarn install in the root of this repo.
  • Build everything using yarn build in the root of this repo.

Development workflow

After doing changes to the source code of ccd-js-gen run yarn build from the packages/ccd-js-gen directory.

To run CLI locally use:

./bin/ccd-js-gen.js --module "<module>" --out-dir "./lib/generated"

To run tests locally use:

yarn test