Transactions

This document describes how to create different transactions for both the Nodejs and Web SDK's.

Table of Contents:

Constructing transactions

Create a simple transfer with or without memo

The following example demonstrates how a simple transfer can be created.

From ./../examples/nodejs/common/simpleTransfer.ts#77~122

    const walletFile = readFileSync(cli.flags.walletFile, 'utf8');
const walletExport = parseWallet(walletFile);
const sender = AccountAddress.fromBase58(walletExport.value.address);

const toAddress = AccountAddress.fromBase58(cli.flags.receiver);
const nextNonce: NextAccountNonce = await client.getNextAccountNonce(sender);

const header: AccountTransactionHeader = {
expiry: TransactionExpiry.futureMinutes(60),
nonce: nextNonce.nonce,
sender,
};

// Include memo if it is given otherwise don't
let simpleTransfer = undefined;
if (cli.flags.memo) {
simpleTransfer = {
amount: CcdAmount.fromMicroCcd(cli.flags.amount),
toAddress,
memo: new DataBlob(Buffer.from(cli.flags.memo, 'hex')),
};
} else {
simpleTransfer = {
amount: CcdAmount.fromMicroCcd(cli.flags.amount),
toAddress,
};
}

const accountTransaction: AccountTransaction = {
header: header,
payload: simpleTransfer,
type: AccountTransactionType.Transfer,
};

// Sign transaction
const signer = buildAccountSigner(walletExport);
const signature: AccountTransactionSignature = await signTransaction(accountTransaction, signer);

const transactionHash = await client.sendAccountTransaction(accountTransaction, signature);

const status = await client.waitForTransactionFinalization(transactionHash);
console.dir(status, { depth: null, colors: true });

Create a Register data transaction

The following example demonstrates how a register data transaction can be created.

    const header: AccountTransactionHeader = {
expiry: new TransactionExpiry(new Date(Date.now() + 3600000)),
nonce: 1n, // the next nonce for this account, can be found using getNextAccountNonce
sender: new AccountAddress("4ZJBYQbVp3zVZyjCXfZAAYBVkJMyVj8UKUNj9ox5YqTCBdBq2M"),
};
const registerData: RegisterDataPayload = {
data: new DataBlob(Buffer.from('6B68656C6C6F20776F726C64', 'hex')) // Add the bytes you wish to register as a DataBlob
};
const registerDataAccountTransaction: AccountTransaction = {
header: header,
payload: registerData,
type: AccountTransactionType.RegisterData,
};

Create a configure delegation transaction

The following example demonstrates how a configure delegation transaction can be created. Note that although all the fields are optional, they are all required, when becoming a delegator.

From ./../examples/nodejs/common/delegationAdd.ts#71~107

    // Read wallet-file
const walletFile = readFileSync(cli.flags.walletFile, 'utf8');
const wallet = parseWallet(walletFile);
const sender = AccountAddress.fromBase58(wallet.value.address);
const signer = buildAccountSigner(wallet);

const header: AccountTransactionHeader = {
expiry: TransactionExpiry.futureMinutes(60),
nonce: (await client.getNextAccountNonce(sender)).nonce,
sender: sender,
};

const configureDelegationPayload: ConfigureDelegationPayload = {
stake: CcdAmount.fromMicroCcd(cli.flags.stake),
delegationTarget: {
delegateType: DelegationTargetType.PassiveDelegation,
},
restakeEarnings: cli.flags.restake,
};

const configureDelegationTransaction: AccountTransaction = {
header: header,
payload: configureDelegationPayload,
type: AccountTransactionType.ConfigureDelegation,
};

// Sign transaction
const signature = await signTransaction(configureDelegationTransaction, signer);

const transactionHash = await client.sendAccountTransaction(configureDelegationTransaction, signature);

console.log('Transaction submitted, waiting for finalization...');

const status = await client.waitForTransactionFinalization(transactionHash);
console.dir(status, { depth: null, colors: true });

Create a configure baker transaction

The following example demonstrates how a configure baker transaction can be created. Note that although all the fields are optional, they are all required, when registering as a baker.

From ./../examples/nodejs/common/bakerAdd.ts#66~107

    // Read wallet-file
const walletFile = readFileSync(cli.flags.walletFile, 'utf8');
const wallet = parseWallet(walletFile);
const sender = AccountAddress.fromBase58(wallet.value.address);
const signer = buildAccountSigner(wallet);

