> For the complete documentation index, see [llms.txt](https://docs.armchain.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.armchain.org/pqc/transaction-types.md).

# Transaction Types

Armchain introduces **Type 3** transactions for post-quantum cryptography. If you're using the Armchain Ethers SDK, this is handled automatically. The SDK creates and signs Type 3 transactions for you. This page is for developers who want to understand the format details or build custom tooling.

## Transaction Type Summary

| Type  | Name               | EIP          | Signature    | Supported |
| ----- | ------------------ | ------------ | ------------ | --------- |
| 0     | Legacy             | Pre-EIP-2718 | ECDSA        | **No**    |
| 1     | Access List        | EIP-2930     | ECDSA        | **No**    |
| 2     | EIP-1559           | EIP-1559     | ECDSA        | **No**    |
| **3** | **PQC (Armchain)** | -            | **ML-DSA44** | **Yes**   |

> **Armchain only supports Type 3 transactions.** ECDSA-signed transactions (Types 0, 1, 2) are rejected by the network.

## Type 3 Transaction Format

### Fields

| Field                  | Type      | Description                            |
| ---------------------- | --------- | -------------------------------------- |
| `type`                 | `uint8`   | Always `0x03`                          |
| `chainId`              | `uint64`  | Chain ID (55 devnet)                   |
| `nonce`                | `uint64`  | Sender account nonce                   |
| `maxPriorityFeePerGas` | `uint256` | Tip for the validator (EIP-1559)       |
| `maxFeePerGas`         | `uint256` | Maximum total gas price (EIP-1559)     |
| `gasLimit`             | `uint64`  | Maximum gas for execution              |
| `to`                   | `address` | Recipient (null for contract creation) |
| `value`                | `uint256` | ARM amount in wei                      |
| `data`                 | `bytes`   | Calldata or constructor bytecode       |
| `accessList`           | `[]tuple` | Storage access list (EIP-2930)         |
| `signatureBlob`        | `bytes`   | ML-DSA44 signature + public key        |

### Signature Blob

The `signatureBlob` field contains the **concatenation** of the ML-DSA44 signature and the public key:

```
signatureBlob (3,732 bytes)
```

ML-DSA44 does not support public key recovery, so both the signature and the public key are included in the blob.

### RLP Encoding

Type 3 transactions use **typed transaction envelopes** (EIP-2718):

```
Serialized = 0x03 || RLP([
    chainId,
    nonce,
    maxPriorityFeePerGas,
    maxFeePerGas,
    gasLimit,
    to,
    value,
    data,
    accessList,
    signatureBlob
])
```

The **signing payload** (hash that is signed) is:

```
sigHash = Keccak256(0x03 || RLP([
    chainId,
    nonce,
    maxPriorityFeePerGas,
    maxFeePerGas,
    gasLimit,
    to,
    value,
    data,
    accessList
]))
```

The signature is computed over the `sigHash`, then the `signatureBlob` (signature + public key) is appended to the transaction fields.

## Creating Type 3 Transactions

### Using the Ethers SDK

```javascript
import { ethers } from "@armchain-ethersv6/ethers";

const provider = new ethers.JsonRpcProvider("https://www.armchain.org/devnet");
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

// Simple transfer
const tx = await wallet.sendTransaction({
  to: "0xRecipientAddress",
  value: ethers.parseEther("1.0"),
  type: 3, // PQC transaction type
});

console.log("TX hash:", tx.hash);
const receipt = await tx.wait();
console.log("Confirmed in block:", receipt.blockNumber);
```

### Manual Transaction Construction

```javascript
import { ethers } from "@armchain-ethersv6/ethers";

const provider = new ethers.JsonRpcProvider("https://www.armchain.org/devnet");
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

// Build a transaction manually
const txRequest = {
  type: 3,
  chainId: 55,
  to: "0xRecipientAddress",
  value: ethers.parseEther("1.0"),
  nonce: await provider.getTransactionCount(wallet.address),
  maxFeePerGas: ethers.parseUnits("100", "gwei"),
  maxPriorityFeePerGas: ethers.parseUnits("1", "gwei"),
  gasLimit: 21000n,
};

// Sign (produces the signatureBlob internally)
const signedTx = await wallet.signTransaction(txRequest);

// Broadcast
const response = await provider.broadcastTransaction(signedTx);
console.log("TX hash:", response.hash);
```

### Contract Deployment

```javascript
const factory = new ethers.ContractFactory(abi, bytecode, wallet);
const contract = await factory.deploy(/* constructor args */, {
  type: 3,
});
await contract.waitForDeployment();
console.log("Deployed at:", await contract.getAddress());
```

### Contract Interaction

```javascript
const contract = new ethers.Contract(contractAddress, abi, wallet);

// Read (no transaction needed)
const value = await contract.someViewFunction();

// Write (creates a Type 3 transaction)
const tx = await contract.someFunction(arg1, arg2, {
  type: 3,
});
await tx.wait();
```

## JSON-RPC Representation

When queried via JSON-RPC (`eth_getTransactionByHash`), Type 3 transactions include:

```json
{
  "type": "0x3",
  "chainId": "0x37",
  "nonce": "0x0",
  "maxPriorityFeePerGas": "0x3b9aca00",
  "maxFeePerGas": "0x174876e800",
  "gas": "0x5208",
  "to": "0x...",
  "value": "0xde0b6b3a7640000",
  "input": "0x",
  "accessList": [],
  "signatureBlob": "0x...",
  "hash": "0x...",
  "blockHash": "0x...",
  "blockNumber": "0x1",
  "transactionIndex": "0x0"
}
```

> **Note**: Type 3 transactions do NOT have `v`, `r`, `s` fields. They have a single `signatureBlob` field instead.

## Size Comparison

| Component                   | Type 2 (ECDSA)                     | Type 3 (ML-DSA44)                   |
| --------------------------- | ---------------------------------- | ----------------------------------- |
| Transaction fields          | \~45 bytes                         | \~45 bytes                          |
| Signature                   | 65 bytes (v, r, s)                 | 2,420 bytes (raw ML-DSA44)          |
| Public key                  | 0 bytes (recovered from signature) | 1,312 bytes (appended to signature) |
| **Signature blob total**    | **65 bytes**                       | **3,732 bytes**                     |
| **Total (simple transfer)** | **\~110 bytes**                    | **\~3,930 bytes**                   |

The \~36x increase in transaction size is the cost of quantum resistance. Impact:

* **Bandwidth**: More data to propagate between nodes
* **Storage**: Larger blocks on disk
* **Intrinsic gas**: Higher data-related gas costs

However, it does NOT impact:

* **Execution gas**: EVM computation costs are identical
* **Finality time**: Still instant (Lachesis aBFT)
* **Throughput**: Block gas limits accommodate the larger transactions

## Validation Rules

When a Type 3 transaction arrives at a node, the following checks are performed:

1. **Type check**: Transaction type must be `0x03`
2. **Chain ID check**: Must match the node's chain ID
3. **Signature blob size**: Must be exactly 3,732 bytes (2,420 + 1,312)
4. **ML-DSA44 verification**: Verify the signature over the signing hash using the public key from the signature blob
5. **Address derivation**: The sender address (`from`) is derived from the public key: `Keccak256(pubkey)[12:]`
6. **Nonce check**: Must match the sender's current nonce
7. **Balance check**: Sender must have sufficient ARM for `value + gas * maxFeePerGas`
8. **Gas limit check**: Must be at least the intrinsic gas

If any check fails, the transaction is rejected and not propagated.

## Further Reading

* [ML-DSA44 Deep Dive](/pqc/mldsa44.md): Algorithm details
* [PQC Overview](/pqc/overview.md): Why post-quantum cryptography
* [Ethers SDK](/developers/overview-1.md): Working with transactions in code
