Skip to content

Commit aadf53a

Browse files
core foundation guides update
1 parent 87ebca5 commit aadf53a

22 files changed

Lines changed: 1493 additions & 813 deletions

docs/core-foundation/guide-abi-encoding.md

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: ABI Encoding and Decoding
33
sidebar_label: "ABI Encoding"
4-
sidebar_position: 3
4+
sidebar_position: 15
55
description: Encode and decode Ethereum ABI data — the Solidity abi.encode, abi.encodePacked, and keccak256 equivalents in C#
66
---
77

@@ -19,7 +19,7 @@ You need direct ABI encoding when you want to:
1919
- **Compute CREATE2 addresses** — which require `keccak256(abi.encodePacked(...))`
2020
- **Verify or build EIP-712 typed data signatures** — for gasless approvals (ERC-2612 Permit), off-chain orders, etc.
2121
- **Decode raw transaction data or logs** — parse calldata or event topics manually
22-
- **Build function selectors**compute the 4-byte selector from a function signature
22+
- **Build function selectors** — the first 4 bytes of the Keccak-256 hash of the function signature, which tells the EVM which function to call
2323

2424
## Install
2525

@@ -43,7 +43,6 @@ dotnet add package Nethereum.Signer.EIP712
4343

4444
Standard ABI encoding pads every value to 32 bytes. This is what Solidity's `abi.encode(...)` produces.
4545