const header: AccountTransactionHeader = {
expiry: TransactionExpiry.futureMinutes(60),
nonce: (await client.getNextAccountNonce(sender)).nonce,
sender,
};

const bakerKeys = generateBakerKeys(sender);

const configureBakerPayload: ConfigureBakerPayload = {
stake: CcdAmount.fromMicroCcd(cli.flags.stake),
restakeEarnings: true,
openForDelegation: OpenStatus.OpenForAll,
keys: bakerKeys,
metadataUrl: 'www.url.for.metadata',
transactionFeeCommission: 10000,
bakingRewardCommission: 10000,
finalizationRewardCommission: 100000,
};

const configureBakerAccountTransaction: AccountTransaction = {
header: header,
payload: configureBakerPayload,
type: AccountTransactionType.ConfigureBaker,
};

// Sign transaction
const signature = await signTransaction(configureBakerAccountTransaction, signer);

const transactionHash = await client.sendAccountTransaction(configureBakerAccountTransaction, signature);

console.log('Transaction submitted, waiting for finalization...');

const status = await client.waitForTransactionFinalization(transactionHash);
console.dir(status, { depth: null, colors: true });

The open for delegation field determines the baker pools status and can have three different values:

  • OpenForAll: New delegators can join the pool.
  • ClosedForAll: New delegators won't be able to join the pool, but the current delegators won't be moved to passive delegation.
  • ClosedForNew: New delegators won't be able to join the pool, and the current delegators will be moved to passive delegation.

The three commission rates should specified in parts per hundred thousand, i.e. 100% is 100000 and 1% 1000. Additionally they value should be within the allowed range. The allowed ranges are part of the chain parameters.

Create a credential for an existing account

The following example demonstrates how to create a credential for an existing account. This credential can then be deployed onto the account by the account owner with an update credentials transaction. See Create an update credentials transaction for how to create this transaction payload using the output from the example below:

    const lastFinalizedBlockHash = (await client.getConsensusStatus()).lastFinalizedBlock;
const cryptographicParameters = await client.getCryptographicParameters(lastFinalizedBlockHash);
if (!cryptographicParameters) {
throw new Error('Cryptographic parameters were not found on a block that has been finalized.');
}

// The parts of the identity required to create a new credential, parsed from
// e.g. a wallet export.
const identityInput: IdentityInput = ...

// Require just one key on the credential to sign. This can be any number
// up to the number of public keys added to the credential.
const threshold: number = 1;

// The index of the credential that will be created. This index is per identity
// and has to be in sequence, and not already used. Note that index 0 is used
// by the initial credential that was created with the identity.
const credentialIndex: number = 1;

// In this example the credential will have one signing key, but there
// could be multiple. The signatures on the credential must be supplied
// in the same order as the keys are here.
const publicKeys: VerifyKey[] = [
{
schemeId: "Ed25519",
verifyKey: "c8cd7623c5a9316d8e2fccb51e1deee615bdb5d324fb4a6d33801848fb5e459e"
}
];

// The attributes to reveal about the account holder on chain. In the case of an
// empty array no attributes are revealed.
const revealedAttributes: AttributeKey[] = [];

// The next step creates an unsigned credential for an existing account.
// Note that unsignedCredentialForExistingAccount also contains the randomness used,
// which should be saved to later be able to reveal attributes, or prove properties about them.
const existingAccountAddress = new AccountAddress("3sAHwfehRNEnXk28W7A3XB3GzyBiuQkXLNRmDwDGPUe8JsoAcU");
const unsignedCredentialForExistingAccount = createUnsignedCredentialForExistingAccount(
identityInput,
cryptographicParameters.value,
threshold,
publicKeys,
credentialIndex,
revealedAttributes,
existingAccountAddress
);

// Sign the credential information.
const credentialDigestToSign = getCredentialForExistingAccountSignDigest(unsignedCredentialForExistingAccount.unsignedCdi, existingAccountAddress);
const credentialSigningKey = 'acab9ec5dfecfe5a6e13283f7ca79a6f6f5c685f036cd044557969e4dbe9d781';
const credentialSignature = Buffer.from(await ed.sign(credentialDigestToSign, credentialSigningKey)).toString('hex');

// Combine the credential and the signatures so that the object is ready
// to be submitted as part of an update credentials transaction. This is the
// object that must be provided to the account owner, who can then use it to
// deploy it to their account.
const signedCredentialForExistingAccount: CredentialDeploymentInfo = buildSignedCredentialForExistingAccount(unsignedCredentialForExistingAccount.unsignedCdi, [credentialSignature]);

