# Operate an Advanced Strategy \[Route protocol actions through the on-chain policy engine]

:::info
In Railnet smart contracts, an Advanced Strategy is implemented as a **Specialized Vehicle**. Its policy engine is a Zodiac Roles Modifier v2 contract enabled as a module on the Strategy Safe.
:::

You already craft transactions for your target protocols and you already have a signer. The integration is one outer encode step and a different `to` address:

```text [Pseudocode]
inner   = encode(protocol.someAction, args)          # unchanged
wrapped = encode(RolesModifier.execTransactionWithRole,
                 [innerTo, 0, inner, 0, ROLE_KEY, true])
tx      = { to: POLICY_ENGINE_ADDRESS, data: wrapped, value: 0 }
signer.signAndBroadcast(tx)                          # unchanged
```

<CallFlow />

From the protocol's side, `msg.sender == STRATEGY_ADDRESS`. Nothing about your signer is visible to the target contract, so no allowlist or integration on their side changes. What your role allows is defined in the [policy engine](/strategies/advanced/policy-engine).

## What the administrator gives you

| Name | Type | What it is |
|---|---|---|
| `STRATEGY_ADDRESS` | `address` | The Safe holding capital, which executes the protocol call |
| `POLICY_ENGINE_ADDRESS` | `address` | The Roles Modifier v2 enabled as a module on the Strategy |
| `ROLE_KEY` | `bytes32` | Your member role on the policy engine |

These are independent values with no derivation between them; ask for all three. The role key is either a numeric index left-padded to 32 bytes, or an ASCII label right-zero-padded (`"aave_usdc"` becomes `0x616176655f757364630000...`). Either way it is opaque to you: pass the literal `bytes32` through. A wrong key reverts with `NoMembership`.

## The only ABI you need

```json [JSON]
{
  "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" }]
}
```

* `to`: the protocol target (Aave Pool, Uniswap Router, USDC), not the Strategy
* `value`: ETH to forward, almost always `0`
* `data`: your inner calldata, untouched
* `operation`: always `0` (CALL). `1` (DELEGATECALL) has no valid use from an operator member and the policy engine blocks it
* `roleKey`: the `bytes32` above
* `shouldRevert`: `true`, so inner failures revert the transaction instead of silently returning `false`

## Reference implementation

```typescript [TypeScript]
import { createWalletClient, http } from "viem"
import { privateKeyToAccount } from "viem/accounts"
import { mainnet } from "viem/chains"

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),
})

// Produced by your existing protocol-crafting code
const INNER_TARGET: `0x${string}` = /* protocol contract */
const INNER_DATA:   `0x${string}` = /* protocol calldata */

const txHash = await wallet.writeContract({
  address:      POLICY_ENGINE_ADDRESS,
  abi:          ROLES_ABI,
  functionName: "execTransactionWithRole",
  args: [INNER_TARGET, 0n, INNER_DATA, 0, ROLE_KEY, true],
})
```

Swap `privateKeyToAccount` for your signer's viem-compatible account. Fireblocks, Turnkey, AWS KMS, Ledger, Dynamic and Privy all ship one, and the rest of the snippet is unchanged.

## Any signer works

The policy engine checks that `from` is a registered member of the role; it does not inspect how the transaction was signed. An EOA, a hardware wallet, institutional custody (Fireblocks, BitGo, Copper, Anchorage, Qredo) or cloud KMS and MPC all produce the same on-chain call. Build a plain contract call with `to = POLICY_ENGINE_ADDRESS` and `data = wrapped_data` in whatever SDK you already run.

## When the member is a Safe

Registering a dedicated Safe as the member puts multi-party approval in front of every Strategy transaction. Build `wrapped_data` as above, then wrap it a second time as a SafeTx with `to = POLICY_ENGINE_ADDRESS`, `value = 0`, `operation = 0`. Collect owner signatures through your existing Safe flow and execute. The on-chain path becomes `Member Safe → Policy Engine → Strategy Safe → Protocol`, and gas is paid by whoever executes the SafeTx.

```typescript [TypeScript]
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,
    operation: 0,
  }],
})
// then sign, propose and execute via your existing Safe flow
```

## Next steps

<Cards>
  <Card title="Policy engine" description="What your role allows, and why a call reverts." icon="shield-half" to="/strategies/advanced/policy-engine" />

  <Card title="Accounting" description="Run the propose-and-settle NAV cycle." icon="calculator" to="/strategies/advanced/accounting" />

  <Card title="Vault 7540 mechanics" description="Deposits, redemptions, settlement, fees." icon="vault" to="/strategies/advanced/vault" />
</Cards>
