Skip to content

Compile & Deploy a Sophia Smart Contract

Introduction

The whole script is located in the repository and this page explains in detail how to:

  • deal with the different phases of compiling Sophia contracts to bytecode
  • deploy the bytecode to get a callable contract address
  • invoke the deployed contract on the æternity blockchain

1. Specify imports

You need to import AeSdk, Node and MemoryAccount classes from the SDK.

const { AeSdk, Node, MemoryAccount } = require('@aeternity/aepp-sdk');
Note:

  • You need to have the SDK installed via npm i @aetenity/aepp-sdk -g to run that example code.

2. Define constants

The following constants are used in the subsequent code snippets. typically you read the source code from a separate .aes file

const CONTRACT_SOURCE = `
contract Multiplier =
  record state = { factor: int }
  entrypoint init(f : int) : state = { factor = f }
  stateful entrypoint setFactor(f : int): int =
    put(state{ factor = f })
    f * 10
  entrypoint multiplyBy(x : int) = x * state.factor
`;
const ACCOUNT_KEYPAIR = {
  publicKey: 'ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR',
  secretKey: 'bf66e1c256931870908a649572ed0257876bb84e3cdf71efb12f56c7335fad54d5cf08400e988222f26eb4b02c8f89077457467211a6e6d955edb70749c6a33b',
};
const NODE_URL = 'https://testnet.aeternity.io';
const COMPILER_URL = 'https://compiler.aepps.com';
Note:

  • The keypair of the account is pre-funded and only used for demonstration purpose
  • In case the account runs out of funds you can always request AE using the Faucet

3. Open async codeblock

Most functions of the SDK return Promises, so the recommended way of dealing with subsequent actions is running them one by one using await. Therefore you need to put the logic into an async code block

(async () => {

4. Create object instances

  const account = new MemoryAccount({ keypair: ACCOUNT_KEYPAIR });
  const node = new Node(NODE_URL);
  const aeSdk = new AeSdk({
    nodes: [{ name: 'testnet', instance: node }],
    compilerUrl: COMPILER_URL,
  });
  await aeSdk.addAccount(account, { select: true });

5. Get contract instance

Knowing the source code allows you to initialize a contract instance and interact with the contract in a convenient way.

  console.log(CONTRACT_SOURCE);
  const contract = await aeSdk.getContractInstance({ source: CONTRACT_SOURCE });

6. Compile the contract

The compile function sends a raw Sophia contract as string to the HTTP compiler for bytecode compilation. In the future this will be done without talking to the node, but requiring a bytecode compiler implementation directly in the SDK.

  const bytecode = await contract.compile();
  console.log(`Obtained bytecode ${bytecode}`);

7. Deploy the contract

Invoking deploy on the contract instance will result in the CreateContractTx being created, signed (using the secretKey of the previously defined MemoryAccount) and broadcasted to the network. It will be picked up by the miners and written to the chain.

  const deployInfo = await contract.deploy([5]);
  console.log(`Contract deployed at ${deployInfo.address}`);
Note:

  • Sophia contracts always have an init function which needs to be invoked.
  • The SDK generates the required calldata for the provided arguments by @aeternity/aepp-calldata package.

8. Call a contract function

Once the ContractCreateTx has been successfully mined, you can attempt to invoke any public function (aka entrypoint in Sophia) defined within it.

  await contract.methods.setFactor(6);
Note:

  • setFactor is a stateful entrypoint that changes to the contract's state so contract broadcasting the transaction to be mined

9. Call a contract function via dry-run

You can use callStatic option which performs a dry-run of the transaction which allows you to get the result without having to mine a transaction.

  let call = await contract.methods.setFactor(7, { callStatic: true });

10. Decode the call result

The execution result, if successful, will be an FATE-encoded result value. The decodedResult property will contain the result value decoded using calldata package.

  console.log(`setFactor execution result: ${call.decodedResult}`);

11. Call a contract non-stateful entrypoint via dry-run

  call = await contract.methods.multiplyBy(8);
  console.log(`multiplyBy execution result: ${call.decodedResult}`);
Note:

  • The contract automatically chooses to perform a dry-run call as multiplyBy is a non-stateful entrypoint
  • if multiplyBy would be a stateful entrypoint the transaction would be broadcasted to the network and picked up by miners

12. Close and run async codeblock

Now you can close the async codeblock and execute it at the same time.

})();