Skip to main content

Contract Interactions

Interactions are state-changing calls to smart contract endpoints. They require gas, signing, and are recorded on-chain.

Basic Interaction Flow

import 'package:abidock_mvx/abidock_mvx.dart';
import 'dart:io';

void main() async {
final provider = GatewayNetworkProvider.devnet();
final account = await Account.fromMnemonic('your mnemonic...');
final networkAccount = await provider.getAccount(account.address);

final abiJson = await File('contract.abi.json').readAsString();
final abi = SmartContractAbi.fromJson(abiJson);

final controller = SmartContractController(
contractAddress: SmartContractAddress.fromBech32('erd1qqq...'),
networkProvider: provider,
abi: abi,
);

// 1. Build and sign the transaction
final tx = await controller.call(
account: account,
nonce: networkAccount.nonce,
endpointName: 'deposit',
arguments: [],
options: BaseControllerInput(gasLimit: GasLimit(10000000)),
);

// 2. Send it
final txHash = await provider.sendTransaction(tx);
print('Transaction: $txHash');

// 3. Wait for completion
final watcher = TransactionWatcher(networkProvider: provider);
final result = await watcher.awaitCompleted(txHash);
print('Status: ${result.status.status}');
}

Sending EGLD to Contract

// Payable endpoint that accepts EGLD
final tx = await controller.call(
account: account,
nonce: networkAccount.nonce,
endpointName: 'deposit',
arguments: [],
value: Balance.fromEgld(1.0),
options: BaseControllerInput(gasLimit: GasLimit(10000000)),
);

Sending ESDT Tokens

// Single ESDT payment
final tx = await controller.call(
account: account,
nonce: networkAccount.nonce,
endpointName: 'stake',
arguments: [],
tokenTransfers: [
TokenTransferValue.fromPrimitives(
tokenIdentifier: 'USDC-123456',
amount: BigInt.from(1000000), // 1 USDC (6 decimals)
),
],
options: BaseControllerInput(gasLimit: GasLimit(15000000)),
);

Sending NFTs

// NFT payment
final tx = await controller.call(
account: account,
nonce: networkAccount.nonce,
endpointName: 'depositNft',
arguments: [],
tokenTransfers: [
TokenTransferValue.fromPrimitives(
tokenIdentifier: 'MYNFT-abc123',
nonce: BigInt.from(42),
amount: BigInt.one,
),
],
options: BaseControllerInput(gasLimit: GasLimit(15000000)),
);

Multi-Token Transfers

// Multiple tokens in one call
final tx = await controller.call(
account: account,
nonce: networkAccount.nonce,
endpointName: 'addLiquidity',
arguments: [
BigInt.from(1000), // slippage
],
tokenTransfers: [
TokenTransferValue.fromPrimitives(
tokenIdentifier: 'WEGLD-123456',
amount: BigInt.parse('500000000000000000'), // 0.5 WEGLD
),
TokenTransferValue.fromPrimitives(
tokenIdentifier: 'USDC-789012',
amount: BigInt.from(500000000), // 500 USDC
),
],
options: BaseControllerInput(gasLimit: GasLimit(25000000)),
);

With Complex Arguments

Arguments are passed as native Dart values:

// Struct argument - pass as Map
// Field types determine what native values to use:
// - U32 fields take int
// - U64 fields take int or BigInt
final tx = await controller.call(
account: account,
nonce: networkAccount.nonce,
endpointName: 'createUser',
arguments: [
{
'name': 'Alice',
'age': 30, // U32 takes int
},
],
options: BaseControllerInput(gasLimit: GasLimit(10000000)),
);
// List argument with U64 values
final tx = await controller.call(
account: account,
nonce: networkAccount.nonce,
endpointName: 'processIds',
arguments: [
[BigInt.from(1), BigInt.from(2), BigInt.from(3)], // U64 takes BigInt
],
options: BaseControllerInput(gasLimit: GasLimit(10000000)),
);

Reading Transaction Results

After a transaction completes, parse the results:

// Wait for transaction
final watcher = TransactionWatcher(networkProvider: provider);
final txOnNetwork = await watcher.awaitCompleted(txHash);

// Check status
if (!txOnNetwork.isSuccessful) {
print('Failed: ${txOnNetwork.status.status}');
return;
}

// Parse return values from smart contract results
final scResults = txOnNetwork.smartContractResults;
if (scResults != null) {
for (final result in scResults) {
print('Result data: ${result['data']}');
}
}

Gas Estimation

Manual Gas Setting

final tx = await controller.call(
account: account,
nonce: networkAccount.nonce,
endpointName: 'myFunction',
arguments: [],
options: BaseControllerInput(gasLimit: GasLimit(15000000)),
);

Auto Gas Estimation with simulateGas

Use the simulateGas helper to estimate gas via network simulation:

// Step 1: Create transaction with max gas for simulation
final simulationTx = await controller.call(
account: account,
nonce: networkAccount.nonce,
endpointName: 'myFunction',
arguments: [],
options: BaseControllerInput(gasLimit: const GasLimit(600000000)),
);

// Step 2: Estimate gas using simulation
final gasLimit = await simulateGas(simulationTx, provider);

// Step 3: Create final transaction with estimated gas
final tx = await controller.call(
account: account,
nonce: networkAccount.nonce,
endpointName: 'myFunction',
arguments: [],
options: BaseControllerInput(gasLimit: gasLimit),
);
Generated Code

When using --autogas with the code generator, this simulation pattern is handled automatically in the generated call functions.

Common Gas Values

OperationTypical Gas
Simple call5,000,000 - 10,000,000
Token transfer + call10,000,000 - 20,000,000
Complex computation20,000,000 - 50,000,000
Multi-transfer call20,000,000 - 30,000,000
Gas Buffer

