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";
const grpcClient =
const contractAddress =
const signer =
const sender =
const receiver =
const contractClient = await MyContract.create(
grpcClient,
contractAddress
);
const parameter: MyContract.TransferParameter = [{
tokenId: "",
amount: 10,
from: { type: 'Account', content: sender },
to: { type: 'Account', content: receiver },
data: "",
}];
const transactionHash = await MyContract.sendTransfer(contractClient, {
senderAddress: sender,
energy: SDK.Energy.create(12000)
}, 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";
const outDirPath = "./generated";
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";
const outputModuleName = "wCCD-module";
const grpcClient = ...;
const moduleRef = SDK.ModuleReference.fromHexString('<hex of module referecene>');
const moduleSource = await grpcClient.getModuleSource(moduleRef);
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 = ...;
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 = ...;
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 = ...;
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.
const myModule: MyModule.Type = await MyModule.create(grpcClient);
const signer = ...;
const transactionMeta = {
senderAddress: SDK.AccountAddress.fromBase58("357EYHqrmMiJBmUZTVG5FuaMq4soAhgtgz6XNEAJaXHW3NHaUf"),
energy: SDK.Energy.create(12000),
};
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
.
const webWalletConnection: WalletConnection = ...;
const parameter: MyModule.MyContractParameter = ...;
const walletParameter = MyModule.createMyContractParameterWebWallet(parameter);
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 = ...;
const contractAddress = SDK.ContractAddress.create(...);
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 = ...;
const contractAddress = SDK.ContractAddress.create(...);
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 = ...;
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.
const myContract: MyContract.Type = await MyContract.create(grpcClient, contractAddress);
const signer = ...;
const transactionMeta = {
senderAddress: SDK.AccountAddress.fromBase58("357EYHqrmMiJBmUZTVG5FuaMq4soAhgtgz6XNEAJaXHW3NHaUf"),
energy: SDK.Energy.create(12000),
};
const parameter = ...;
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 = ...;
const parameter = ...;
const metadata = {
amount: SDK.CcdAmount.fromCcd(10),
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>
.
const invokeResult = await MyContract.dryRunLaunchRocket(myContract, invoker, parameter);
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>
.
const invokeResult = await MyContract.dryRunLaunchRocket(myContract, invoker, parameter);
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
.
const webWalletConnection: WalletConnection = ...;
const parameter: MyContract.LaunchRocketParameter = ...;
const walletParameter = MyContract.createLaunchRocketParameterWebWallet(parameter);
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