Create an update credentials transaction

The following demonstrates how to construct an update credentials transaction, which is used to deploy additional credentials to an account, remove existing credentials on the account or to update the credential threshold on the account. Note that the initial credential with index 0 cannot be removed.

    // The signed credential that is to be deployed on the account. Received from the
// credential holder.
const signedCredentialForExistingAccount: CredentialDeploymentInfo = ...

// The credentials that are deployed have to be indexed. Index 0 is used up
// by the initial credential on an account. The indices that have already been
// used can be found in the AccountInfo.
const accountAddress = new AccountAddress("3sAHwfehRNEnXk28W7A3XB3GzyBiuQkXLNRmDwDGPUe8JsoAcU");
const accountInfo = await client.getAccountInfo(accountAddress, lastFinalizedBlockHash);
const nextAvailableIndex = Math.max(...Object.keys(accountInfo.accountCredentials).map((key) => Number(key))) + 1;

// The current number of credentials on the account is required, as it is used to calculate
// the correct energy cost.
const currentNumberOfCredentials = BigInt(Object.keys(accountInfo.accountCredentials).length);

const newCredential: IndexedCredentialDeploymentInfo = {
cdi: signedCredentialForExistingAccount,
index: nextAvailableIndex
};

// List the credential id (credId) of any credentials that should be removed from the account.
// The existing credentials (and their credId) can be found in the AccountInfo.
const credentialsToRemove = ["b0f11a9dcdd0758c8eec717956455deed73a0db59995da2cb20d73ee974eb39aec2c79970c640126827a8fbb84217424"];

// Update the credential threshold to 2, so that transactions require signatures from both
// of the credentials. If left at e.g. 1, then both credentials can create transactions
// by themselves.
const threshold = 2;

const updateCredentialsPayload: UpdateCredentialsPayload = {
newCredentials: [newCredential],
removeCredentialIds: credentialsToRemove,
threshold: threshold,
currentNumberOfCredentials: currentNumberOfCredentials,
};

Deploy module

The following example demonstrates how to construct a "deployModule" transaction, which is used to deploy a smart contract module.

From ./../examples/nodejs/common/deployModule.ts#63~101

    const walletFile = readFileSync(cli.flags.walletFile, 'utf8');
const wallet = parseWallet(walletFile);
const sender = AccountAddress.fromBase58(wallet.value.address);

// Get the wasm file as a buffer.
const wasmModule = Buffer.from(readFileSync(cli.flags.moduleFile));

// Note that if built using cargo-concordium `1.0.0`, the version should be added
// to the payload. In `2.0.0` and newer, the version is prepended into the module
// itself. To deploy a V0 module, which has been built with cargo-concordium
// version below 2, you should add the version field to the payload.
const deployModule: DeployModulePayload = {
source: wasmModule,
};

const header: AccountTransactionHeader = {
expiry: TransactionExpiry.futureMinutes(60),
nonce: (await client.getNextAccountNonce(sender)).nonce,
sender,
};

const deployModuleTransaction: AccountTransaction = {
header,
payload: deployModule,
type: AccountTransactionType.DeployModule,
};

// Sign transaction
const signer = buildAccountSigner(wallet);
const signature: AccountTransactionSignature = await signTransaction(deployModuleTransaction, signer);

const transactionHash = await client.sendAccountTransaction(deployModuleTransaction, signature);

console.log('Transaction submitted, waiting for finalization...');

const status = await client.waitForTransactionFinalization(transactionHash);
console.dir(status, { depth: null, colors: true });

Init Contract

The following example demonstrates how to initialize a smart contract from a module, which has already been deployed. The name of the contract "weather".

From ./../examples/nodejs/composed-examples/initAndUpdateContract.ts#89~123


const initHeader: AccountTransactionHeader = {
expiry: TransactionExpiry.futureMinutes(60),
nonce: (await client.getNextAccountNonce(sender)).nonce,
sender,
};

const initParams = serializeInitContractParameters(contractName, sunnyWeather, schema!.buffer);

const initPayload: InitContractPayload = {
amount: CcdAmount.zero(),
moduleRef: moduleRef,
initName: contractName,
param: initParams,
maxContractExecutionEnergy: maxCost,
};

