Skip to main content

Interact with Contracts

Alephium's full node provides a set of APIs to interact with the deployed contracts. These interactions can be divided into two categories depending on whether they intend to update the state of the blockchain: Contract calls are read-only interactions that are gas free, executed straight away with result returned to the caller immediately. TxScript transactions on the other hand update the blockchain state, require gas, get executed when transactions are mined with only the transaction ids returned to the caller.

Interacting with contracts directly through the full node API can be tedious. Alephium's Web3 SDK abstracts away many details by generating wrapper code for contracts and transaction scripts. Let's demonstrate its usefulness using the TokenFaucet contract discussed in the Getting started guide:

token_faucet.ral

import "std/fungible_token_interface"

// Defines a contract named `TokenFaucet`.
// A contract is a collection of fields (its state) and functions.
// Once deployed, a contract resides at a specific address on the Alephium blockchain.
// Contract fields are permanently stored in contract storage.
// A contract can issue an initial amount of token at its deployment.
Contract TokenFaucet(
symbol: ByteVec,
name: ByteVec,
decimals: U256,
supply: U256,
mut balance: U256
) implements IFungibleToken {

// Events allow for logging of activities on the blockchain.
// Alephium clients can listen to events in order to react to contract state changes.
event Withdraw(to: Address, amount: U256)

enum ErrorCodes {
InvalidWithdrawAmount = 0
}

// A public function that returns the initial supply of the contract's token.
// Note that the field must be initialized as the amount of the issued token.
pub fn getTotalSupply() -> U256 {
return supply
}

// A public function that returns the symbol of the token.
pub fn getSymbol() -> ByteVec {
return symbol
}

// A public function that returns the name of the token.
pub fn getName() -> ByteVec {
return name
}

// A public function that returns the decimals of the token.
pub fn getDecimals() -> U256 {
return decimals
}

// A public function that returns the current balance of the contract.
pub fn balance() -> U256 {
return balance
}

// A public function that transfers tokens to anyone who calls it.
// The function is annotated with `updateFields = true` as it changes the contract fields.
// The function is annotated as using contract assets as it does.
@using(assetsInContract = true, updateFields = true, checkExternalCaller = false)
pub fn withdraw(amount: U256) -> () {
// Debug events can be helpful for error analysis
emit Debug(`The current balance is ${balance}`)

// Make sure the amount is valid
assert!(amount <= 2, ErrorCodes.InvalidWithdrawAmount)
// Functions postfixed with `!` are built-in functions.
transferTokenFromSelf!(callerAddress!(), selfTokenId!(), amount)
// Ralph does not allow underflow.
balance = balance - amount

// Emit the event defined earlier.
emit Withdraw(callerAddress!(), amount)
}
}

The TokenFaucet contract has 5 public functions that only reads the contract states: getTotalSupply, getSymbol, getName, getDecimals and balance. It also has a function called withdraw, which not only updates the contract state, but also transfers assets.

After TokenFaucet contract is compiled, a corresponding Typescript class is generated. We can get an instance of this class after deploying it to devnet:

import { DUST_AMOUNT } from '@alephium/web3'
import { getSigner } from '@alephium/web3-test'
import { TokenFaucet, TokenFaucetTypes, Withdraw } from '../artifacts/ts'

const signer = await getSigner()
const deployResult = await TokenFaucet.deploy(signer, {
initialFields: {
symbol: stringToHex('TF'),
name: stringToHex('TokenFaucet'),
decimals: 18n,
supply: 10n ** 18n,
balance: 10n
}
})

const tokenFaucet = deployResult.contractInstance

Contract Call

Inside of the TokenFaucet typescript class, Alephium SDK generates methods for all of the pure functions in the TokenFaucet contract. They can be called just like regular typescript functions:

const getNameResult = await tokenFaucet.methods.getName()
console.log(`name: ${hexToString(getNameResult.returns)}`) // name: TokenFaucet

const getDecimalsResult = await tokenFaucet.methods.getDecimals()
console.log(`decimals: ${getDecimalsResult.returns)}`) // decimals: 18

Note that results of the contract calls are returned immediately and there is no gas or signatures required.

Alephium SDK also generates code for calling multiple pure functions at the same time, reducing the number of network requests:

const result = await tokenFaucet.multicall({
getSymbol: {},
getName: {},
getDecimals: {},
getTotalSupply: {}
})

console.log(`name: ${hexToString(result.getName.returns)}`) // name: TokenFaucet
console.log(`symbol: ${hexToString(result.getSymbol.returns)}`) // symbol: TF
console.log(`decimals: ${result.getDecimals.returns}`) // decimals: 18
console.log(`total supply: ${result.getTotalSupply.returns}`) // total supply: 10

TxScript Transactions

TokenFaucet contract also has a function called withdraw, which transfers the token from the faucet to the caller and updates the balance. When calling withdraw function we'll need to execute it as a transaction using TxScript:

TxScript Withdraw(token: TokenFaucet, amount: U256) {
// Call token contract's withdraw function.
token.withdraw(amount)
}

Alephium's SDK also generates a corresponding typescript class for the Withdraw transaction script after compilation, which we can use execute the transaction:

const signer = await getSigner()
const withdrawResult = await Withdraw.execute(signer, {
initialFields: { token: tokenFaucet.contractId, amount: 2n },
attoAlphAmount: DUST_AMOUNT * 2n
})
console.log(`tx id: ${withdrawResult.txId}`) // tx id: xxxx

We pass in the initialFields as defined in the Withdraw transaction script, and enough attoAlphAmount for covering the gas as well as the dust amount for receiving tokens.

Note that a signer is required to sign the transaction. The execution result of the Withdraw script contains a transaction id, which can be used to query the status of the transaction later. Some other useful information in the execution result include:

  • unsignedTx: Serialized version of unsigned transaction
  • signature: Signer's signature for the transaction
  • gasAmount: Gas cost of the transaction

Function executed from TxScript can not return value to the caller directly because the transaction will be processed later and the result is non-deterministic depending on the future state of the blockchain when the transaction is mined. Events are instead often used to gain insights into the contract activities.

Events Subscription

The withdraw function for TokenFaucet contract emits a Withdraw event, which contains the recipient and amount of the withdrawn token.

emit Withdraw(callerAddress!(), amount)

Events like this are very useful for dApps to track contract activities. Alephium's Web3 SDK provides a set of functions to interact with contract's events, here is how we can subscribe to the Withdraw event emitted from the withdraw function in TokenFaucet contract:

// Subscribe to the `Withdraw` event in
tokenFaucet.subscribeWithdrawEvent({
pollingInterval: 500,
messageCallback: (event: TokenFaucetTypes.WithdrawEvent): Promise<void> => {
console.log('got withdraw event:', event)
return Promise.resolve()
},
errorCallback: (error: any, subscription): Promise<void> => {
console.log(error)
subscription.unsubscribe()
return Promise.resolve()
}
})

If there are more than one events emitted from the TokenFaucet contract, a function called subscribeAllEvents will also be generated to subscribe to all events emitted from the contract.

Further Reading

Alephium's Web3 SDK builds developer friendly abstractions on top of Alephium's full node APIs. For more details about the APIs please refer to the OpenAPI Documentation.

Please read a more detailed explanation about TxScript here, a unique feature in Alephium that is a more flexible and efficient way of creating transactions that interact with smart contracts.

Events are crucial to build dApps, more information can be found here.