Skip to content
Bernardo Ramos edited this page Jul 1, 2020 · 7 revisions

Connections

To be able to communicate with the Aergo blockchain we need to specify the host address and the TCP port.

We create an instance using the aergo_connect function.

Example:

aergo *instance;

/* connect to the blockchain */
instance = aergo_connect("testnet-api.aergo.io", 7845);

/* use the instance object on function calls */
...

/* release the object when no longer needed */
aergo_free(instance);

Asynchronous Calls

Blockchains have many advantages but they are slower than traditional databases.

One request using a transaction can take up to 1 second. If you want to make your application responsive while a transaction is being processed on the blockchain you can use asynchronous calls and receive a transaction receipt on a callback function.

As there is no global cross-platform message loop, libaergo has a function to be called to check for incoming results and notifications:

int aergo_process_requests(aergo *instance, int timeout);

If your application uses a message loop, put a call to this function on a timer and use the timeout argument 0

void on_timer() {
  aergo_process_requests(instance, 0);
}

This function returns the number of active requests. If it is zero, you can stop the timer.

You can also use it in a loop and set a value for the timeout.


Accounts

If your application will call smart contract functions (not query) or if it will hold and transfer tokens then it will need an account.

An account is based on a secret+public key pairs. It has an address used to identify the account and it is generated from the public key.

The account objects are allocated by your application. This is the structure:

struct aergo_account {
  bool use_ledger;
  int  index;
  unsigned char privkey[32];
  unsigned char pubkey[33];
  char address[64];
  uint64_t nonce;
  double balance;
  uint8_t state_root[32];
  bool is_updated;
};

Make sure that the memory is zeroed after allocating the object.

Using Ledger Nano

If the account is stored on a Ledger Nano device you can access it by setting this element:

  account.use_ledger = true;

It is also possible to select the Aergo account index on the device:

  account.index = <number>;

The default account index is zero.

Private key

When not using a hardware wallet it is required to inform the account private key.

The private/secret key is a random 32 byte byte array.

Your application is responsible for generating a true random private key, storing it into persistent storage and loading it into the account object.

It is very important to to use a very strong source of randomness to generate the private key.

On Linux you can use getrandom if available

On Mac you can use SecRandomCopyBytes

On Windows you can use RtlGenRandom

As not any random 32 bytes are valid private keys, we must verify if the generated random bytes are valid:

  do {
    fill_random(account.privkey, 32);
  } while (aergo_check_privkey(instance, &account) == false);

Account state

After loading the private key into the account structure we can fill the remaining fields by requesting information about the state of the account to the blockchain.

  bool success = aergo_get_account_state(instance, &account, error);

Now you can check the account address, the balance, the nonce and the account's state root.

You can check and compile the blockchain account info example

For details about the history of transactions for a specific account please access the aergoscan

To add balance to a specific account on the testnet please access the faucet


Transaction Receipts

Transactions are used for value transfers and smart contract execution (call).

A receipt is generated when a transaction is processed by the the blockchain network.

Your application will receive the receipt with this format:

struct transaction_receipt {
  char contractAddress[56];
  char status[16];
  char ret[2048];
  uint64_t blockNo;
  char blockHash[32];
  int32_t txIndex;
  char txHash[32];
  uint64_t gasUsed;
  double feeUsed;
  bool feeDelegation;
};

The status can be:

  • SUCCESS

    For smart contract calls the result is available in the ret field

  • ERROR

    Failed transfer or smart contract execution. The error message can be found in the ret field

On synchronous calls the receipt is returned as a function parameter.

On asynchronous calls the receipt is returned on the registered callback function.

The callback functions used to receive the transaction receipt have this format:

void on_transaction_receipt(void *arg, transaction_receipt *receipt) {

}

Your application can request transaction receipts using these functions:

bool aergo_get_receipt(aergo *instance,
  const char *txn_hash,
  struct transaction_receipt *receipt);

bool aergo_get_receipt_async(aergo *instance,
  const char *txn_hash,
  transaction_receipt_cb cb,
  void *arg);

Smart Contracts

Smart contracts are a group of functions stored on the blockchain that can be called by external applications or users.

Aergo uses Lua as the programming language for these contracts and they support SQL.

You can write a smart contract, publish it on the blockchain and call it from your application.

There are 2 types of function calls on smart contracts:

Those who write to the blockchain are named calls

Those who just read from the blockchain are named queries

Calls are made via blockchain transactions and they consume tokens from the caller account.

Queries are made without transactions and they don't need to be signed by an account. They do not require payment for the processing.

Smart Contract Calls

They are used when your application will send data to be processed by and stored into the blockchain.

The application needs to specify the contract address and the smart contract function name. The arguments to the function call are optional.

The arguments can be passed using a variadic function call specifying the type of each argument.

  • s - string
  • i - integer
  • l - long
  • d - double
  • b - binary data (expects 2 arguments: pointer and size)

Here is an example of a synchronous call using this method:

transaction_receipt receipt;

bool success = aergo_call_smart_contract(instance,
  &receipt,
  &account,
  "...",          /* contract_address */
  "my_function",  /* function */
  "isdb",         /* arguments types */
  123, "testing", 2.5, pic_ptr, pic_len);

