How to Use ABI Encoding and Decoding for Contract Interaction in Sway
Sway automatically generates abi_encode and abi_decode implementations for every type appearing in a contract's public interface, handling calldata serialization for contract calls, transaction logs, and off-chain SDK communication without manual byte manipulation.
The FuelLabs/sway compiler eliminates boilerplate serialization by auto-implementing encoding traits for all contract-visible types. When you interact with contracts—whether through the abi cast in on-chain scripts or the Rust SDK off-chain—the compiler inserts precise byte-layout operations defined by the Fuel ABI specification. This guide explains the encoding mechanism, its three primary use cases, and the exact source locations where this serialization logic resides.
How ABI Encoding Works in Sway
Sway’s compiler analyzes type signatures at compile time and generates optimized serialization routines. This process ensures that data sent between contracts, scripts, and off-chain clients follows a consistent binary layout.
Compiler-Generated Trait Implementations
For every struct, enum, array, or vector that appears in a contract’s public interface, the compiler auto-implements encoding logic in sway-core/src/semantic_analysis/ast_node/declaration/auto_impl/abi_encoding.rs. The function generate_abi_encode_struct_body constructs the encoder body by emitting call chains like self.field.abi_encode(buffer) for each struct field.
The generated methods receive a mutable Buffer and return it, allowing the compiler to concatenate encodings efficiently without intermediate allocations. This auto-implementation satisfies the AbiEncode trait requirement that the compiler enforces for all public function arguments and return values.
The Four-Step Encoding Algorithm
According to the sway-core source code, the compiler follows this precise pipeline when generating encoding instructions:
- Type inspection – The compiler resolves the concrete
TypeIdof each argument to determine its memory layout. - Size hint calculation –
TypeInfo::abi_encode_size_hint(defined insway-core/src/type_system/info.rs) computes the exact buffer size for primitive types, or falls back to “potentially infinite” for reference-containing types like dynamic vectors. - Code generation – For complex types, the compiler emits a
matchexpression that writes a discriminator tag followed by the inner value (enums), or sequential field encodings (structs). - Buffer concatenation – Each generated method appends bytes to the mutable
Buffer, which is then passed to the Fuel VM as calldata or log data.
Three Critical Use Cases for ABI Encoding
The auto-generated encoding logic serves three distinct interaction patterns in the Sway ecosystem.
Contract Calls via the ContractCaller Type
When you cast a contract address to an ABI interface using abi(AbiName, address), the compiler returns a ContractCaller<AbiName> struct. Each method call on this struct triggers automatic encoding:
abi Wallet;
fn main(wallet_addr: ContractId, amount: u64) -> u64 {
let wallet = abi(Wallet, wallet_addr);
wallet.deposit(amount); // amount is automatically abi_encode'd
wallet.withdraw(amount)
}
The compiler expands wallet.deposit(amount) into a low-level call that serializes the u64 using the struct generated in generate_abi_encode_struct_body, prepends the function selector, and sends the resulting byte vector to the contract.
Transaction Logging with __log
The __log<T> intrinsic requires that T implement AbiEncode. During semantic analysis, the compiler transforms __log(x) into encode(x) via the wrap_logged_expr_into_encode_call function in sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs:
fn foo(x: u64) {
__log(x); // Compiler inserts abi_encode call for x
}
This allows any encodable type—whether a primitive or a complex struct—to be serialized into the transaction receipt logs for debugging or event indexing.
Off-Chain SDK Encoding
The Rust fuels SDK mirrors the on-chain encoding logic to ensure compatibility. The ABIEncoder (found in the SDK harness tests at test/src/sdk-harness/test_projects/low_level_call/mod.rs) converts Rust values into Fuel ABI-encoded byte vectors:
use fuels::core::codec::{ABIEncoder, EncoderConfig};
let selector = encode_fn_selector("transfer(u64)").to_vec();
let calldata = ABIEncoder::new(EncoderConfig::default())
.encode(&[amount.into_token()])
.unwrap();
The SDK uses Tokenizable::into_token to bridge Rust types into the encoder, producing calldata that matches exactly what the on-chain abi_decode expects when the contract method is invoked.
End-to-End Implementation Examples
These practical examples demonstrate the encoding flow from contract definition to off-chain interaction.
On-Chain Contract Definition and Interaction
Define a contract with storage-mutating functions:
abi Wallet {
#[storage(read, write)]
fn deposit(amount: u64);
#[storage(read, write)]
fn withdraw(amount: u64) -> u64;
}
contract;
impl Wallet for Contract {
fn deposit(amount: u64) {
self.balance += amount;
}
fn withdraw(amount: u64) -> u64 {
let bal = self.balance;
if amount > bal {
revert("Insufficient funds");
}
self.balance = bal - amount;
amount
}
}
Call this contract from a script, letting the compiler handle encoding:
abi Wallet;
fn main(wallet_addr: ContractId, amount: u64) -> u64 {
let wallet = abi(Wallet, wallet_addr);
wallet.deposit(amount);
wallet.withdraw(amount)
}
Off-Chain Rust SDK Calldata Construction
Use the generated bindings and encoder for type-safe off-chain calls:
use fuels::{prelude::*, core::codec::ABIEncoder};
abigen!(WalletContract, "out/wallet-abi.json");
#[tokio::main]
async fn main() {
let wallet_id = ContractId::from_str("0x1234...").unwrap();
let provider = Provider::launch_custom(Some(1), Some(1), Some(1_000_000)).await.unwrap();
let wallet = WalletContract::new(wallet_id, provider);
// Manual encoding matches the on-chain abi_encode logic
let selector = encode_fn_selector("deposit(u64)").to_vec();
let calldata = ABIEncoder::default()
.encode(&[42_u64.into_token()])
.unwrap();
let _ = provider
.contract_call(wallet_id, selector, calldata)
.await
.unwrap();
}
Structured Debugging with Encoded Logs
Emit encodable events directly from contract logic:
struct DepositEvent {
sender: Address,
amount: u64,
}
fn log_deposit(sender: Address, amount: u64) {
let event = DepositEvent { sender, amount };
__log(event); // Automatically serialized via abi_encode
}
Core Source Files and Architecture
The following files in the FuelLabs/sway repository implement the encoding pipeline:
sway-core/src/semantic_analysis/ast_node/declaration/auto_impl/abi_encoding.rs– Auto-generatesabi_encodeandabi_decodeimplementations for structs, enums, and collections.sway-core/src/type_system/info.rs– Providesabi_encode_size_hintfor buffer pre-allocation calculations.sway-core/src/abi_generation/fuel_abi.rs– Generates the JSON ABI metadata consumed by off-chain tools and theabigen!macro.sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs– Containswrap_logged_expr_into_encode_callfor the__logintrinsic transformation.test/src/sdk-harness/test_projects/low_level_call/mod.rs– Demonstrates SDK-side encoding withABIEncoderandfn_selector!macros.
Summary
- Automatic implementation – The Sway compiler generates
abi_encodeandabi_decodemethods for every type insway-core/src/semantic_analysis/ast_node/declaration/auto_impl/abi_encoding.rs, eliminating manual serialization. - Contract interaction – The
abi(AbiName, address)cast produces aContractCallerthat automatically encodes arguments using the generated methods before sending calldata to the Fuel VM. - Debug logging – The
__logintrinsic relies on the same encoding pipeline, transforming logged values into ABI-encoded byte arrays viawrap_logged_expr_into_encode_call. - Cross-language compatibility – The Rust SDK’s
ABIEncoderuses identical logic to the on-chain encoder, ensuring that off-chaincalldatamatches contract expectations.
Frequently Asked Questions
How does Sway generate encoding logic for custom structs?
The compiler analyzes struct definitions during semantic analysis and invokes generate_abi_encode_struct_body in sway-core/src/semantic_analysis/ast_node/declaration/auto_impl/abi_encoding.rs to emit a method that sequentially encodes each field into a mutable Buffer.
What is the difference between on-chain and off-chain ABI encoding in Sway?
On-chain encoding happens automatically when calling methods through the abi cast, while off-chain encoding requires explicit use of the ABIEncoder in the Rust SDK. Both use the same binary layout defined by the Fuel ABI specification and generated metadata from fuel_abi.rs.
Why does the __log intrinsic require types to implement AbiEncode?
The __log<T> intrinsic serializes values into transaction receipt logs, which requires a consistent byte representation. The compiler enforces T: AbiEncode and transforms the call into an encode operation via wrap_logged_expr_into_encode_call to ensure the data is correctly serialized for the VM.
Where does the compiler calculate buffer sizes for encoding?
The TypeInfo::abi_encode_size_hint method in sway-core/src/type_system/info.rs computes exact byte sizes for primitive types and provides fallback logic for dynamically-sized references, enabling efficient buffer pre-allocation during code generation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →