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

# Smart Contracts & Multi-sigs

> Own and operate a Derive account from a multi-sig or smart contract on the Ethereum L1.

A Derive account is owned by a single Ethereum L1 address — and that address can be a
**multi-sig or any smart contract**, not just an EOA. The contract keeps custody and control
on L1, while a delegated [session key](/authentication/session-keys) does the day-to-day
trading.

<Check>All of the steps in this guide can be done through the UX or programmatically.</Check>

## Why it works differently

Most of the API is driven by **signed actions**: your wallet produces a single-key EIP-712
signature and the protocol verifies it (see [Action signing](/authentication/action-signing)).
A multi-sig or smart contract has no single private key, so it cannot produce that signature
directly.

Instead, a contract owner operates entirely through **L1 onchain actions** — transactions it
sends to the `OnchainActionManager` contract. The exchange's L1 listener picks each one up and
applies it, authorized by the transaction's `msg.sender`. Two building blocks are enough to run
an account end to end:

<CardGroup cols={2}>
  <Card title="Deposit" icon="wallet">
    Creating and funding the account is already an L1 call — the same for every owner. See
    [Programmatic Onboarding](/getting-started/depositing).
  </Card>

  <Card title="Set session key" icon="key">
    Authorize an EOA session key from L1, then let that key sign trades, transfers, and vault
    operations offchain.
  </Card>
</CardGroup>

<Note>
  The account owner is whatever address you pass as `owner` when depositing, and the L1 listener
  authorizes onchain actions by the transaction's `msg.sender`. So the **same contract that owns
  the account must send the `OnchainActionManager` transactions** — set the session key from the
  multi-sig itself, not from an unrelated EOA.
</Note>

## Set up and operate

<Steps>
  <Step title="Create and fund the account">
    The first deposit creates the account and its subaccounts automatically.

    Make sure that either

    * `owner` address you pass into
      `OnchainActionMAnager.depositToNewSubaccount()` is the multi-sig / smart contract, or
    * the `wallet` you send to `public/register_deposit_address` is the multi-sig / smart contract.

    Deposit through the UX or see [Programmatic Onboarding](/getting-started/depositing) for more information.

    <Note>
      An onchain Set Session Key is ignored if the account does not exist yet.
    </Note>
  </Step>

  <Step title="Authorize a session key via L1">
    Submit a Set Session Key onchain action from the owner contract. This registers an EOA
    session key with the protocol scopes you choose (see [Access scopes](/authentication/access-scopes)).

    <CodeGroup>
      ```solidity Solidity theme={null}
      interface IOnchainActionManager {
          function submit(uint256 actionType, bytes calldata data) external payable returns (uint256 actionId);
      }

      uint256 constant SET_SESSION_KEY = 51;
      // Protocol scope codes — see /authentication/access-scopes.
      uint256 constant SCOPE_TRADE_ALL = 2;
      uint256 constant SCOPE_TRANSFER_EXISTING_SUBACCOUNT = 12;

      /// Called by the owner contract itself, so msg.sender is the account owner.
      function authorizeSessionKey(IOnchainActionManager manager, address sessionKey, uint256 expirySec) external {
          // SetSessionKeyActionData — standard `abi.encode(address, uint256,
          // uint256[] scopes, uint256[] subaccountIds)`.
          uint256[] memory scopes = new uint256[](2);
          scopes[0] = SCOPE_TRADE_ALL;
          scopes[1] = SCOPE_TRANSFER_EXISTING_SUBACCOUNT;
          uint256[] memory subaccountIds = new uint256[](0); // empty = all subaccounts
          bytes memory data = abi.encode(sessionKey, expirySec, scopes, subaccountIds);
          manager.submit(SET_SESSION_KEY, data);
      }
      ```

      ```typescript TypeScript (SDK) theme={null}
      import { DeriveClient, ProtocolScopeCode } from '@derivexyz/derive-ts';
      import { Wallet } from 'ethers';

      const client = new DeriveClient({ network: 'mainnet', ownerAddress: MULTISIG_ADDRESS });

      // A signer connected to the chain RPC that executes the OnchainActionManager
      // transaction as the account owner (an owner EOA, a Safe module, etc.).
      const signer = new Wallet(process.env.OWNER_KEY!, provider);

      // Register a trading session key via L1.
      await client.onchainActions.setSessionKey({
        signer,
        sessionKey: SESSION_KEY_ADDRESS,
        expirySec: Math.floor(Date.now() / 1000) + 30 * 24 * 3600,
        scopes: [ProtocolScopeCode.TradeAll, ProtocolScopeCode.TransferExistingSubaccount],
      });
      ```

      ```python Python (SDK) theme={null}
      # Open an issue in derivexyz/derive-py if you'd like this implemented in the SDK.
      ```

      ```rust Rust (SDK) theme={null}
      // Open an issue in derivexyz/derive-rs if you'd like this implemented in the SDK.
      ```
    </CodeGroup>
  </Step>

  <Step title="Operate with the session key">
    From here everything is normal: the session key [logs in](/authentication/session-login) and
    signs actions — trading, transfers, and [creating or curating a vault](/vaults/create-a-vault)
    — with no further L1 transactions.

    When connecting to the UX with the session key you will be prompted to choose between signing in
    as the session key or the smart contract.
  </Step>

  <Step title="Revoke">
    Remove a key by sending the same action with expiry `0` — the only way to delete a key (the
    offchain API has no revoke).

    <CodeGroup>
      ```typescript TypeScript (SDK) theme={null}
      // Revoke: a Set Session Key with expiry 0.
      await client.onchainActions.revokeSessionKey({ signer, sessionKey: SESSION_KEY_ADDRESS });
      ```

      ```solidity Solidity theme={null}
      // Revoke = Set Session Key with expiry 0 (deletes the key).
      // IOnchainActionManager as declared in the previous step.
      function revokeSessionKey(IOnchainActionManager manager, address sessionKey) external {
          uint256[] memory empty = new uint256[](0);
          bytes memory data = abi.encode(sessionKey, uint256(0), empty, empty); // expiry 0 = delete
          manager.submit(51, data);
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

## Related

<CardGroup cols={2}>
  <Card title="Session keys" icon="key" href="/authentication/session-keys">
    What a session key is, its scopes, and its lifecycle.
  </Card>

  <Card title="Access scopes" icon="lock" href="/authentication/access-scopes">
    The full protocol and off-chain scope catalog to scope a key tightly.
  </Card>

  <Card title="Programmatic Onboarding" icon="wallet" href="/getting-started/depositing">
    Create and fund the account with an L1 deposit.
  </Card>

  <Card title="Create a Vault" icon="vault" href="/vaults/create-a-vault">
    Run a vault from the account with the session key.
  </Card>
</CardGroup>


## Related topics

- [DevEx improvements](/migrating/v3-improvements.md)
- [Create a Vault](/vaults/create-a-vault.md)
- [Session Keys](/authentication/session-keys.md)