You can check and compile the smart contract call example

And here is the asynchronous version of this call:

void on_transaction_receipt(void *arg, transaction_receipt *receipt) {
  ...
}

bool success = aergo_call_smart_contract_async(instance,
  on_transaction_receipt, NULL,
  &account,
  "...",          /* contract_address */
  "my_function",  /* function */
  "isdb",         /* arguments types */
  123, "testing", 2.5, pic_ptr, pic_len);

Don't forget to call the aergo_process_requests() function on a timer or a loop to process the incoming result.

You can check and compile the smart contract asynchronous call example

If the arguments must be joined before the function call then you can serialize the arguments into a JSON array and use this functions instead:

transaction_receipt receipt;

bool success = aergo_call_smart_contract_json(instance,
  &receipt,
  &account,
  "...",          /* contract_address */
  "my_function",  /* function */
  json_array);    /* arguments */

There is also the asynchronous version of this function.

Smart Contract Query

They are used when your application will just read data from the blockchain.

The API are similar to the functions used to smart contract calls.

Here is an example of a synchronous query:

char result[4096];

bool success = aergo_query_smart_contract(instance,
  result, sizeof result,
  "...",          /* contract_address */
  "my_function",  /* function */
  "isdb",         /* arguments types */
  123, "testing", 2.5, data_ptr, data_len);

You can check and compile the smart contract query example

And here is the asynchronous version of this call:

void on_smart_contract_query_result(void *arg, bool success, char *result){
  ...
}

bool aergo_query_smart_contract_async(aergo *instance,
  query_smart_contract_cb cb,
  void *arg,
  const char *contract_address,
  const char *function,
  const char *types,
  ...);

Don't forget to call the aergo_process_requests() function on a timer or a loop to process the incoming result.

You can check and compile the smart contract asynchronous query example

If the arguments must be joined before the function call then you can serialize the arguments into a JSON array and use this functions instead:

char result[4096];

bool success = aergo_query_smart_contract_json(instance,
  result, sizeof result,
  "...",          /* contract_address */
  "my_function",  /* function */
  json_array);    /* arguments */

There is also the asynchronous version of this function.

Smart contract events

Smart contracts can generate events and your application can subscribe to receive event notifications.

The notification data is presented to the application as a contract_event object:

struct contract_event {
  char contractAddress[64];
  char eventName[64];
  char jsonArgs[2048];
  int32_t eventIdx;
  char txHash[32];
  char blockHash[32];
  uint64_t blockNo;
  int32_t txIndex;
};

Here is the structure of the callback function and the function used to subscribe to the events:

void on_smart_contract_event(void *arg, contract_event *event){
  ...
}

bool success = aergo_contract_events_subscribe(
  instance,
  "...",   /* contract_address */
  "",      /* event_name - let empty to receive all events */
  on_smart_contract_event,
  NULL);   /* context pointer passed to the callback function */

Don't forget to call the aergo_process_requests() function on a timer or a loop to process the incoming result.

You can check and compile the full example


Transfer

Accounts can hold tokens and they can be transferred to other accounts.

libaergo supports 4 different formats for the amount when transferring tokens.

And there are synchronous and asynchronous versions for these functions.

Amount as Double

bool aergo_transfer(aergo *instance,
  transaction_receipt *receipt,
  aergo_account *from_account,
  const char *to_account,
  double value);

bool aergo_transfer_async(aergo *instance,
  transaction_receipt_cb cb,
  void *arg,
  aergo_account *from_account,
  const char *to_account,
  double value);

Amount as integer and decimal parts

Note: decimal part with 18 digits!

Example: in 3.5 the integer part is 3 and the decimal part is 500000000000000000

bool aergo_transfer_int(aergo *instance,
  transaction_receipt *receipt,
  aergo_account *from_account,
  const char *to_account,
  uint64_t integer,
  uint64_t decimal);

bool aergo_transfer_int_async(aergo *instance,
  transaction_receipt_cb cb,
  void *arg,
  aergo_account *from_account,
  const char *to_account,
  uint64_t integer,
  uint64_t decimal);

Amount as String

It must be null terminated. The unit is aergo

Example: "130.25"

bool aergo_transfer_str(aergo *instance,
  transaction_receipt *receipt,
  aergo_account *from_account,
  const char *to_account,
  const char *value);

bool aergo_transfer_str_async(aergo *instance,
  transaction_receipt_cb cb,
  void *arg,
  aergo_account *from_account,
  const char *to_account,
  const char *value);

Amount as big-endian variable size integer

This is the format exported from bignumber/biginteger implementations

bool aergo_transfer_bignum(aergo *instance,
  transaction_receipt *receipt,
  aergo_account *from_account,
  const char *to_account,
  const unsigned char *amount,
  int len);

bool aergo_transfer_bignum_async(aergo *instance,
  transaction_receipt_cb cb,
  void *arg,
  aergo_account *from_account,
  const char *to_account,
  const unsigned char *amount,
  int len);

You can check and compile the transfer example as well as the asynchronous transfer example

Clone this wiki locally