const initTransaction: AccountTransaction = {
header: initHeader,
payload: initPayload,
type: AccountTransactionType.InitContract,
};

const initSignature = await signTransaction(initTransaction, signer);
const initTrxHash = await client.sendAccountTransaction(initTransaction, initSignature);

console.log('Transaction submitted, waiting for finalization...');

const initStatus = await client.waitForTransactionFinalization(initTrxHash);
console.dir(initStatus, { depth: null, colors: true });

const contractAddress = affectedContracts(initStatus.summary)[0];

Update Contract

The following example demonstrates how to update a smart contract.

To update a smart contract we create a 'updateContractTransaction'. To do this we need to specify the name of the receive function, which should contain the contract name as a prefix (So if the contract has the name "weather" and the receive function has the name "set" then the receiveName should be "weather.set").

We also need to supply the contract address of the contract instance. This consists of an index and a subindex.

From ./../examples/nodejs/composed-examples/initAndUpdateContract.ts#133~170


const updateHeader: AccountTransactionHeader = {
expiry: TransactionExpiry.futureMinutes(60),
nonce: (await client.getNextAccountNonce(sender)).nonce,
sender,
};

const updateParams = serializeUpdateContractParameters(
contractName,
EntrypointName.fromString('set'),
rainyWeather,
schema!.buffer
);

const updatePayload: UpdateContractPayload = {
amount: CcdAmount.zero(),
address: unwrap(contractAddress),
receiveName,
message: updateParams,
maxContractExecutionEnergy: maxCost,
};

const updateTransaction: AccountTransaction = {
header: updateHeader,
payload: updatePayload,
type: AccountTransactionType.Update,
};

const updateSignature = await signTransaction(updateTransaction, signer);
const updateTrxHash = await client.sendAccountTransaction(updateTransaction, updateSignature);

console.log('Transaction submitted, waiting for finalization...');

const updateStatus = await client.waitForTransactionFinalization(updateTrxHash);
console.dir(updateStatus, { depth: null, colors: true });

Smart contract parameters

In this section we will describe how to provide contracts with parameters. The user should provide the input in the JSON format specified in our documentation.

If the called smart contract function does not take any parameters, you can simply pass an empty buffer as the parameters:

    const params = Buffer.from([]);

If the function does take parameters, however, then you will need to construct them correctly. Let's consider the following example where the contract's initialization parameter is the following structure:

    #[derive(SchemaType, Serialize)]
struct MyStruct {
age: u16,
name: String,
city: String,
}

An example of a valid input would be:

    const userInput = {
age: 51,
name: 'Concordium',
city: 'Zug',
};

An other example could be if the parameter is the following "SomeEnum":

    #[derive(SchemaType, Serialize)]
enum AnotherEnum {
D,
}
#[derive(SchemaType, Serialize)]
enum SomeEnum {
B(AnotherEnum),
}

Then the following would be a valid input:

    const userInput = {
B: [
{
D: []
}
]
};

Then the user needs to provide the schema for the module.

We can get the schema from the smart contract itself if it is embedded the contract:

    const schema = await client.getEmbeddedSchema(moduleRef);

We can also load the schema from a file:

    const rawModuleSchema = Buffer.from(fs.readFileSync(
'SCHEMA-FILE-PATH'
));

Then the parameters can be serialized into bytes:

    const inputParams = serializeInitContractParameters(
"my-contract-name",
userInput,
rawModuleSchema,
schemaVersion
);

For V0 contracts the schemaVersion should be SchemaVersion.V0. For V1 contracts it should currently be SchemaVersion.V1, unless the contract have been built using cargo-concordium >=2.0.0, which are internally versioned, and then the version does not need to be provided.

Serialize parameters with only the specific type's schema

In the previous section the schema used was assumed to be the schema for an entire module. In some cases one might want to use a schema containing only the specific type of the parameter.

For this, the function serializeTypeValue can used.

    const inputParams = serializeTypeValue(userInput, rawTypeSchema);

For reference, the type schema for parameters can be extracted using the functions getInitContractParameterSchema and getUpdateContractParameterSchema. For a receive function:

    const rawTypeSchema = getUpdateContractParameterSchema(
rawModuleSchema,
contractName,
receiveFunctionName,
schemaVersion
)

And for the initialization:

    const rawTypeSchema = getInitContractParameterSchema(
rawModuleSchema,
contractName,
schemaVersion
)

Generated using TypeDoc