-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #168 from Concordium/cis-contract-clients
Cis contract clients
- Loading branch information
Showing
35 changed files
with
3,185 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
# CIS-2 contract | ||
|
||
This document describes the helper class for working with CIS-2 contracts | ||
|
||
**Table of Contents** | ||
|
||
- [CIS2Contract](#cis2contract) | ||
- [Creating a CIS2Contract](#creating-a-cis2contract) | ||
- [Transfer token(s)](#transfer-tokens) | ||
- [Operator update(s)](#operator-updates) | ||
- [Querying for balance of token(s)](#querying-for-balance-of-tokens) | ||
- [Querying for operator of](#querying-for-operator-of) | ||
- [Querying for token metadata](#querying-for-token-metadata) | ||
- [Performing dry-run invocations of contract updates](#performing-dry-run-invocations-of-contract-updates) | ||
|
||
# CIS2Contract | ||
|
||
The CIS2Contract class wraps the [ConcordiumNodeClient]('./gRPC.md'), defining an interface matching the [CIS-2 standard](https://proposals.concordium.software/CIS/cis-2.html). | ||
|
||
## Creating a CIS2Contract | ||
The contract relies on a `ConcordiumNodeClient` instance to communicate with the node along with a contract address of the CIS-2 contract to invoke functions on. | ||
|
||
```js | ||
const contractAddress = {index: 1234n, subindex: 0n}; | ||
const contract = await CIS2Contract.create(nodeClient, contractAddress); // Implied that you already have a `ConcordiumNodeClient` instance of some form. | ||
``` | ||
|
||
This gets the relevant contract information from the node and checks that the contract is in fact a CIS-2 contract (through the [CIS-0 supports function](../packages/common/README.md#check-smart-contract-for-support-for-standards)), hence why it is async. You can also instantiate using the `new` keyword, circumventing standard checks: | ||
|
||
```js | ||
const contract = new CIS2Contract(nodeClient, contractAddress, 'my_contract_name'); | ||
``` | ||
|
||
## Transfer token(s) | ||
The following example demonstrates how to send either a single/list of CIS-2 "transfer" transactions using a `CIS2Contract` instance. | ||
This relies on using private keys for the sender account to sign the transaction before submitting to the node. | ||
|
||
See the signing a transaction section for the [common package](../packages/common/README.md#sign-an-account-transaction) for create an `AccountSigner`. | ||
|
||
```js | ||
const tokenId = ''; // HEX string representing a token ID defined in the contract. | ||
const from = '4UC8o4m8AgTxt5VBFMdLwMCwwJQVJwjesNzW7RPXkACynrULmd'; | ||
const to = '3ybJ66spZ2xdWF3avgxQb2meouYa7mpvMWNPmUnczU8FoF8cGB'; // An account receiver. | ||
// const to = {address: {index: 1234n, subindex: 0n}, hookName: 'someReceiveHookName'} // A contract receiver can be specified as such. | ||
const tokenAmount = 100n; | ||
|
||
const signer: AccountSigner = ...; // Relies on using private keys of sender account. | ||
const update = { from, to, tokenAmount, tokenId }; | ||
// const update [{ from, to, tokenAmount, tokenId }, { from, to, tokenAmount, tokenId }] // Example of batch update. | ||
|
||
const txHash = await contract.transfer( | ||
{ | ||
senderAddress: from, | ||
energy: 10000n, | ||
}, | ||
update, | ||
signer | ||
); | ||
``` | ||
|
||
### Create transfer transaction | ||
This creates a CIS-2 "transfer" transaction, that can then be submitted the transaction through a Concordium compatible wallet, which handles signing and submitting. | ||
|
||
```js | ||
const tokenId = ''; // HEX string representing a token ID defined in the contract. | ||
const from = '4UC8o4m8AgTxt5VBFMdLwMCwwJQVJwjesNzW7RPXkACynrULmd'; | ||
const to = '3ybJ66spZ2xdWF3avgxQb2meouYa7mpvMWNPmUnczU8FoF8cGB'; // An account receiver. | ||
// const to = {address: {index: 1234n, subindex: 0n}, hookName: 'someReceiveHookName'} // A contract receiver can be specified as such. | ||
const tokenAmount = 100n; | ||
|
||
const transfer = { from, to, tokenAmount, tokenId }; | ||
// const transfer [{ from, to, tokenAmount, tokenId }, { from, to, tokenAmount, tokenId }] // Example of batch update. | ||
|
||
const { | ||
type, // The transaction type. | ||
payload, // The transaction payload | ||
parameter: { | ||
json, // The parameter to be supplied to the contract receive function in JSON format. | ||
hex, // The parameter to be supplied to the contract receive function as a hex encoded string | ||
}, | ||
schema: { | ||
value, // The contract schema for the parameter. This is needed to translate the JSON format to a byte array. | ||
type, // The type of the schema. This is always 'parameter', meaning it can be used for the JSON formatted parameter only. | ||
} | ||
} = contract.createTransfer( | ||
{ energy: 10000n }, | ||
transfer | ||
); | ||
``` | ||
|
||
## Operator update(s) | ||
The following example demonstrates how to send either a single/list of CIS-2 "updateOperator" transactions using a `CIS2Contract` instance. | ||
This relies on using private keys for the sender account to sign the transaction before submitting to the node. | ||
|
||
See the signing a transaction section for the [common package](../packages/common/README.md#sign-an-account-transaction) for create an `AccountSigner`. | ||
|
||
```js | ||
const owner = '4UC8o4m8AgTxt5VBFMdLwMCwwJQVJwjesNzW7RPXkACynrULmd'; | ||
const type = 'add'; // or 'remove'; | ||
const address = '3ybJ66spZ2xdWF3avgxQb2meouYa7mpvMWNPmUnczU8FoF8cGB'; // Address to add as operator of owner | ||
// const address = {index: 1234n, 0n}; // Example of contract address operator. | ||
|
||
const signer: AccountSigner = ...; // Relies on using private keys of sender account. | ||
const update = { type, address }; | ||
// const update [{type, address}, {type, address}] // Example of batch update. | ||
|
||
const txHash = await contract.updateOperator( | ||
{ | ||
senderAddress: owner, | ||
energy: 10000n, | ||
}, | ||
update, | ||
signer | ||
); | ||
``` | ||
|
||
### Create update operator transaction | ||
This creates a CIS-2 "updateOperator" transaction, that can then be submitted the transaction through a Concordium compatible wallet, which handles signing and submitting. | ||
|
||
```js | ||
const owner = '4UC8o4m8AgTxt5VBFMdLwMCwwJQVJwjesNzW7RPXkACynrULmd'; | ||
const type = 'add'; // or 'remove'; | ||
const address = '3ybJ66spZ2xdWF3avgxQb2meouYa7mpvMWNPmUnczU8FoF8cGB'; // Address to add as operator of owner | ||
// const address = {index: 1234n, 0n}; // Example of contract address operator. | ||
|
||
const update = { type, address }; | ||
// const update [{type, address}, {type, address}] // Example of batch update. | ||
|
||
const { | ||
type, // The transaction type. | ||
payload, // The transaction payload | ||
parameter: { | ||
json, // The parameter to be supplied to the contract receive function in JSON format. | ||
hex, // The parameter to be supplied to the contract receive function as a hex encoded string | ||
}, | ||
schema: { | ||
value, // The contract schema for the parameter. This is needed to translate the JSON format to a byte array. | ||
type, // The type of the schema. This is always 'parameter', meaning it can be used for the JSON formatted parameter only. | ||
} | ||
} = contract.createUpdateOperator( | ||
{ energy: 10000n }, | ||
update | ||
); | ||
``` | ||
|
||
## Querying for balance of token(s) | ||
The following example demonstrates how to send either a single/list of CIS-2 "balanceOf" queries using a `CIS2Contract` instance. The response for the query will be either a single/list of bigint balances corresponding to the queries. | ||
|
||
```js | ||
const tokenId = ''; // HEX string representing a token ID defined in the contract. | ||
const address = '4UC8o4m8AgTxt5VBFMdLwMCwwJQVJwjesNzW7RPXkACynrULmd'; | ||
|
||
const query = { address, tokenId }; | ||
const balance = await contract.balanceOf(query); | ||
|
||
// List of queries | ||
const query1 = { address, tokenId }; | ||
const query2 = { address, tokenId }; | ||
const query3 = { address, tokenId }; | ||
|
||
const [balance1, balance2, balance3] = await contract.balanceOf([query1, query2, query3]); | ||
``` | ||
|
||
## Querying for operator of | ||
The following example demonstrates how to send either a single/list of CIS-2 "operatorOf" queries using a `CIS2Contract` instance. The response for the query will be either a single/list of boolean values corresponding to the queries, each signaling whether the specified `address` is an operator of the specified `owner`. | ||
|
||
```js | ||
const owner = '4UC8o4m8AgTxt5VBFMdLwMCwwJQVJwjesNzW7RPXkACynrULmd'; | ||
const address = '3ybJ66spZ2xdWF3avgxQb2meouYa7mpvMWNPmUnczU8FoF8cGB'; // Address to check if operator of owner | ||
// const address = {index: 1234n, 0n}; // Example of contract address operator. | ||
|
||
const query = { owner, address }; | ||
|
||
const isOperator = await contract.operatorOf(query); | ||
|
||
// List of queries | ||
const query1 = { owner, address }; | ||
const query2 = { owner, address }; | ||
const query3 = { owner, address }; | ||
|
||
const [isOperator1, isOperator2, isOperator3] = await contract.isOperatorOf([query1, query2, query3]); | ||
``` | ||
|
||
## Querying for token metadata | ||
The following example demonstrates how to send either a single/list of CIS-2 "tokenMetadata" queries using a `CIS2Contract` instance. The response for the query will be either a single/list of `MetadataUrl` objects corresponding to the queries. | ||
|
||
To get the actual metadata JSON for the contract, a subsequent request to the URL returned would have to be made. | ||
|
||
```js | ||
const tokenId = ''; // HEX string representing a token ID defined in the contract. | ||
const metadataUrl = await contract.tokenMetadata(tokenId); | ||
|
||
// List of queries | ||
const tokenId1 = '00'; | ||
const tokenId2 = '01'; | ||
const tokenId3 = '02'; | ||
|
||
const [metadataUrl1, metadataUrl2, metadataUrl3] = await contract.tokenMetadata([tokenId1, tokenId2, tokenId3]); | ||
``` | ||
|
||
## Performing dry-run invocations of contract updates | ||
It can be useful to perform a dry-run of contract updates sent, as it allows you to see the result of the update without actually performing (and the user paying for) it. | ||
One common use case is to check the energy needed to execute the update which can be translated to a cost in CCD. | ||
|
||
### Token transfer(s) dry-run | ||
The following example demonstrates how to perform a dry-run of CIS-2 "transfer" with either a single/list of transfers using a `CIS2Contract` instance. The response will be an object containing information about the function invocation. | ||
|
||
```js | ||
const tokenId = ''; // HEX string representing a token ID defined in the contract. | ||
const from = '4UC8o4m8AgTxt5VBFMdLwMCwwJQVJwjesNzW7RPXkACynrULmd'; | ||
const to = '3ybJ66spZ2xdWF3avgxQb2meouYa7mpvMWNPmUnczU8FoF8cGB'; // An account receiver. | ||
// const to = {address: {index: 1234n, subindex: 0n}, hookName: 'someReceiveHookName'} // A contract receiver can be specified as such. | ||
const tokenAmount = 100n; | ||
|
||
const update = { from, to, tokenAmount, tokenId }; | ||
// const update [{ from, to, tokenAmount, tokenId }, { from, to, tokenAmount, tokenId }] // Example of batch update. | ||
|
||
const result = await contract.dryRun.transfer(from, update); | ||
|
||
``` | ||
|
||
### Operator updates(s) dry-run | ||
The following example demonstrates how to perform a dry-run of CIS-2 "updateOperator" with either a single/list of updates using a `CIS2Contract` instance. The response will be an object containing information about the function invocation. | ||
|
||
```js | ||
const owner = '4UC8o4m8AgTxt5VBFMdLwMCwwJQVJwjesNzW7RPXkACynrULmd'; | ||
const type = 'add'; // or 'remove'; | ||
const address = '3ybJ66spZ2xdWF3avgxQb2meouYa7mpvMWNPmUnczU8FoF8cGB'; // Address to add as operator of owner | ||
// const address = {index: 1234n, 0n}; // Example of contract address operator. | ||
|
||
const update = { type, address }; | ||
// const update [{type, address}, {type, address}] // Example of batch update. | ||
|
||
const result = await contract.dryRun.updateOperator(from, update); | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { | ||
createConcordiumClient, | ||
cis0Supports, | ||
ContractAddress, | ||
} from '@concordium/node-sdk'; | ||
import { credentials } from '@grpc/grpc-js'; | ||
import meow from 'meow'; | ||
|
||
const cli = meow( | ||
` | ||
Usage | ||
$ yarn run-example <path-to-this-file> [options] | ||
Required | ||
--index, -i The index of the smart contract | ||
--supportsQuery, -q The support queries to test for. Setting multiple values is supported to test for a list of standards. | ||
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, | ||
}, | ||
subindex: { | ||
type: 'number', | ||
default: 0, | ||
}, | ||
supportsQuery: { | ||
type: 'string', | ||
alias: 'q', | ||
isRequired: true, | ||
isMultiple: true, | ||
}, | ||
}, | ||
} | ||
); | ||
|
||
const [address, port] = cli.flags.endpoint.split(':'); | ||
const client = createConcordiumClient( | ||
address, | ||
Number(port), | ||
credentials.createInsecure() | ||
); | ||
|
||
if (cli.flags.h) { | ||
cli.showHelp(); | ||
} | ||
|
||
(async () => { | ||
const contractAddress: ContractAddress = { | ||
index: BigInt(cli.flags.index), | ||
subindex: BigInt(cli.flags.subindex), | ||
}; | ||
const response = await cis0Supports( | ||
client, | ||
contractAddress, | ||
cli.flags.supportsQuery | ||
); | ||
|
||
console.log(response); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { createConcordiumClient, CIS2Contract } from '@concordium/node-sdk'; | ||
import { credentials } from '@grpc/grpc-js'; | ||
import meow from 'meow'; | ||
import { parseAddress } from '../shared/util'; | ||
|
||
const cli = meow( | ||
` | ||
Usage | ||
$ yarn run-example <path-to-this-file> [options] | ||
Required | ||
--address, -a An address to get balance for. Base58 string for account address, string in the format <index>,<subindex> (f.x. 123,0) for contract address. | ||
--index, -i The index of the smart contract | ||
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 | ||
--tokenId, -t The token ID to query a balance for. Defaults to '', which represents the smallest token ID possible, commonly used for single token contract instances. | ||
`, | ||
{ | ||
importMeta: import.meta, | ||
flags: { | ||
endpoint: { | ||
type: 'string', | ||
alias: 'e', | ||
default: 'localhost:20000', | ||
}, | ||
index: { | ||
type: 'number', | ||
alias: 'i', | ||
isRequired: true, | ||
}, | ||
subindex: { | ||
type: 'number', | ||
default: 0, | ||
}, | ||
address: { | ||
type: 'string', | ||
isRequired: true, | ||
alias: 'a', | ||
}, | ||
tokenId: { | ||
type: 'string', | ||
alias: 't', | ||
default: '', | ||
}, | ||
}, | ||
} | ||
); | ||
|
||
const [address, port] = cli.flags.endpoint.split(':'); | ||
const client = createConcordiumClient( | ||
address, | ||
Number(port), | ||
credentials.createInsecure() | ||
); | ||
|
||
if (cli.flags.h) { | ||
cli.showHelp(); | ||
} | ||
|
||
(async () => { | ||
const contract = await CIS2Contract.create(client, { | ||
index: BigInt(cli.flags.index), | ||
subindex: BigInt(cli.flags.subindex), | ||
}); | ||
|
||
const accountBalance = await contract.balanceOf({ | ||
address: parseAddress(cli.flags.address), | ||
tokenId: cli.flags.tokenId, | ||
}); | ||
|
||
console.log(accountBalance); | ||
})(); |
Oops, something went wrong.