> ## Documentation Index
> Fetch the complete documentation index at: https://docs.railnet.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Operate an Advanced Strategy

> Route protocol actions through the on-chain policy engine — one ABI, one extra encode step

export const CallFlow = () => {
  const CSS = `
    .cf-bg{fill:#F8F8F8;rx:4;ry:4}.dark .cf-bg{fill:#141414}
    .cf-t{fill:#374151;font-family:"Inter Tight","Inter",system-ui,sans-serif}.dark .cf-t{fill:#D1D5DB}
    .cf-tm{fill:#9CA3AF;font-family:"Inter Tight","Inter",system-ui,sans-serif;font-style:italic}.dark .cf-tm{fill:#6B7280}
    .cf-n{fill:#fff;stroke:#D1D5DB;stroke-width:1;rx:3;ry:3}.dark .cf-n{fill:#1F2937;stroke:#4B5563}
    .cf-pe{fill:#CCE5E6;rx:3;ry:3}.dark .cf-pe{fill:#1A3535}
    .cf-ln{stroke:#ACACAC;fill:none;stroke-width:1.5;color:#ACACAC}.dark .cf-ln{stroke:#666;color:#666}
  `;
  return <div className="not-prose w-full my-6 overflow-x-auto" style={{
    padding: '2px'
  }}>
      <style>{CSS}</style>
      <svg viewBox="0 -15 500 270" style={{
    width: '100%',
    height: 'auto',
    maxWidth: '520px',
    display: 'block'
  }} role="img" aria-label="On-chain call flow: member signer through policy engine to protocol">
        <defs>
          <marker id="cf-a" viewBox="0 0 10 7" refX="9" refY="3.5" markerWidth="8" markerHeight="6" orient="auto">
            <polygon points="0 0,10 3.5,0 7" fill="currentColor" />
          </marker>
        </defs>
        <rect x="0" y="-15" width="500" height="270" className="cf-bg" />

        <rect x="40" y="10" width="180" height="34" className="cf-n" />
        <text x="130" y="31" textAnchor="middle" className="cf-t" fontSize="12" fontWeight="600">Member signer</text>

        <line x1="130" y1="44" x2="130" y2="68" className="cf-ln" markerEnd="url(#cf-a)" />
        <text x="148" y="60" className="cf-tm" fontSize="10">signs tx to</text>

        <rect x="40" y="74" width="180" height="34" className="cf-pe" />
        <text x="130" y="95" textAnchor="middle" className="cf-t" fontSize="12" fontWeight="600">Policy Engine</text>
        <text x="232" y="90" className="cf-tm" fontSize="10">checks role + param constraints</text>

        <line x1="130" y1="108" x2="130" y2="140" className="cf-ln" markerEnd="url(#cf-a)" />

        <rect x="40" y="146" width="180" height="34" className="cf-pe" />
        <text x="130" y="167" textAnchor="middle" className="cf-t" fontSize="12" fontWeight="600">Strategy (Safe)</text>
        <text x="232" y="162" className="cf-tm" fontSize="10">msg.sender at the protocol</text>

        <line x1="130" y1="180" x2="130" y2="210" className="cf-ln" markerEnd="url(#cf-a)" />

        <rect x="15" y="216" width="270" height="34" className="cf-n" />
        <text x="150" y="237" textAnchor="middle" className="cf-t" fontSize="12" fontWeight="500">Protocol (Aave, Uniswap, Curve, ...)</text>
      </svg>
    </div>;
};

<Info>In Railnet smart contracts, an Advanced Strategy is implemented as a **Specialized Vehicle**. The Policy Engine is a Zodiac Roles Modifier v2 contract enabled as a module on the Strategy's Safe wallet.</Info>

This is the operator guide for teams running protocol actions on a Railnet Advanced Strategy — the on-chain container that holds the capital and enforces the mandate you were given. It assumes you already know how to craft a transaction for your target protocol (`supply`, `swap`, `mint`, whatever) and that you already have a production signer. What you don't yet know is the one extra step that routes your calldata through the Strategy's Policy Engine so your mandate is enforced for you on-chain.

Good news for the PM: your current execution engine keeps doing everything it already does. You add one outer encode step and change the `to` address. That's the whole integration.

Good news for the dev: it's one ABI fragment, one function call, six args.

## TL;DR

<CodeGroup>
  ```text Pseudocode theme={null}
  inner   = encode(protocol.someAction, args)          # you already know how
  wrapped = encode(RolesModifier.execTransactionWithRole,
                   [innerTo, 0, inner, 0, ROLE_KEY, true])
  tx      = { to: POLICY_ENGINE_ADDRESS, data: wrapped, value: 0 }
  signer.signAndBroadcast(tx)                          # your existing infra
  ```
</CodeGroup>

Four lines. The first and last already exist in your codebase. Line 2 is the integration.

## Mental model