Add 20-30% buffer to estimated gas to handle variations:

final estimatedGas = 10000000;
final gasWithBuffer = (estimatedGas * 1.3).toInt();

Error Handling

try {
final tx = await controller.call(
account: account,
nonce: networkAccount.nonce,
endpointName: 'myFunction',
arguments: [],
options: BaseControllerInput(gasLimit: GasLimit(10000000)),
);

final txHash = await provider.sendTransaction(tx);

final watcher = TransactionWatcher(networkProvider: provider);
final result = await watcher.awaitCompleted(txHash);

if (!result.isSuccessful) {
// Parse error from results
print('Transaction failed: ${result.status.status}');
}
} on SmartContractException catch (e) {
print('Contract error: ${e.message}');
} on NetworkException catch (e) {
print('Network error: ${e.message}');
} on TransactionException catch (e) {
print('Transaction error: ${e.message}');
}

Complete Example

import 'package:abidock_mvx/abidock_mvx.dart';
import 'dart:io';

void main() async {
final provider = GatewayNetworkProvider.devnet();

// Load account
final account = await Account.fromMnemonic('your mnemonic phrase here...');
final networkAccount = await provider.getAccount(account.address);

print('Sender: ${account.address.bech32}');

// Load ABI
final abiJson = await File('assets/adder.abi.json').readAsString();
final abi = SmartContractAbi.fromJson(abiJson);

final controller = SmartContractController(
contractAddress: SmartContractAddress.fromBech32('erd1qqq...'),
networkProvider: provider,
abi: abi,
);

// Build transaction to call "add" function
final tx = await controller.call(
account: account,
nonce: networkAccount.nonce,
endpointName: 'add',
arguments: [BigInt.from(100)],
options: BaseControllerInput(gasLimit: GasLimit(10000000)),
);

print(' Function: add');
print(' Argument: 100');
print(' Gas: ${tx.gasLimit.value}');

// Send
final txHash = await provider.sendTransaction(tx);
print('Sent: $txHash');

// Wait
final watcher = TransactionWatcher(networkProvider: provider);
final result = await watcher.awaitCompleted(txHash);


print('Status: ${result.status.status}');

// Query new value
final newValue = await controller.query(
endpointName: 'getSum',
arguments: [],
);

print('New sum: ${newValue.first}');
}

Next Steps

Interactions Without ABI

When you don't have an ABI or prefer manual encoding, use callRaw():

import 'package:abidock_mvx/abidock_mvx.dart';

void main() async {
final provider = GatewayNetworkProvider.devnet();

// Create controller WITHOUT ABI
final controller = SmartContractController.withoutAbi(
contractAddress: SmartContractAddress.fromBech32('erd1qqq...'),
networkProvider: provider,
);

// Load account
final account = await Account.fromMnemonic('your mnemonic...');
final networkAccount = await provider.getAccount(account.address);

// Create transaction with TypedValue arguments
final tx = await controller.callRaw(
account: account,
nonce: networkAccount.nonce,
endpointName: 'swap',
arguments: [
TokenIdentifierValue('WEGLD-abc123'),
BigUIntValue(BigInt.from(1000000000000000000)),
],
options: BaseControllerInput(gasLimit: GasLimit(15000000)),
);

// Send transaction
final txHash = await provider.sendTransaction(tx);
print('Sent: $txHash');
}

Raw Calls with Token Transfers

Token transfers work the same way with raw calls:

// ESDT transfer with raw call
final tx = await controller.callRaw(
account: account,
nonce: networkAccount.nonce,
endpointName: 'stake',
arguments: [],
tokenTransfers: [
TokenTransferValue.fromPrimitives(
tokenIdentifier: 'STAKE-abc123',
amount: BigInt.from(1000000),
),
],
options: BaseControllerInput(gasLimit: GasLimit(15000000)),
);

When to Use Raw Calls

ScenarioApproach
Have ABI fileUse call() with native values
No ABI availableUse callRaw() with TypedValue
Working with unknown contractsUse callRaw()
Custom argument encodingUse callRaw()
Raw bytes as argumentsUse Uint8List directly

Combining Query and Call Without ABI

// Controller without ABI
final controller = SmartContractController.withoutAbi(
contractAddress: pairAddress,
networkProvider: provider,
);

// Query current state (raw)
final reserves = await controller.queryRaw(
endpointName: 'getReservesAndTotalSupply',
arguments: [],
);

// Decode reserves manually
final codec = BinaryCodec.withDefaults();
final firstReserve = codec.decodeTopLevel(
reserves[0],
BigUIntType(),
) as BigUIntValue;
print('First reserve: ${firstReserve.value}');

// Execute swap (raw)
final tx = await controller.callRaw(
account: account,
nonce: networkAccount.nonce,
endpointName: 'swapTokensFixedInput',
arguments: [
TokenIdentifierValue('USDC-123456'),
BigUIntValue(BigInt.one), // minimum amount out
],
tokenTransfers: [
TokenTransferValue.fromPrimitives(
tokenIdentifier: 'WEGLD-abc123',
amount: BigInt.from(1000000000000000000),
),
],
options: BaseControllerInput(gasLimit: GasLimit(30000000)),
);
TypedValue Types

Common TypedValue types for raw calls:

  • BigUIntValue(BigInt.from(100)) - unsigned big integers
  • AddressValue.fromBech32('erd1...') - addresses
  • TokenIdentifierValue('TOKEN-abc123') - token identifiers
  • BooleanValue(true) - booleans
  • U64Value(BigInt.from(1000)) - u64 integers
  • BytesValue(Uint8List.fromList([...])) - raw bytes