46-
<!-- tag:AbiEncodeTests:ShouldEncodeMultipleTypesIncludingDynamicString -->
4746
```csharp
4847
using Nethereum.ABI;
4948

@@ -60,17 +59,15 @@ Each value is padded to 32 bytes with proper offset handling for dynamic types (
6059

6160
You can also let Nethereum infer the types:
6261

63-
<!-- tag:AbiEncodeTests:ShouldEncodeMultipleValuesUsingDefaultConvertors -->
6462
```csharp
6563
// Auto-detect types from .NET values
6664
byte[] encoded = abiEncode.GetABIEncoded("1", "2", "3");
6765
```
6866

6967
### abi.encodePacked() — Packed Encoding
7068

71-
Packed encoding concatenates values without padding — shorter output, used for hashing.
69+
Packed encoding concatenates values without padding — it's not self-describing (you must know the types to decode it). Used in Solidity's `abi.encodePacked()` for hashing and CREATE2 address computation.
7270

73-
<!-- tag:ABIPackingTests:ShouldEncodeSha3UsingTypes (packed portion) -->
7471
```csharp
7572
byte[] packed = abiEncode.GetABIEncodedPacked(
7673
new ABIValue("string", "Hello!%"),
@@ -98,8 +95,6 @@ The `GetSha3ABIEncoded` family hashes standard-encoded data; the `GetSha3ABIEnco
9895

9996
If you have typed DTOs (from code generation or hand-written), encode them directly:
10097

101-
<!-- tag:AbiEncodeTests:ShouldEncodeParams -->
102-
<!-- tag:ABIPackingTests:ShouldEncodeParams -->
10398
```csharp
10499
[FunctionOutput]
105100
public class MyParams
@@ -121,7 +116,6 @@ byte[] hash = abiEncode.GetSha3ABIParamsEncodedPacked(input);
121116

122117
Decode ABI-encoded bytes back to typed values:
123118

124-
<!-- tag:AbiEncodingDocExampleTests:ShouldDecodeEncodedValues -->
125119
```csharp
126120
string address = abiEncode.DecodeEncodedAddress(encodedBytes);
127121
BigInteger value = abiEncode.DecodeEncodedBigInteger(encodedBytes);
@@ -138,7 +132,6 @@ MyParams result = abiEncode.DecodeEncodedComplexType<MyParams>(encodedBytes);
138132

139133
A function selector is the first 4 bytes of the Keccak-256 hash of the canonical function signature. It identifies which function to call in a contract.
140134

141-
<!-- tag:AbiEncodingDocExampleTests:ShouldCalculateFunctionSelector -->
142135
```csharp
143136
var keccak = Sha3Keccack.Current;
144137
var transferSignature = "transfer(address,uint256)";
@@ -152,7 +145,6 @@ The selector `a9059cbb` is what appears at the start of every ERC-20 `transfer`
152145

153146
For lower-level control, `FunctionCallEncoder` builds complete calldata (selector + encoded parameters):
154147

155-
<!-- tag:AbiEncodingDocExampleTests:ShouldEncodeBasicFunctionCall -->
156148
```csharp
157149
var functionCallEncoder = new FunctionCallEncoder();
158150
var sha3Signature = "a9059cbb";
@@ -168,7 +160,6 @@ var result = functionCallEncoder.EncodeRequest(sha3Signature, inputsParameters,
168160

169161
Or use a typed DTO, which is cleaner and catches errors at compile time:
170162

171-
<!-- tag:AbiEncodingDocExampleTests:ShouldEncodeUsingParameterAttributes -->
172163
```csharp
173164
[Function("transfer")]
174165
public class TransferFunction
@@ -196,7 +187,6 @@ In practice, you rarely call `FunctionCallEncoder` directly — `web3.Eth.GetCon
196187

197188
When you call a view/pure function and get raw hex back:
198189

199-
<!-- tag:AbiEncodingDocExampleTests:ShouldDecodeFunctionOutput -->
200190
```csharp
201191
var functionCallDecoder = new FunctionCallDecoder();
202192

@@ -219,7 +209,6 @@ var result = functionCallDecoder.DecodeOutput(encodedOutput, outputParameters);
219209

220210
Events use indexed parameters as log topics. The first topic is always the Keccak-256 hash of the event signature:
221211

222-
<!-- tag:AbiEncodingDocExampleTests:ShouldDecodeTransferEventTopic -->
223212
```csharp
224213
[Event("Transfer")]
225214
public class TransferEventDTO
@@ -250,7 +239,6 @@ The `true` in the `[Parameter]` attribute marks indexed parameters, which appear
250239

251240
Solidity custom errors (since 0.8.4) are encoded identically to function calls — a 4-byte selector followed by ABI-encoded parameters:
252241

253-
<!-- tag:AbiEncodingDocExampleTests:ShouldDecodeCustomError -->
254242
```csharp
255243
var error = new ErrorABI("InsufficientBalance");
256244
error.InputParameters = new[]
@@ -276,7 +264,6 @@ For a higher-level approach to error handling with automatic revert reason detec
276264

277265
Parse a JSON ABI string into a strongly-typed contract model for inspection or dynamic interaction:
278266

279-
<!-- tag:AbiEncodingDocExampleTests:ShouldDeserializeContractAbi -->
280267
```csharp
281268
var abi = @"[{""constant"":false,""inputs"":[{""name"":""a"",""type"":""uint256""}],
282269
""name"":""multiply"",""outputs"":[{""name"":""d"",""type"":""uint256""}],""type"":""function""},
@@ -299,7 +286,6 @@ The `Eip712TypedDataEncoder` handles encoding, hashing, and domain separator com
299286

300287
### Signing a Simple Typed Message
301288

302-
<!-- tag:Eip712DocExampleTests:ShouldSignAndRecoverSimpleTypedMessage -->
303289
```csharp
304290
using Nethereum.ABI.EIP712;
305291
using Nethereum.Signer;
@@ -334,8 +320,6 @@ var encoded = encoder.EncodeAndHashTypedData(message,
334320

335321
This is the most common real-world use of EIP-712 — letting a user approve a token spend without paying gas:
336322

337-
<!-- tag:Eip712DocExampleTests:ShouldSignErc2612Permit -->
338-
<!-- tag:Eip712DocExampleTests:ShouldSignWithAutoGeneratedSchema -->
339323
```csharp
340324
[Struct("Permit")]
341325
public class Permit
@@ -383,7 +367,6 @@ var signature = key.SignAndCalculateV(hash);
383367

384368
When you receive EIP-712 typed data as JSON (e.g., from a frontend or wallet):
385369

386-
<!-- tag:Eip712DocExampleTests:ShouldSignAndRecoverFromJson -->
387370
```csharp
388371
var json = @"{
389372
""types"": {
@@ -416,6 +399,7 @@ For full EIP-712 signing workflows including verification and recovery, see the
416399
## Next Steps
417400

418401
- [Smart Contract Interaction](../smart-contracts/guide-smart-contract-interaction) — use ABI encoding through typed contract handlers (recommended for most use cases)
402+
- [Decode Transactions](guide-decode-transactions) — inspect and decode raw transaction calldata
419403
- [EIP-712 Signing](../signing-and-key-management/guide-eip712-signing) — full typed data signing workflows
420404
- [Events & Logs](../smart-contracts/guide-events) — decode event data from transaction receipts
421405
- [Error Handling](../smart-contracts/guide-error-handling) — decode revert reasons and custom errors

docs/core-foundation/guide-address-utils.md

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
---
22
title: Validate and Format Addresses
33
sidebar_label: "Address Utilities"
4-
sidebar_position: 8
4+
sidebar_position: 17
55
description: Validate, checksum, compare, and format Ethereum addresses
66
---
77

88
# Validate and Format Addresses
99

10-
Validate Ethereum addresses, apply EIP-55 checksums, compare addresses case-insensitively, and handle empty/zero addresses.
10+
Every Ethereum interaction starts with an address. When your application accepts an address from user input, reads one from a contract event, or compares addresses from different sources, you need to answer three questions: is it valid, is it the same address, and is it safely formatted?
11+
12+
Ethereum addresses are 20-byte hex strings, but they arrive in many forms: lowercase from RPC calls, uppercase from older wallets, mixed-case with an EIP-55 checksum, or truncated in sloppy data. `AddressUtil` in Nethereum handles all of these cases so you don't have to write your own comparison and validation logic.
1113

1214
## Installation
1315

@@ -17,64 +19,91 @@ dotnet add package Nethereum.Util
1719

1820
## EIP-55 Checksum
1921

20-
<!-- tag:UtilDocExampleTests:ShouldCreateChecksumAddresses -->
22+
EIP-55 is a mixed-case encoding that catches typos. The capitalisation of each hex letter is determined by hashing the address, so if you change any letter's case, the checksum fails. Always checksum addresses before displaying them to users or storing them.
23+
2124
```csharp
2225
var addressUtil = AddressUtil.Current;
23-
var checksummed = addressUtil.ConvertToChecksumAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed");
26+
27+
var rawAddress = "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed";
28+
var checksummed = addressUtil.ConvertToChecksumAddress(rawAddress);
29+
Console.WriteLine(checksummed);
2430
// "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
25-
Assert.True(addressUtil.IsChecksumAddress("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"));
31+
32+
bool isValid = addressUtil.IsChecksumAddress(checksummed);
33+
Console.WriteLine($"Checksum valid: {isValid}"); // true
2634
```
2735

2836
## Validate Address Format
2937

30-
<!-- tag:UtilDocExampleTests:ShouldValidateAndCompareAddresses -->
38+
Before processing any address from user input or external data, check that it is well-formed. This catches obvious mistakes early, before they cause a failed transaction or a confusing error deeper in your code.
39+
3140
```csharp
32-
Assert.True(addressUtil.IsValidEthereumAddressHexFormat("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"));
33-
Assert.False(addressUtil.IsValidEthereumAddressHexFormat("not-an-address"));
34-
Assert.True(addressUtil.IsValidAddressLength("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"));
41+
var userInput = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed";
42+
bool isValidFormat = addressUtil.IsValidEthereumAddressHexFormat(userInput);
43+
Console.WriteLine($"Valid address: {isValidFormat}"); // true
44+
45+
bool isGarbage = addressUtil.IsValidEthereumAddressHexFormat("not-an-address");
46+
Console.WriteLine($"Garbage input valid: {isGarbage}"); // false
47+
48+
bool correctLength = addressUtil.IsValidAddressLength(userInput);
49+
Console.WriteLine($"Correct length: {correctLength}"); // true
3550
```
3651

3752
## Compare Addresses
3853

39-
<!-- tag:UtilDocExampleTests:ShouldValidateAndCompareAddresses -->
54+
Addresses from different sources often have different casing. An RPC node returns lowercase, a block explorer returns checksummed, and a user might paste uppercase. Direct string comparison would treat these as different addresses. Use case-insensitive comparison instead.
55+
4056
```csharp
41-
Assert.True(addressUtil.AreAddressesTheSame(
42-
"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
43-
"0x5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED"));
44-
Assert.True("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
45-
.IsTheSameAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"));
57+
var fromContract = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed";
58+
var fromRpc = "0x5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED";
59+
60+
bool same = addressUtil.AreAddressesTheSame(fromContract, fromRpc);
61+
Console.WriteLine($"Same address: {same}"); // true
62+
63+
bool alsoSame = fromContract.IsTheSameAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed");
64+
Console.WriteLine($"Extension method match: {alsoSame}"); // true
4665
```
4766

4867
## Empty and Zero Addresses
4968

50-
<!-- tag:UtilDocExampleTests:ShouldHandleEmptyAddresses -->
69+
Smart contracts use the zero address (`0x0000...0000`) as a sentinel value, for example to indicate "no owner" or a burn destination. Null, empty strings, and short zero forms like `"0x0"` all represent the same concept. Detecting these prevents sending funds to an unrecoverable address.
70+
5171
```csharp
52-
Assert.True(addressUtil.IsAnEmptyAddress(null));
53-
Assert.True(addressUtil.IsAnEmptyAddress("0x0"));
54-
Assert.Equal("0x0000000000000000000000000000000000000000", AddressUtil.ZERO_ADDRESS);
72+
bool isNullEmpty = addressUtil.IsAnEmptyAddress(null);
73+
Console.WriteLine($"Null is empty: {isNullEmpty}"); // true
74+
75+
bool isShortZero = addressUtil.IsAnEmptyAddress("0x0");
76+
Console.WriteLine($"0x0 is empty: {isShortZero}"); // true
77+
78+
Console.WriteLine($"Zero address constant: {AddressUtil.ZERO_ADDRESS}");
79+
// "0x0000000000000000000000000000000000000000"
5580
```
5681

5782
## UniqueAddressList
5883

59-
<!-- tag:UtilDocExampleTests:ShouldDeduplicateAddressesInUniqueList -->
84+
When collecting addresses from events or transaction logs, duplicates with different casing are common. `UniqueAddressList` deduplicates automatically using case-insensitive comparison.
85+
6086
```csharp
6187
var uniqueList = new UniqueAddressList();
6288
uniqueList.Add("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed");
6389
uniqueList.Add("0x5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED");
64-
Assert.Single(uniqueList);
90+
Console.WriteLine($"Count after adding same address twice: {uniqueList.Count}"); // 1
6591
```
6692

6793
## Pad Short Addresses
6894

69-
<!-- tag:UtilDocExampleTests:ShouldPadAndConvertAddresses -->
95+
Some data sources return truncated addresses. Padding restores them to the standard 20-byte format so they work with contracts and RPC calls.
96+
7097
```csharp
7198
var padded = addressUtil.ConvertToValid20ByteAddress("0x1234");
99+
Console.WriteLine(padded);
72100
// "0x0000000000000000000000000000000000001234"
73101
```
74102

75103
## Next Steps
76104

77-
- [Keys and Accounts](guide-keys-accounts) — generate keys and derive addresses
105+
- [Keys and Accounts](guide-keys-accounts) -- generate keys and derive addresses
106+
- [Hex Encoding](guide-hex-encoding) -- hex conversion and byte-array handling
78107

79108
## Further Reading
80109

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
title: Decode Function Calls from Transaction Input
3+
sidebar_label: "Decode Transactions"
4+
sidebar_position: 12
5+
description: Validate and decode smart contract function calls from transaction input data
6+
---
7+
8+
# Decode Function Calls from Transaction Input
9+
10+
When you retrieve a transaction from the blockchain (as in [Query Blocks](guide-query-blocks)), its `Input` field contains the raw ABI-encoded function call. This guide shows how to check if a transaction calls a specific function and decode its parameters into typed C# objects.
11+
12+
```bash
13+
dotnet add package Nethereum.Web3
14+
dotnet add package Nethereum.Contracts
15+
```
16+
17+
## Define the Function Message
18+
19+
The first 4 bytes of a transaction's input data identify which function is being called. This selector is the Keccak-256 hash of the function signature (e.g., `transfer(address,uint256)` produces `a9059cbb`). The remaining bytes are the ABI-encoded parameters.
20+
21+
To decode that data, define a C# class that mirrors the Solidity function signature. The `[Function]` and `[Parameter]` attributes tell Nethereum how to map the ABI-encoded bytes back to typed properties:
22+
23+
```csharp
24+
using Nethereum.ABI.FunctionEncoding.Attributes;
25+
using Nethereum.Contracts;
26+
using System.Numerics;
27+
28+
[Function("transfer", "bool")]
29+
public class TransferFunction : FunctionMessage
30+
{
31+
[Parameter("address", "_to", 1)]
32+
public string To { get; set; }
33+
34+
[Parameter("uint256", "_value", 2)]
35+
public BigInteger TokenAmount { get; set; }
36+
}
37+
```
38+
39+
## Check and Decode the Transaction
40+
41+
`IsTransactionForFunctionMessage<T>()` compares the first 4 bytes of the transaction input against the function selector derived from `TransferFunction`. If the selector matches, `DecodeTransaction` ABI-decodes the remaining bytes into the typed message. If the selector does not match, the transaction is for a different function and is skipped.
42+
43+
```csharp
44+
using Nethereum.Web3;
45+
using Nethereum.Contracts.Extensions;
46+
47+
var web3 = new Web3("https://mainnet.infura.io/v3/YOUR-PROJECT-ID");
48+
49+
var txn = await web3.Eth.Transactions.GetTransactionByHash
50+
.SendRequestAsync(
51+
"0x0404a0517a7443db1787b5461b9d5fc18546809419c0cc6a736599b60677ed71");
52+
53+
if (txn.IsTransactionForFunctionMessage<TransferFunction>())
54+
{
55+
var transfer = new TransferFunction().DecodeTransaction(txn);
56+
Console.WriteLine("To: " + transfer.To);
57+
Console.WriteLine("Amount: " + Web3.Convert.FromWei(transfer.TokenAmount));
58+
}
59+
```
60+
61+
`Web3.Convert.FromWei` converts a raw `BigInteger` value in wei to its ether-unit decimal representation. See [Unit Conversion](guide-unit-conversion) for details.
62+
63+
## Next Steps
64+
65+
- [ABI Encoding](guide-abi-encoding) -- encode and decode smart contract data in depth
66+
- [Query Blocks](guide-query-blocks) -- get block and transaction data
67+
- [Transaction Recovery](guide-transaction-recovery) -- recover the sender from a signed transaction

0 commit comments

Comments
 (0)