Encoding and decoding of ABI Types, functions, events of Ethereum contracts
$ dotnet add package Nethereum.ABIEncoding and decoding of ABI Types, functions, events of Ethereum contracts
Nethereum.ABI is the core package for Ethereum's Application Binary Interface (ABI) encoding and decoding in .NET. It provides comprehensive support for encoding function calls, decoding function outputs, processing event logs, EIP-712 typed data signing, and handling all Solidity data types including complex structures like tuples and dynamic arrays.
This package is fundamental to all smart contract interactions in Nethereum, as it translates between .NET objects and the binary format Ethereum uses for contract communication.
dotnet add package Nethereum.ABI
Nethereum:
The ABI is a JSON specification that describes:
Function calls are encoded as:
Events are stored in transaction logs with:
Structured data hashing and signing standard that enables:
Nethereum.ABI supports all Solidity types:
uint256, int256, address, bool, bytes, bytesN, stringuint256[20], address[5]uint256[], string[]using Nethereum.ABI.FunctionEncoding;
using Nethereum.ABI.Model;
// Create encoder
var functionCallEncoder = new FunctionCallEncoder();
// Define function signature and parameters
var sha3Signature = "c6888fa1"; // First 8 hex chars of Keccak-256("functionName(paramTypes)")
var parameters = new[]
{
new Parameter("address", "recipient"),
new Parameter("uint256", "amount")
};
// Encode function call
string encoded = functionCallEncoder.EncodeRequest(
sha3Signature,
parameters,
"0x1234567890abcdef1234567890abcdef12345678", // recipient
1000000000000000000 // amount (1 ETH in wei)
);
// Result: "0xc6888fa10000000000000000000000001234567890abcdef1234567890abcdef12345678000000000000000000000000000000000000000000000000001e4c89d6c7e400"
// From test: FunctionEncodingTests.cs
var functionCallDecoder = new FunctionCallDecoder();
var outputParameters = new[]
{
new ParameterOutput()
{
Parameter = new Parameter("uint[]", "numbers")
{
DecodedType = typeof(List<int>)
}
}
};
var result = functionCallDecoder.DecodeOutput(
"0x0000000000000000000000000000000000000000000000000000000000000020" +
"0000000000000000000000000000000000000000000000000000000000000003" +
"0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000002",
outputParameters
);
var numbers = (List<int>)result[0].Result;
// numbers: [0, 1, 2]
From test: FunctionEncodingTests.cs:125
using Nethereum.ABI.FunctionEncoding;
using Nethereum.ABI.Model;
var functionCallEncoder = new FunctionCallEncoder();
var sha3Signature = "c6888fa1";
var inputsParameters = new[]
{
new Parameter("string", "greeting"),
new Parameter("uint[20]", "numbers"),
new Parameter("string", "farewell")
};
var array = new uint[20];
for (uint i = 0; i < 20; i++)
array[i] = i + 234567;
string encoded = functionCallEncoder.EncodeRequest(
sha3Signature,
inputsParameters,
"hello", // Dynamic string (pointer to data)
array, // Fixed-size array (inline)
"world" // Dynamic string (pointer to data)
);
// Result starts with function selector, followed by:
// - Pointer to "hello" data
// - 20 uint256 values inline
// - Pointer to "world" data
// - Actual "hello" string data
// - Actual "world" string data
From test: FunctionAttributeEncodingTests.cs:55
using Nethereum.ABI.FunctionEncoding;
using Nethereum.ABI.FunctionEncoding.Attributes;
[Function("multiply")]
public class MultiplyFunction : FunctionMessage
{
[Parameter("uint256", "a", 1)]
public int A { get; set; }
}
var input = new MultiplyFunction { A = 69 };
var encoder = new FunctionCallEncoder();
string encoded = encoder.EncodeRequest(input, "c6888fa1");
// Result: "0xc6888fa10000000000000000000000000000000000000000000000000000000000000045"
// 69 decimal = 0x45 hex, padded to 32 bytes
From test: EventTopicDecoderTests.cs:13
using Nethereum.ABI.FunctionEncoding;
using Nethereum.ABI.FunctionEncoding.Attributes;
using System.Numerics;
[Event("Transfer")]
public class TransferEvent
{
[Parameter("address", "_from", 1, indexed: true)]
public string From { get; set; }
[Parameter("address", "_to", 2, indexed: true)]
public string To { get; set; }
[Parameter("uint256", "_value", 3, indexed: true)]
public BigInteger Value { get; set; }
}
var topics = new[]
{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // Event signature
"0x0000000000000000000000000000000000000000000000000000000000000000", // from (zero address)
"0x000000000000000000000000c14934679e71ef4d18b6ae927fe2b953c7fd9b91", // to
"0x0000000000000000000000000000000000000000000000400000402000000001" // value
};
var data = "0x"; // No non-indexed data
var transferEvent = new TransferEvent();
new EventTopicDecoder().DecodeTopics(transferEvent, topics, data);
// transferEvent.From: "0x0000000000000000000000000000000000000000"
// transferEvent.To: "0xc14934679e71ef4d18b6ae927fe2b953c7fd9b91"
// transferEvent.Value: 1180591691223594434561
From test: AbiEncodeTests.cs:12
using Nethereum.ABI;
using Nethereum.Hex.HexConvertors.Extensions;
var abiEncode = new ABIEncode();
// Encode multiple values with explicit types
byte[] encoded = abiEncode.GetABIEncoded(
new ABIValue("string", "hello"),
new ABIValue("int", 69),
new ABIValue("string", "world")
);
string hexResult = encoded.ToHex(true);
// Result: "0x0000000000000000000000000000000000000000000000000000000000000060..."
// Includes pointers to dynamic data and the actual string data
From test: AbiEncodeTests.cs:34
using Nethereum.ABI;
using Nethereum.ABI.FunctionEncoding.Attributes;
public class TestParamsInput
{
[Parameter("string", 1)]
public string First { get; set; }
[Parameter("int256", 2)]
public int Second { get; set; }
[Parameter("string", 3)]
public string Third { get; set; }
}
var abiEncode = new ABIEncode();
var input = new TestParamsInput
{
First = "hello",
Second = 69,
Third = "world"
};
byte[] encoded = abiEncode.GetABIParamsEncoded(input);
// Automatically encodes based on Parameter attributes
From test: FunctionAttributeEncodingTests.cs:14
using Nethereum.ABI.ABIDeserialisation;
using System.Linq;
var abi = @"[
{
""constant"": false,
""inputs"": [{""name"": ""a"", ""type"": ""uint256""}],
""name"": ""multiply"",
""outputs"": [{""name"": ""d"", ""type"": ""uint256""}],
""type"": ""function""
}
]";
var deserializer = new ABIJsonDeserialiser();
var contract = deserializer.DeserialiseContract(abi);
var multiplyFunction = contract.Functions.FirstOrDefault(x => x.Name == "multiply");
// multiplyFunction.Sha3Signature: "c6888fa1"
// multiplyFunction.Constant: false
// multiplyFunction.InputParameters[0].Type: "uint256"
From test: AbiDeserialiseTuplesTests.cs:22
using Nethereum.ABI.ABIDeserialisation;
using System.Linq;
// Complex ABI with nested tuple containing array of tuples
var abi = @"[{
""constant"": false,
""inputs"": [{
""components"": [
{""name"": ""id"", ""type"": ""uint256""},
{
""components"": [
{""name"": ""id"", ""type"": ""uint256""},
{""name"": ""productId"", ""type"": ""uint256""},
{""name"": ""quantity"", ""type"": ""uint256""}
],
""name"": ""lineItem"",
""type"": ""tuple[]""
},
{""name"": ""customerId"", ""type"": ""uint256""}
],
""name"": ""purchaseOrder"",
""type"": ""tuple""
}],
""name"": ""SetPurchaseOrder"",
""outputs"": [],
""type"": ""function""
}]";
var contractAbi = new ABIJsonDeserialiser().DeserialiseContract(abi);
var functionABI = contractAbi.Functions.FirstOrDefault(e => e.Name == "SetPurchaseOrder");
// Function signature includes full tuple structure
// functionABI.Sha3Signature: "0cc400bd"
From test: FunctionEncodingTests.cs:79-107
using Nethereum.ABI.FunctionEncoding;
using Nethereum.ABI.Model;
var encoder = new FunctionCallEncoder();
var signature = "c6888fa1";
// Address encoding
var addressParam = new[] { new Parameter("address", "recipient") };
string encodedAddress = encoder.EncodeRequest(
signature,
addressParam,
"0x1234567890abcdef1234567890abcdef12345678"
);
// Result: "0xc6888fa10000000000000000000000001234567890abcdef1234567890abcdef12345678"
// Boolean encoding
var boolParam = new[] { new Parameter("bool", "flag") };
string encodedBool = encoder.EncodeRequest(signature, boolParam, true);
// Result: "0xc6888fa10000000000000000000000000000000000000000000000000000000000000001"
// Integer encoding
var intParam = new[] { new Parameter("int", "number") };
string encodedInt = encoder.EncodeRequest(signature, intParam, 69);
// Result: "0xc6888fa10000000000000000000000000000000000000000000000000000000000000045"
// Note: 69 decimal = 0x45 hex
From test: Eip712TypedDataSignerSimpleScenarioTest.cs:66
using Nethereum.ABI.EIP712;
using Nethereum.ABI.FunctionEncoding.Attributes;
using Nethereum.Signer.EIP712;
using System.Collections.Generic;
// Define your domain-specific structs
[Struct("Person")]
public class Person
{
[Parameter("string", "name", 1)]
public string Name { get; set; }
[Parameter("address[]", "wallets", 2)]
public List<string> Wallets { get; set; }
}
[Struct("Mail")]
public class Mail
{
[Parameter("tuple", "from", 1, "Person")]
public Person From { get; set; }
[Parameter("tuple[]", "to", 2, "Person[]")]
public List<Person> To { get; set; }
[Parameter("string", "contents", 3)]
public string Contents { get; set; }
}
// Create typed data definition
var typedData = new TypedData<Domain>
{
Domain = new Domain
{
Name = "Ether Mail",
Version = "1",
ChainId = 1,
VerifyingContract = "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
Types = MemberDescriptionFactory.GetTypesMemberDescription(typeof(Domain), typeof(Mail), typeof(Person)),
PrimaryType = nameof(Mail),
};
// Create message
var mail = new Mail
{
From = new Person
{
Name = "Cow",
Wallets = new List<string>
{
"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"
}
},
To = new List<Person>
{
new Person
{
Name = "Bob",
Wallets = new List<string>
{
"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57",
"0xB0B0b0b0b0b0B000000000000000000000000000"
}
}
},
Contents = "Hello, Bob!"
};
// Set the message
typedData.SetMessage(mail);
// Sign using private key
var signer = new Eip712TypedDataSigner();
var key = new EthECKey("94e001d6adf3a3275d5dd45971c2a5f6637d3e9c51f9693f2e678f649e164fa5");
string signature = signer.SignTypedDataV4(mail, typedData, key);
// signature: "0x943393c998ab7e067d2875385e2218c9b3140f563694267ac9f6276a9fcc53e1..."
// Recover signer address from signature
string recoveredAddress = signer.RecoverFromSignatureV4(mail, typedData, signature);
// recoveredAddress matches key.GetPublicAddress()
From test: Eip712TypedDataSignerTest.cs:107
using Nethereum.ABI.EIP712;
using Nethereum.Signer.EIP712;
// EIP-712 typed data as JSON (MetaMask format)
var typedDataJson = @"{
'domain': {
'chainId': 1,
'name': 'Ether Mail',
'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
'version': '1'
},
'message': {
'contents': 'Hello, Bob!',
'from': {
'name': 'Cow',
'wallets': [
'0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
'0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF'
]
},
'to': [{
'name': 'Bob',
'wallets': [
'0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
'0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57',
'0xB0B0b0b0b0b0B000000000000000000000000000'
]
}]
},
'primaryType': 'Mail',
'types': {
'EIP712Domain': [
{'name': 'name', 'type': 'string'},
{'name': 'version', 'type': 'string'},
{'name': 'chainId', 'type': 'uint256'},
{'name': 'verifyingContract', 'type': 'address'}
],
'Mail': [
{'name': 'from', 'type': 'Person'},
{'name': 'to', 'type': 'Person[]'},
{'name': 'contents', 'type': 'string'}
],
'Person': [
{'name': 'name', 'type': 'string'},
{'name': 'wallets', 'type': 'address[]'}
]
}
}";
// Deserialize and encode
var rawTypedData = TypedDataRawJsonConversion.DeserialiseJsonToRawTypedData(typedDataJson);
var signer = new Eip712TypedDataSigner();
byte[] encodedTypedData = signer.EncodeTypedDataRaw(rawTypedData);
// Sign directly from JSON
var key = new EthECKey("94e001d6adf3a3275d5dd45971c2a5f6637d3e9c51f9693f2e678f649e164fa5");
string signature = signer.SignTypedDataV4(rawTypedData, key);
From test: EIP712TypeDataSignatureMultipleComplexInnerObjects.cs:13
using Nethereum.ABI.EIP712;
using Nethereum.ABI.FunctionEncoding.Attributes;
using System.Collections.Generic;
using System.Numerics;
[Struct("UsageLimit")]
public class UsageLimit
{
[Parameter("uint8", "limitType", 1)]
public byte LimitType { get; set; }
[Parameter("uint256", "limit", 2)]
public BigInteger Limit { get; set; }
[Parameter("uint256", "period", 3)]
public BigInteger Period { get; set; }
}
[Struct("Constraint")]
public class Constraint
{
[Parameter("uint8", "condition", 1)]
public byte Condition { get; set; }
[Parameter("uint64", "index", 2)]
public ulong Index { get; set; }
[Parameter("bytes32", "refValue", 3)]
public byte[] RefValue { get; set; }
}
[Struct("CallSpec")]
public class CallSpec
{
[Parameter("address", "target", 1)]
public string Target { get; set; }
[Parameter("bytes4", "selector", 2)]
public byte[] Selector { get; set; }
[Parameter("uint256", "maxValuePerUse", 3)]
public BigInteger MaxValuePerUse { get; set; }
[Parameter("tuple", "valueLimit", 4, structTypeName: "UsageLimit")]
public UsageLimit ValueLimit { get; set; }
[Parameter("tuple[]", "constraints", 5, structTypeName: "Constraint[]")]
public List<Constraint> Constraints { get; set; }
}
[Struct("SessionSpec")]
public class SessionSpec
{
[Parameter("address", "signer", 1)]
public string Signer { get; set; }
[Parameter("uint256", "expiresAt", 2)]
public BigInteger ExpiresAt { get; set; }
[Parameter("tuple[]", "callPolicies", 3, structTypeName: "CallSpec[]")]
public List<CallSpec> CallPolicies { get; set; }
}
// This demonstrates deep nesting: SessionSpec contains array of CallSpec,
// each CallSpec contains UsageLimit struct and array of Constraint structs
// Perfect for complex DeFi protocols, account abstraction, session keys, etc.
From test: Eip712TypedDataEncoder.cs
using Nethereum.ABI.EIP712;
using Nethereum.Hex.HexConvertors.Extensions;
var encoder = new Eip712TypedDataEncoder();
// Encode from typed data
var mail = new Mail { /* ... */ };
var typedData = new TypedData<Domain> { /* ... */ };
byte[] encoded = encoder.EncodeTypedData(mail, typedData);
// Encode and hash in one operation (for signing)
byte[] hash = encoder.EncodeAndHashTypedData(mail, typedData);
// Encode directly from JSON
string json = /* EIP-712 JSON */;
byte[] encodedFromJson = encoder.EncodeTypedData(json);
byte[] hashFromJson = encoder.EncodeAndHashTypedData(json);
// The hash is what gets signed with ECDSA
string hashHex = hash.ToHex(true);
From test: FunctionEncodingTests.cs:161
using Nethereum.ABI.FunctionEncoding;
using Nethereum.ABI.Model;
var encoder = new FunctionCallEncoder();
var signature = "c6888fa1";
var parameters = new[] { new Parameter("address", "_address1") };
try
{
string encoded = encoder.EncodeRequest(signature, parameters, (object)null);
}
catch (AbiEncodingException ex)
{
// ex.Message: "An error occurred encoding abi value. Order: '1', Type: 'address',
// Value: 'null'. Ensure the value is valid for the abi type."
Console.WriteLine(ex.Message);
}
ABIEncodeGetABIEncoded(params ABIValue[] abiValues) - Encode values with explicit typesGetABIEncoded(params object[] values) - Encode values with automatic type detectionGetABIParamsEncoded<T>(T input) - Encode object using Parameter attributesGetABIEncodedPacked(params ABIValue[] abiValues) - Packed encoding (no padding)GetSha3ABIEncoded(...) - Encode and hash in one operationFunctionCallEncoderEncodeRequest(string sha3Signature, Parameter[] parameters, params object[] values) - Encode function callEncodeRequest<T>(T functionInput, string sha3Signature) - Encode using attributesFunctionCallDecoderDecodeOutput(string output, params Parameter[] parameters) - Decode function return valuesDecodeFunctionOutput<T>(string output) - Decode using attributesEventTopicDecoderDecodeTopics(object destination, string[] topics, string data) - Decode event log into objectDecodeTopics<T>(string[] topics, string data) - Decode event log to typed objectABIJsonDeserialiserDeserialiseContract(string abi) - Parse contract ABI JSONDeserialiseContract(JArray abi) - Parse from JArrayContractABI with Functions, Events, Errors, ConstructorEip712TypedDataEncoderEncodeTypedData<T, TDomain>(T message, TypedData<TDomain> typedData) - Encode typed data with messageEncodeTypedData(string json) - Encode from EIP-712 JSONEncodeAndHashTypedData(...) - Encode and hash for signingEncodeTypedDataRaw(TypedDataRaw typedData) - Low-level encodingEip712TypedDataSigner (in Nethereum.Signer)SignTypedDataV4<T>(T message, TypedData<Domain> typedData, EthECKey key) - Sign EIP-712 dataRecoverFromSignatureV4<T>(T message, TypedData<Domain> typedData, string signature) - Recover signerSignTypedDataV4(TypedDataRaw typedData, EthECKey key) - Sign from raw typed data[Function("name")] - Mark class as function definition[Event("name")] - Mark class as event definition[Struct("name")] - Mark class as EIP-712 struct[Parameter("type", "name", order, indexed)] - Mark property as parameter[FunctionOutput] - Mark class can be used for output decodingTypedData<TDomain> - Typed data with domain separationDomain - EIP-712 domain (name, version, chainId, verifyingContract, salt)MemberDescription - Type member definition (name, type)MemberDescriptionFactory - Generate type descriptions from .NET typesMemberValue - Runtime value for encodingTypedDataRaw - Raw typed data without genericsFunction signatures are the first 4 bytes of Keccak-256 hash of the canonical function signature:
Keccak256("transfer(address,uint256)") → 0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b
Function selector: 0xa9059cbb (first 4 bytes)
Event signatures are the full 32 bytes of Keccak-256 hash of the canonical event signature:
Keccak256("Transfer(address,address,uint256)") → 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
Event topic[0]: Full hash (identifies the event)
The domain separator prevents signature replay attacks across:
verifyingContract address)chainId)version)name)Formula: Keccak256(encodeData("EIP712Domain", domain))
Final hash signed by user:
Keccak256("\x19\x01" + domainSeparator + hashStruct(message))
Where:
\x19\x01 is the version byte for structured datadomainSeparator is the hash of the domainhashStruct(message) is the hash of the primary message typeWhen calculating signatures, types must be canonical:
uint → uint256int → int256transfer(address,uint256) not transfer(address, uint256)Runnable examples available at Nethereum Playground:
Human-Readable ABI:
ABI Encoding: