Skip to content

Commit

Permalink
Merge pull request #242 from Concordium/setup-ccd-js-gen
Browse files Browse the repository at this point in the history
Initial set up of `ccd-js-gen` package
  • Loading branch information
limemloh authored Sep 6, 2023
2 parents 95a5ebe + 1eef169 commit 8012dec
Show file tree
Hide file tree
Showing 17 changed files with 882 additions and 7 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ jobs:
./packages/nodejs/lib
./packages/common/grpc
./packages/nodejs/grpc
./packages/ccd-js-gen/lib
typecheck-examples:
runs-on: ubuntu-22.04
Expand Down Expand Up @@ -158,6 +159,7 @@ jobs:

lint:
runs-on: ubuntu-22.04
needs: build
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v3
Expand All @@ -167,6 +169,11 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
cache: yarn

- name: Get build-debug
uses: ./.github/actions/download-artifact
with:
name: build-debug

- name: Install dependencies
run: yarn install --immutable

Expand Down
77 changes: 77 additions & 0 deletions examples/ccd-js-gen/wCCD/client-tokenMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { credentials } from '@grpc/grpc-js';
import * as SDK from '@concordium/node-sdk';
import meow from 'meow';
import { parseEndpoint } from '../../shared/util';

// The generated module could be imported directly like below,
// but for this example it is imported dynamicly to improve
// the error message when not generated.
// import * as wCCDModule from './lib/wCCD';

const cli = meow(
`
This example uses a generated smart contract client for the wCCD smart contract.
Usage
$ yarn run-example <path-to-this-file> [options]
Required
--index, -i The index of the smart contract. Defaults to 2059, which is wCCD on testnet.
Options
--help, -h Displays this message
--endpoint, -e Specify endpoint of a grpc2 interface of a Concordium node in the format "<scheme>://<address>:<port>". Defaults to 'http://localhost:20000'
--subindex, The subindex of the smart contract. Defaults to 0
`,
{
importMeta: import.meta,
flags: {
endpoint: {
type: 'string',
alias: 'e',
default: 'http://localhost:20000',
},
index: {
type: 'number',
alias: 'i',
default: 2059,
},
subindex: {
type: 'number',
default: 0,
},
},
}
);

const [address, port, scheme] = parseEndpoint(cli.flags.endpoint);
const grpcClient = SDK.createConcordiumClient(
address,
Number(port),
scheme === 'https' ? credentials.createSsl() : credentials.createInsecure()
);

const contractAddress: SDK.ContractAddress = {
index: BigInt(cli.flags.index),
subindex: BigInt(cli.flags.subindex),
};

(async () => {
// Importing the generated smart contract module client.
/* eslint-disable import/no-unresolved */
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const wCCDModule = await import('./lib/wCCD').catch((e) => {
/* eslint-enable import/no-unresolved */
console.error(
'\nFailed to load the generated wCCD module, did you run the `generate` script?\n'
);
throw e;
});

const parameter = '010000'; // First 2 bytes for number of tokens to query, 1 byte for the token ID.
const contract = new wCCDModule.Cis2WCCD(grpcClient, contractAddress);

const responseHex = await contract.dryRun.tokenMetadata(parameter);
console.log({ responseHex });
})();
73 changes: 73 additions & 0 deletions examples/ccd-js-gen/wCCD/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { credentials } from '@grpc/grpc-js';
import * as SDK from '@concordium/node-sdk';
import * as Gen from '@concordium/ccd-js-gen';
import * as Path from 'node:path';
import * as Url from 'node:url';
import meow from 'meow';
import { parseEndpoint } from '../../shared/util';

const cli = meow(
`
This example fetches the wCCD smart contract module source from the chain and generates a typescript client for interacting with such a smart contract.
Usage
$ yarn run-example <path-to-this-file> [options]
Required
--index, -i The index of the smart contract. Defaults to 2059, which is wCCD on Testnet.
Options
--help, -h Displays this message
--endpoint, -e Specify endpoint of a grpc2 interface of a Concordium node in the format "address:port". Defaults to 'localhost:20000'
--subindex, The subindex of the smart contract. Defaults to 0
`,
{
importMeta: import.meta,
flags: {
endpoint: {
type: 'string',
alias: 'e',
default: 'localhost:20000',
},
index: {
type: 'number',
alias: 'i',
isRequired: true,
default: 2059,
},
subindex: {
type: 'number',
default: 0,
},
},
}
);

const [address, port] = parseEndpoint(cli.flags.endpoint);
const grpcClient = SDK.createConcordiumClient(
address,
Number(port),
credentials.createInsecure()
);

const contractAddress: SDK.ContractAddress = {
index: BigInt(cli.flags.index),
subindex: BigInt(cli.flags.subindex),
};

(async () => {
console.info(`Fetching instance information for ${contractAddress.index}.`);
const info = await grpcClient.getInstanceInfo(contractAddress);
console.info(
`Fetching smart contract module source with reference '${info.sourceModule.moduleRef}'.`
);
const moduleSource = await grpcClient.getModuleSource(info.sourceModule);
const mod = SDK.Module.fromModuleSource(moduleSource);
const filePath = Url.fileURLToPath(import.meta.url);
const outDir = Path.join(Path.dirname(filePath), 'lib');
console.info(`Generating smart contract module client at '${outDir}'.`);
await Gen.generateContractClients(mod, 'wCCD', outDir, {
output: 'TypeScript',
});
console.info('Code generation was successful.');
})();
2 changes: 2 additions & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"name": "@concordium/examples",
"type": "module",
"dependencies": {
"@concordium/ccd-js-gen": "workspace:^",
"@concordium/common-sdk": "workspace:^",
"@concordium/node-sdk": "workspace:^",
"@grpc/grpc-js": "^1.3.4",
"@noble/ed25519": "^1.7.1",
Expand Down
26 changes: 21 additions & 5 deletions examples/shared/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,27 @@ export const parseAddress = (input: string): Base58String | ContractAddress => {
return { index: BigInt(i), subindex: BigInt(si) };
};

export const parseEndpoint = (endpoint: string): [string, number] => {
// Regular expression for matching the scheme prefix of a URL.
const schemeRegex = /^(\w+):\/\//;

/**
* Parse endpoint information from a string, such as 'http://my-concordium-node:20000'
* @param endpoint String with information of an endpoint.
* @returns Triple with ['<address>', <port>, '<scheme>'].
*/
export const parseEndpoint = (
endpoint: string
): [string, number, string | undefined] => {
const result = schemeRegex.exec(endpoint);
const matched = result?.[0];
const scheme = result?.[1];

const noSchemeEndpoint = endpoint.substring(matched?.length ?? 0);

// Split endpoint on last colon
const lastColonIndex = endpoint.lastIndexOf(':');
const address = endpoint.substring(0, lastColonIndex);
const port = Number(endpoint.substring(lastColonIndex + 1));
const lastColonIndex = noSchemeEndpoint.lastIndexOf(':');
const address = noSchemeEndpoint.substring(0, lastColonIndex);
const port = Number(noSchemeEndpoint.substring(lastColonIndex + 1));

return [address, port];
return [address, port, scheme];
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"./packages/common",
"./packages/nodejs",
"./packages/web",
"./packages/ccd-js-gen",
"./examples"
]
},
Expand Down
4 changes: 4 additions & 0 deletions packages/ccd-js-gen/bin/ccd-js-gen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node
// eslint-disable-next-line @typescript-eslint/no-var-requires
const cli = require('../lib/src/cli.js');
cli.main();
52 changes: 52 additions & 0 deletions packages/ccd-js-gen/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "@concordium/ccd-js-gen",
"version": "1.0.0",
"description": "Generate JS clients for the Concordium Blockchain",
"bin": "bin/ccd-js-gen.js",
"main": "lib/src/lib.js",
"typings": "lib/src/lib.d.ts",
"scripts": {
"build": "tsc",
"build-dev": "yarn build",
"clean": "rm -r lib",
"lint": "eslint src/** bin/** --cache --ext .ts,.js --max-warnings 0",
"lint-fix": "yarn --silent lint --fix; exit 0",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"concordium",
"smart-contracts"
],
"repository": {
"type": "git",
"url": "https://github.com/Concordium/concordium-node-sdk-js",
"directory": "packages/ccd-js-gen"
},
"author": {
"name": "Concordium Software",
"email": "[email protected]",
"url": "https://concordium.com"
},
"license": "Apache-2.0",
"dependencies": {
"@concordium/common-sdk": "9.3.0",
"buffer": "^6.0.3",
"commander": "^11.0.0",
"ts-morph": "^19.0.0"
},
"devDependencies": {
"@types/node": "^20.5.0",
"@typescript-eslint/eslint-plugin": "^4.28.1",
"@typescript-eslint/parser": "^4.28.1",
"eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-prettier": "^3.4.0",
"prettier": "^2.3.2",
"typescript": "^4.3.5"
},
"prettier": {
"singleQuote": true,
"tabWidth": 4
}
}
34 changes: 34 additions & 0 deletions packages/ccd-js-gen/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
This file contains code for building the command line inferface to the ccd-js-gen library.
*/
import { Command } from 'commander';
import packageJson from '../package.json';
import * as lib from './lib';

/** Type representing the CLI options/arguments and needs to match the options set with commander.js */
type Options = {
/** Smart contract module to generate clients from */
module: string;
/** The output directory for the generated code */
outDir: string;
};

// Main function, which is called be the executable script in `bin`.
export async function main(): Promise<void> {
const program = new Command();
program
.name(packageJson.name)
.description(packageJson.description)
.version(packageJson.version)
.requiredOption(
'-m, --module <module-file>',
'Smart contract module to generate clients from'
)
.requiredOption(
'-o, --out-dir <directory>',
'The output directory for the generated code'
)
.parse(process.argv);
const options = program.opts<Options>();
await lib.generateContractClientsFromFile(options.module, options.outDir);
}
Loading

0 comments on commit 8012dec

Please sign in to comment.