* The **Advanced Strategy** is a Safe. It holds the capital. Shortened to "the Strategy" for the rest of this guide.
* The **Policy Engine** is a Zodiac Roles Modifier v2 contract, enabled as a module on the Strategy. It is a **separate address** from the Strategy — the administrator gives you both.
* Your **member signer** (EOA, hardware wallet, Fireblocks vault, Safe, KMS key, anything) is registered against a **role key** on the Policy Engine. That role defines exactly which functions, on which targets, with which argument shapes, you are allowed to push through.

On-chain call flow:

<CallFlow />

From the protocol's perspective `msg.sender == STRATEGY_ADDRESS`. No allowlist update, no integration change, nothing about your signer is visible to the target contract.

## What the administrator gives you

| Name                    | Type      | What it is                                                         |
| ----------------------- | --------- | ------------------------------------------------------------------ |
| `STRATEGY_ADDRESS`      | `address` | The Safe that holds the capital and executes the protocol call     |
| `POLICY_ENGINE_ADDRESS` | `address` | The Roles Modifier v2 contract enabled as a module on the Strategy |
| `ROLE_KEY`              | `bytes32` | Identifier for your member role on the Policy Engine               |

These are independent values. There is no deterministic derivation between `STRATEGY_ADDRESS` and `POLICY_ENGINE_ADDRESS` — ask the admin for both.

## The role key

Two encodings are in the wild:

**Numeric index** — a `uint` left-padded to 32 bytes. Simplest single-role deployments use this.

```
0x0000000000000000000000000000000000000000000000000000000000000001
```

**ASCII label packed as bytes32** — an admin-chosen human-readable string, left-aligned and right-zero-padded to 32 bytes. Most production deployments use this because it's greppable in explorer traces. Example — `"aave_usdc"`:

```
0x616176655f757364630000000000000000000000000000000000000000000000
```

At runtime the value is opaque to you. The admin tells you the literal `bytes32`; you pass it through. If it's wrong, the Policy Engine reverts with `NoMembership`.

## The only ABI you need

This is the one contract surface you must paste into your ABI registry. Everything else about the Policy Engine is invisible to the execution path.

```json theme={null}
{
  "name": "execTransactionWithRole",
  "type": "function",
  "stateMutability": "nonpayable",
  "inputs": [
    { "name": "to",           "type": "address" },
    { "name": "value",        "type": "uint256" },
    { "name": "data",         "type": "bytes"   },
    { "name": "operation",    "type": "uint8"   },
    { "name": "roleKey",      "type": "bytes32" },
    { "name": "shouldRevert", "type": "bool"    }
  ],
  "outputs": [{ "name": "success", "type": "bool" }]
}
```

Argument by argument:

* **`to`** — the **protocol** target (Aave Pool, Uniswap Router, USDC token, ...), not the Strategy.
* **`value`** — ETH to forward. Almost always `0`.
* **`data`** — your already-crafted inner calldata, untouched.
* **`operation`** — **always `0` (CALL)**. Never `1` (DELEGATECALL); that path is reserved for internal tooling and has no valid use from an operator member.
* **`roleKey`** — the `bytes32` from the section above.
* **`shouldRevert`** — set to `true`. Makes inner failures revert the whole transaction instead of silently returning `false`, so your ops pipeline sees real errors.

## The flow in pseudocode

Language-agnostic. Steps 1, 3, and 4 are what you're already doing for direct protocol calls today; step 2 is the entire integration.

<Steps>
  <Step title="Craft your inner protocol call the way you already do">
    ```
    inner_to, inner_data, inner_value = craft_your_protocol_tx(...)
    ```
  </Step>

  <Step title="Wrap it in execTransactionWithRole">
    This is the one new step.

    ```
    wrapped_data = abi_encode(
        ROLES_MODIFIER_EXEC_ABI,
        "execTransactionWithRole",
        [inner_to, inner_value, inner_data, 0, ROLE_KEY, True],
    )
    ```
  </Step>

  <Step title="Build an ordinary transaction with the Policy Engine as the target">
    ```
    tx = {
        "to":    POLICY_ENGINE_ADDRESS,
        "data":  wrapped_data,
        "value": 0,
        # nonce / gas / chainId filled in by your existing signer pipeline
    }
    ```
  </Step>

  <Step title="Hand it to whatever signer you already use">
    ```
    signed  = signer.sign(tx)
    tx_hash = rpc.send_raw_transaction(signed)
    receipt = rpc.wait_for_receipt(tx_hash)
    ```
  </Step>
</Steps>

That's the contract: encode one extra layer, change the `to` address, everything else stays.

## Reference implementation

