> For the complete documentation index, see [llms.txt](https://spire-docs.gitbook.io/spire/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://spire-docs.gitbook.io/spire/da-builder/getting-started/detailed-walkthrough.md).

# Detailed Walkthrough

> **Note:** This walkthrough provides a step-by-step guide for integrating with DA Builder. For a complete working example, see our [sample integration repository](https://github.com/spire-labs/da-builder-sample-integration).

For complete configuration details including endpoints and contract addresses, see the [Quick Reference](/spire/da-builder/quick-reference.md).

### 0. Whitelist Your Address

Before DA Builder will accept txs from your EOA it must be whitelisted. Reach out [here](https://www.spire.dev/contact) to get it added first. Once you receive a response indicating it has been added you can proceed with the next steps.

### 1. Implement a Trustless Proposer

DA Builder only allows transactions to be submitted by an EOA with valid EIP-7702 account code set. The EIP-7702 account code must implement the `IProposer` interface.

```solidity
interface IProposer {
    function onCall(address _target, bytes calldata _data, uint256 _value) external returns (bool);
}
```

This enables DA Builder to aggregate transactions from multiple users and execute them as a single transaction on their behalf.

We recommend using a trustless proposer contract similar to the following example. A trustless proposer can use EIP-712 signatures as a verification process to ensure that the only person able to generate a transaction the proposer will execute is the EOA owner. DA Builder is then able to batch the transaction with those of other users to reduce costs.

```solidity
contract TrustlessProposer {
    bytes32 public constant CALL_TYPEHASH =
        keccak256("Call(uint256 deadline,uint256 nonce,address target,uint256 value,bytes calldata)");

    address public immutable PROPOSER_MULTICALL;
    uint256 public nestedNonce;

    function onCall(address _target, bytes calldata _data, uint256 _value) external returns (bool) {
        // The estimated gas used is not perfect but provides a meaningful bound to know if we went over the gas limit
        uint256 _startGas = gasleft();
        
        if (msg.sender != PROPOSER_MULTICALL && address(this) != msg.sender) revert Unauthorized();

        // Decode the data parameter which contains: (signature, deadline, nonce, actual_calldata, gasLimit)
        (bytes memory _sig, uint256 _deadline, uint256 _nonce, bytes memory _calldata, uint256 _gasLimit) =
            abi.decode(_data, (bytes, uint256, uint256, bytes, uint256));

        if (block.timestamp > _deadline) revert DeadlinePassed();
        if (_nonce != nestedNonce) revert NonceTooLow();

        // Create the EIP-712 message hash
        bytes32 _structHash = keccak256(abi.encode(CALL_TYPEHASH, _deadline, _nonce, _target, _value, keccak256(_calldata), _gasLimit));
        bytes32 _messageHash = _hashTypedDataV4(_structHash);
        
        address _signer = ecrecover(_messageHash, v, r, s);
        if (_signer != address(this)) revert SignatureInvalid();

        (bool _success,) = _target.call{value: _value}(_calldata);
        if (!_success) revert LowLevelCallFailed();

        nestedNonce++;
        // If gas used is greater than gasLimit, revert
        if (_startGas - gasleft() > _gasLimit) {
            revert GasLimitExceeded();
        }
        return true;
    }
}
```

#### How a Trustless Proposer Works

The `TrustlessProposer` above implements three core security mechanisms:

* **Signature Protection**: The `_data` parameter must contain a signature that you created offline, which the contract verifies matches your EOA address
* **Nonce Protection**: Each call requires a unique nonce to prevent replay attacks
* **Deadline Protection**: Each call has a deadline to prevent stale transactions

> **⚠️ Important Safety Note:**
>
> **Custom Storage Layout**: EOA contracts should use a fixed storage location (`layout at`) to prevent storage conflicts when upgrading account code. Since EIP-7702 account code can be updated but storage persists, using a predictable storage layout ensures that e.g. the `nestedNonce` variable won't conflict with future or past account code. See [here](https://decentralizedsecurity.es/eip-7702-ethereums-next-step-toward-a-more-flexible-account-model#:~:text=Storage%20Collision) for more details.

> **For a more complete implementation:** See [TrustlessProposer.sol](https://github.com/spire-labs/da-builder-sample-integration/blob/main/src/proposers/TrustlessProposer.sol) in the sample repository.

### 2. EIP-7702 Account Code Setup

Set up your EOA to use the proposer contract as its account code. This allows others to execute operations on your behalf - your transactions can now be combined with other users' transactions:

```rust
// Set up EIP-7702 account code using Alloy
let authorization = Authorization {
    chain_id: U256::from(chain_id),
    address: proposer_address,
    nonce: auth_nonce,
};

let auth_hash = authorization.signature_hash();
let signature = wallet.sign_hash_sync(&auth_hash)?;
let signed_authorization = authorization.into_signed(signature);

let tx = TransactionRequest::default()
    .with_to(wallet_address)
    .with_authorization_list(vec![signed_authorization])
    .with_input(Bytes::new())
    .with_chain_id(chain_id)
    .with_nonce(nonce);

provider.send_transaction(tx).await?;
```

> **For complete EIP-7702 setup:** See [setup\_eip7702\_account\_code](https://github.com/spire-labs/da-builder-sample-integration/blob/main/src/client/dabuilder_client.rs#L331) in the sample repository.

### 3. Gas Tank Integration

Deposit funds into the DA Builder Gas Tank to pay for the gas used by your transactions. The savings from shared transactions are split amongst participants in the aggregated transaction:

```rust
// Deposit funds to Gas Tank
gas_tank_contract.deposit()
    .value(deposit_amount)
    .send()
    .await?;
```

> **For complete Gas Tank integration:** See [deposit\_to\_gas\_tank](https://github.com/spire-labs/da-builder-sample-integration/blob/main/src/client/dabuilder_client.rs#L289) in the sample repository.

### 4. DA Builder Transaction Submission

Submit transactions through DA Builder's RPC endpoint. The call you make into DA Builder to execute the underlying transaction might be different depending on how you have implemented your Proposer. We provide an example client that interfaces with a TrustlessProposer contract in the sample integration repository:

```rust
// Create transaction request
let tx_request = TransactionRequest::default()
    .with_to(target_address)
    .with_input(calldata)
    .with_value(value);

// Submit to DA Builder
let pending_tx = da_builder_client
    .send_da_builder_transaction(tx_request, deadline_secs)
    .await?;

// Wait for confirmation
let receipt = pending_tx.await?;
```

The `send_da_builder_transaction` method automatically:

* Signs the transaction with EIP-712
* Submits to DA Builder's aggregation service
* Returns a pending transaction for monitoring

> **For complete transaction submission:** See [send\_da\_builder\_transaction](https://github.com/spire-labs/da-builder-sample-integration/blob/main/src/client/dabuilder_client.rs#L189) in the sample repository.

#### EIP-712 Signature Generation

At its core, the TrustlessProposer expects a signed message hash to prove the EOA owner is the one that generated the transaction. Using Alloy:

```rust
// Create the message hash for EIP-712 signing
let call_type_hash = keccak256("Call(uint256 deadline,uint256 nonce,address target,uint256 value,bytes calldata)");
let calldata_hash = alloy::primitives::keccak256(call_data.as_ref());
let struct_hash = keccak256(abi::encode(&[
    call_type_hash.into(),
    deadline.into(),
    nonce.into(),
    target.into(),
    value.into(),
    calldata_hash.into(),
    gas_limit.into(),
]));

let message_hash = _hashTypedDataV4(struct_hash);
let signature = wallet.sign_hash_sync(&message_hash)?;
```

> **For complete EIP-712 implementation:** See [prepare\_trustless\_proposer\_call](https://github.com/spire-labs/da-builder-sample-integration/blob/main/src/client/dabuilder_client.rs#L611) in the sample repository.

### 5. Transaction Monitoring

When you submit a transaction to DA Builder, you receive a **DA Builder Request ID** (not an immediate blockchain hash). Use this ID to track your transaction's progress:

```rust
// Submit transaction and get DA Builder Request ID
let pending_tx = da_builder_client
    .send_da_builder_transaction(tx_request, deadline_secs)
    .await?;

// Monitor using the DA Builder Request ID
let receipt = pending_tx.await?;

// The receipt contains the actual blockchain transaction details
if receipt.status == Some(1) {
    println!("Transaction successful on blockchain");
} else {
    println!("Transaction failed on blockchain");
}
```

Your transaction is batched with others and eventually included in an aggregated on-chain transaction. The final receipt contains the actual blockchain transaction hash that you can use for further tracking if needed.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://spire-docs.gitbook.io/spire/da-builder/getting-started/detailed-walkthrough.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