Generic version using TypeScript and viem. Replace the two marked variables with whatever your existing protocol-crafting code produces.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { createWalletClient, http } from "viem"
  import { privateKeyToAccount } from "viem/accounts"
  import { mainnet } from "viem/chains" // your chain of choice

  const ROLES_ABI = [
    { name: "execTransactionWithRole", type: "function", stateMutability: "nonpayable",
      inputs: [
        { name: "to",           type: "address" },
        { name: "value",        type: "uint256" },
        { name: "data",         type: "bytes"   },
        { name: "operation",    type: "uint8"   },
        { name: "roleKey",      type: "bytes32" },
        { name: "shouldRevert", type: "bool"    },
      ],
      outputs: [{ name: "success", type: "bool" }] },
  ] as const

  const wallet = createWalletClient({
    account:   privateKeyToAccount(MEMBER_PRIVATE_KEY),
    chain:     mainnet,
    transport: http(RPC_URL),
  })

  // 1. You already produced these
  const INNER_TARGET: `0x${string}` = /* your protocol contract */
  const INNER_DATA:   `0x${string}` = /* your protocol calldata  */

  // 2. Push it through the Policy Engine
  const txHash = await wallet.writeContract({
    address:      POLICY_ENGINE_ADDRESS,
    abi:          ROLES_ABI,
    functionName: "execTransactionWithRole",
    args: [INNER_TARGET, 0n, INNER_DATA, 0, ROLE_KEY, true],
  })
  ```

  ```solidity Solidity theme={null}
  // Coming soon
  ```
</CodeGroup>

Swap `privateKeyToAccount` for your signer's viem-compatible account object — Fireblocks, Turnkey, AWS KMS, Ledger Connect, Dynamic, Privy, and most others ship one. The rest of the snippet is identical regardless of backend.

## Any signer, same bytes

The Policy Engine does not care **how** a transaction was signed, only that the `from` address is a registered member of the role. Anything that can sign a standard EIP-1559 contract call to `POLICY_ENGINE_ADDRESS` works out of the box.

<AccordionGroup>
  <Accordion title="Hot wallet / EOA">
    As shown in the reference implementation above. Sign and broadcast directly with a private key using viem, ethers, or any web3 library.
  </Accordion>

  <Accordion title="Hardware wallet (Ledger, Trezor, GridPlus)">
    User approves on device. The outer transaction is a plain contract call, so the device shows `to = Policy Engine`, `function = execTransactionWithRole`.
  </Accordion>

  <Accordion title="Institutional custody (Fireblocks, BitGo, Copper, Anchorage, Qredo)">
    Create a contract-call transaction in the provider's SDK with `to = POLICY_ENGINE_ADDRESS`, `data = wrapped_data`. No custom integration on the provider side.
  </Accordion>

  <Accordion title="Cloud KMS / MPC (AWS KMS, GCP KMS, Turnkey, Lit)">
    Sign the raw transaction bytes exactly as you would any other contract call. The Policy Engine sees a standard `from` address — it does not inspect the signing backend.
  </Accordion>

  <Accordion title="A Safe multisig as the member">
    Covered in the next section because it has two extra steps.
  </Accordion>
</AccordionGroup>

Every option above produces the same on-chain call. Pick whichever fits your existing ops posture and you are done.

## When the member signer is a Safe

Some teams register a dedicated Safe as the Policy Engine member so signing authority is already multi-party before any transaction touches the Strategy. The flow adds two steps around what you already built above.

1. Build `wrapped_data` exactly as in step 2 of the pseudocode flow (targeting the Policy Engine).
2. Wrap it a **second time** as a Safe transaction: a SafeTx with `to = POLICY_ENGINE_ADDRESS`, `data = wrapped_data`, `value = 0`, `operation = 0`.
3. Collect owner signatures on the SafeTx hash via your existing Safe flow — Safe Transaction Service, in-house queue, whatever you already run.
4. Execute the SafeTx. The on-chain path becomes:

```
Member Safe --> Policy Engine --> Strategy Safe --> Protocol
```

Minimal wrap step with `@safe-global/protocol-kit` (the rest — proposal, confirmation collection, execution — is your existing Safe infra):

<CodeGroup>
  ```typescript TypeScript theme={null}
  import Safe from "@safe-global/protocol-kit"

  const memberSafe = await Safe.init({
    provider:    RPC_URL,
    signer:      OWNER_KEY,
    safeAddress: MEMBER_SAFE_ADDRESS,
  })

  const safeTx = await memberSafe.createTransaction({
    transactions: [{
      to:        POLICY_ENGINE_ADDRESS,
      value:     "0",
      data:      wrappedData,   // from step 2 of the pseudocode flow
      operation: 0,
    }],
  })
  // ...then sign, propose, and execute via your existing Safe flow
  ```

  ```solidity Solidity theme={null}
  // Coming soon
  ```
</CodeGroup>

Gas is paid by whichever owner executes the SafeTx, not the proposer.

## What to read next

<CardGroup cols={3}>
  <Card title="Policy engine" icon="shield-check" href="/strategies/advanced/policy-engine">
    Understand how the on-chain policy engine governs what operators can do.
  </Card>

  <Card title="Advanced Strategies" icon="layer-group" href="/strategies/advanced">
    Overview of Specialized Vehicles — custody, policy, and accounting.
  </Card>

  <Card title="Risk management" icon="chart-line" href="/strategies/risk-management">
    Risk frameworks and guardrails for strategy operations.
  </Card>
</CardGroup>
