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

# Internal Transfer

> Discover how to perform internal transfers between accounts and sub-accounts using wallet-signed requests.

## Internal Transfer With Wallet Signature

The Internal Transfer with Wallet Signature API allows users to transfer assets between Orderly accounts. This API requires a wallet EIP-712 signature to verify the ownership and authorization of the transfer.

To create an internal transfer request:

<Steps>
  <Step title="Obtain a transfer nonce">
    Get a valid transfer nonce from the [Get Transfer Nonce API](/build-on-omnichain/restful-api/private/get-transfer-nonce).

    <Note>
      This API requires Orderly authentication headers:

      * `orderly-timestamp`: Current timestamp in milliseconds
      * `orderly-account-id`: Your Orderly Account ID
      * `orderly-key`: Your Orderly Key
      * `orderly-signature`: Ed25519 signature of the request message

      The signature is generated by signing `{timestamp}{http_method}{url_path}{payload}` using your Orderly Secret.
      For production use, it's recommended to use the Orderly SDK which handles authentication automatically.
    </Note>

    <Accordion title="Example code">
      <CodeGroup>
        ```ts TypeScript theme={null}
        interface Client {
          _sign_request(method: string, path: string, payload?: any): Promise<any>;
        }

        async function get_transfer_nonce(client: Client): Promise<number | null> {
          try {
            const response = await client._sign_request("GET", "/v1/transfer_nonce");
            if (response.data && response.data.transfer_nonce) {
              return response.data.transfer_nonce;
            } else if (response.transfer_nonce) {
              return response.transfer_nonce;
            }
            return null;
          } catch (error) {
            return null;
          }
        }
        ```

        ```py Python theme={null}
        def get_transfer_nonce(client):
            try:
                response = client._sign_request("GET", "/v1/transfer_nonce")
                if 'data' in response and 'transfer_nonce' in response['data']:
                    return response['data']['transfer_nonce']
                elif 'transfer_nonce' in response:
                    return response['transfer_nonce']
                return None
            except Exception:
                return None
        ```
      </CodeGroup>
    </Accordion>

    ```json theme={null}
    // Response Example
    {
      "success": true,
      "timestamp": 1702989203989,
      "data": {
        "transfer_nonce": 1
      }
    }
    ```
  </Step>

  <Step title="Obtain a signature from EIP-712">
    Sign an EIP-712 message of [message type](/build-on-omnichain/user-flows/wallet-authentication#message-types) `InternalTransfer`:

    ```json theme={null}
    "InternalTransfer": [
        { "name": "receiver", "type": "bytes32" },
        { "name": "token", "type": "string" },
        { "name": "amount", "type": "uint256" },
        { "name": "transferNonce", "type": "uint64" }
    ]
    ```

    where:

    | **Name**      | **Type** | **Required** | **Description**                                                                                  |
    | ------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------ |
    | receiver      | bytes32  | Y            | The Orderly Account ID of the receiver (must be converted to bytes32 format for EIP-712 signing) |
    | token         | string   | Y            | The token symbol (e.g., "USDC")                                                                  |
    | amount        | uint256  | Y            | The amount to transfer (raw value with decimals, e.g., 1000000 for 1 USDC)                       |
    | transferNonce | uint64   | Y            | The nonce obtained from Step 1                                                                   |

    <Note>
      The EIP-712 signature uses the [on-chain domain](/build-on-omnichain/user-flows/wallet-authentication#on-chain-domain). The `verifyingContract` should be the Ledger contract address, which can be found [here](/build-on-omnichain/addresses).
    </Note>

    <Accordion title="Example code">
      <CodeGroup>
        ```ts TypeScript theme={null}
        import { ethers } from "ethers";

        const chainId = 42161; // Arbitrum One
        const ledgerContractAddress = "0x..."; // Get from addresses page

        // Define Domain
        const domain = {
          name: "Orderly",
          version: "1",
          chainId: chainId,
          verifyingContract: ledgerContractAddress
        };

        // Define Types
        const types = {
          InternalTransfer: [
            { name: "receiver", type: "bytes32" },
            { name: "token", type: "string" },
            { name: "amount", type: "uint256" },
            { name: "transferNonce", type: "uint64" }
          ]
        };

        // Define Message Data
        // Note: ethers.js signTypedData automatically converts hex strings to bytes32
        const receiverAccountId = "0x9ff99a5d6cb71a3ef897b0fff5f5801af6dc5f72d8f1608e61409b8fc965bd68";
        const message = {
          receiver: receiverAccountId, // ethers.js automatically handles hex string to bytes32 conversion
          token: "USDC",
          amount: 1000000n, // 1 USDC (assuming 6 decimals)
          transferNonce: 1 // From API
        };

        // Sign
        const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!);
        const signature = await wallet.signTypedData(domain, types, message);
        ```

        ```py Python theme={null}
        import os
        from eth_account import Account
        from eth_account.messages import encode_typed_data

        chain_id = 42161  # Arbitrum One
        ledger_contract_address = "0x..."  # Get from addresses page
        private_key = os.environ.get("PRIVATE_KEY")
        if not private_key:
            raise ValueError("PRIVATE_KEY environment variable is not set")
        receiver_account_id = "0x9ff99a5d6cb71a3ef897b0fff5f5801af6dc5f72d8f1608e61409b8fc965bd68"

        # Construct EIP-712 Data
        # Note: receiver must be converted from hex string to bytes32 for signing
        data = {
            "types": {
                "EIP712Domain": [
                    {"name": "name", "type": "string"},
                    {"name": "version", "type": "string"},
                    {"name": "chainId", "type": "uint256"},
                    {"name": "verifyingContract", "type": "address"},
                ],
                "InternalTransfer": [
                    {"name": "receiver", "type": "bytes32"},
                    {"name": "token", "type": "string"},
                    {"name": "amount", "type": "uint256"},
                    {"name": "transferNonce", "type": "uint64"},
                ],
            },
            "domain": {
                "name": "Orderly",
                "version": "1",
                "chainId": chain_id,
                "verifyingContract": ledger_contract_address,
            },
            "primaryType": "InternalTransfer",
            "message": {
                "receiver": bytes.fromhex(receiver_account_id[2:]),  # Convert hex string to bytes32
                "token": "USDC",
                "amount": 1000000,
                "transferNonce": 1
            },
        }

        # Sign
        account = Account.from_key(private_key)
        signable_message = encode_typed_data(full_message=data)
        signed_message = account.sign_message(signable_message)
        signature = signed_message.signature.hex()
        ```
      </CodeGroup>
    </Accordion>
  </Step>

  <Step title="Make an internal transfer request">
    Call the [Create Internal Transfer With Wallet Signature API](/build-on-omnichain/restful-api/private/create-internal-transfer-with-wallet-signature).

    The request body must include the generated signature and the message parameters used to create it:

    > \[!WARNING]
    > **EIP-712 Signature vs. API Payload Mismatch**:
    > The REST API request body `message` structure includes helper fields `chainId` and `chainType`. These fields **must NOT** be included in the EIP-712 message object during cryptography signing. If you include them in the EIP-712 signature `message` hash, the gateway validation will fail.

    <Note>
      Note that in the API request, `receiver` is a string (hex format with 0x prefix), while in the EIP-712 signature it must be bytes32. The `userAddress` must be an EVM address format (0x-prefixed hex string) for EVM chains, or a Solana address for SOL chains.
    </Note>

    ```json theme={null}

    {
    "message": {
    "receiver": "0x9ff99a5d6cb71a3ef897b0fff5f5801af6dc5f72d8f1608e61409b8fc965bd68",
    "token": "USDC",
    "amount": "1000000",
    "transferNonce": "2",
    "chainId": "421614",
    "chainType": "EVM"
    },
    "signature": "0xa9f5ef503a95af0bb0211858e8b6a83a3d23d7a84b8db3c2aa3327deb34b084577758cc6d8cbc9a047d60436c62d0591ee6693f34487bea785247608516360991b",
    "userAddress": "0xb6309c90b5ecc2f8a87987294af03951e80bfef0",
    "verifyingContract": "0x1826B75e2ef249173FC735149AE4B8e9ea10abff"
    }

    ```

    where:

    | **Parameter**         | **Type** | **Required** | **Description**                                          |
    | --------------------- | -------- | ------------ | -------------------------------------------------------- |
    | message.receiver      | string   | Y            | The Orderly Account ID of the receiver                   |
    | message.token         | string   | Y            | Token symbol                                             |
    | message.amount        | string   | Y            | Transfer amount                                          |
    | message.transferNonce | string   | Y            | Must match the nonce used in the signature               |
    | message.chainId       | string   | Y            | Chain ID                                                 |
    | message.chainType     | string   | Y            | Chain type: `EVM` or `SOL`                               |
    | signature             | string   | Y            | The EIP-712 signature string                             |
    | userAddress           | string   | Y            | The wallet address of the sender (must match the signer) |
    | verifyingContract     | string   | Y            | The contract address used in the EIP-712 domain          |
  </Step>
</Steps>

<Note>
  Ensure the `userAddress` matches the wallet address associated with the Orderly Account ID making
  the request. The standard Orderly authentication headers (Orderly Key signature) are also required
  for this API call.
</Note>

### Full example

<CodeGroup>
  ```ts TypeScript theme={null}
  import { ethers } from 'ethers';

  const ACCOUNT_ID = "0x...";
  const ENV = 'mainnet';
  const ORDERLY_KEY = "ed25519:xxx";
  const ORDERLY_SECRET = "ed25519:xxx";
  const WALLET_SECRET = "70xxxf9";
  const USER_ADDRESS = "0x...";
  const CHAIN_ID = ENV === 'mainnet' ? 42161 : 421614;
  const LEDGER_CONTRACT_ADDRESS = ENV === 'mainnet'
      ? "0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203"
      : "0x1826B75e2ef249173FC735149AE4B8e9ea10abff";
  const RECEIVER_ACCOUNT_ID = "0x...";
  const TRANSFER_AMOUNT = 1000000;
  const ORDERLY_TESTNET = ENV === 'testnet';

  interface Client {
      _sign_request(method: string, path: string, payload?: any): Promise<any>;
  }

  async function get_transfer_nonce(client: Client): Promise<number | null> {
      try {
          const response = await client._sign_request("GET", "/v1/transfer_nonce");
          if (response.data && response.data.transfer_nonce) {
              return response.data.transfer_nonce;
          } else if (response.transfer_nonce) {
              return response.transfer_nonce;
          }
          return null;
      } catch (error) {
          return null;
      }
  }

  async function sign_internal_transfer_message(
      receiver_account_id: string,
      token: string,
      amount: number,
      transfer_nonce: number,
      chain_id: number,
      ledger_contract_address: string,
      private_key: string
  ): Promise<string> {
      const receiver_hex = receiver_account_id.startsWith('0x')
          ? receiver_account_id.slice(2)
          : receiver_account_id;
      const receiver_bytes32 = ethers.zeroPadValue('0x' + receiver_hex, 32);

      const domain = {
          name: "Orderly",
          version: "1",
          chainId: chain_id,
          verifyingContract: ledger_contract_address,
      };

      const types = {
          InternalTransfer: [
              { name: "receiver", type: "bytes32" },
              { name: "token", type: "string" },
              { name: "amount", type: "uint256" },
              { name: "transferNonce", type: "uint64" },
          ],
      };

      const message = {
          receiver: receiver_bytes32,
          token: token,
          amount: amount,
          transferNonce: transfer_nonce,
      };

      const wallet = new ethers.Wallet(private_key);
      const signature = await wallet.signTypedData(domain, types, message);
      return signature;
  }

  async function create_internal_transfer_v2(
      client: Client,
      receiver_account_id: string,
      token: string,
      amount: string,
      transfer_nonce: number,
      chain_id: number,
      chain_type: string,
      signature: string,
      user_address: string,
      verifying_contract: string
  ): Promise<any> {
      const payload = {
          message: {
              receiver: receiver_account_id,
              token: token,
              amount: amount,
              transferNonce: String(transfer_nonce),
              chainId: String(chain_id),
              chainType: chain_type,
          },
          signature: signature,
          userAddress: user_address,
          verifyingContract: verifying_contract,
      };

      try {
          return await client._sign_request("POST", "/v2/internal_transfer", payload);
      } catch (error) {
          return null;
      }
  }

  async function test_internal_transfer_v2(): Promise<any> {
      try {
          const { Rest: Client } = await import('orderly-evm-connector');

          const client = new Client({
              orderly_key: ORDERLY_KEY,
              orderly_secret: ORDERLY_SECRET,
              orderly_account_id: ACCOUNT_ID,
              wallet_secret: WALLET_SECRET,
              orderly_testnet: ORDERLY_TESTNET,
              debug: true
          });

          const transfer_nonce = await get_transfer_nonce(client);
          if (!transfer_nonce) {
              return null;
          }

          const signature = await sign_internal_transfer_message(
              RECEIVER_ACCOUNT_ID,
              "USDC",
              TRANSFER_AMOUNT,
              transfer_nonce,
              CHAIN_ID,
              LEDGER_CONTRACT_ADDRESS,
              WALLET_SECRET
          );

          if (!signature) {
              return null;
          }

          return await create_internal_transfer_v2(
              client,
              RECEIVER_ACCOUNT_ID,
              "USDC",
              String(TRANSFER_AMOUNT),
              transfer_nonce,
              CHAIN_ID,
              "EVM",
              signature,
              USER_ADDRESS,
              LEDGER_CONTRACT_ADDRESS
          );
      } catch (error) {
          return null;
      }
  }

  test_internal_transfer_v2();
  ```

  ```py Python theme={null}
  from eth_account import Account
  from eth_account.messages import encode_typed_data
  from orderly_evm_connector.rest import Rest as Client
  from orderly_evm_connector.error import ClientError

  ACCOUNT_ID = "0x..."
  ENV = 'mainnet'
  ORDERLY_KEY = "ed25519:xxx"
  ORDERLY_SECRET = "ed25519:xxx"
  WALLET_SECRET = "70xxxf9"
  USER_ADDRESS = "0x..."
  CHAIN_ID = 42161 if ENV == 'mainnet' else 421614
  LEDGER_CONTRACT_ADDRESS = "0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203" if ENV == 'mainnet' else "0x1826B75e2ef249173FC735149AE4B8e9ea10abff"
  RECEIVER_ACCOUNT_ID = "0x..."
  TRANSFER_AMOUNT = 1000000
  ORDERLY_TESTNET = ENV == 'testnet'

  def get_transfer_nonce(client):
      try:
          response = client._sign_request("GET", "/v1/transfer_nonce")
          if 'data' in response and 'transfer_nonce' in response['data']:
              return response['data']['transfer_nonce']
          elif 'transfer_nonce' in response:
              return response['transfer_nonce']
          return None
      except Exception:
          return None

  def sign_internal_transfer_message(
      receiver_account_id: str,
      token: str,
      amount: int,
      transfer_nonce: int,
      chain_id: int,
      ledger_contract_address: str,
      private_key: str
  ):
      receiver_hex = receiver_account_id[2:] if receiver_account_id.startswith('0x') else receiver_account_id
      receiver_bytes = bytes.fromhex(receiver_hex)

      data = {
          "types": {
              "EIP712Domain": [
                  {"name": "name", "type": "string"},
                  {"name": "version", "type": "string"},
                  {"name": "chainId", "type": "uint256"},
                  {"name": "verifyingContract", "type": "address"},
              ],
              "InternalTransfer": [
                  {"name": "receiver", "type": "bytes32"},
                  {"name": "token", "type": "string"},
                  {"name": "amount", "type": "uint256"},
                  {"name": "transferNonce", "type": "uint64"},
              ],
          },
          "domain": {
              "name": "Orderly",
              "version": "1",
              "chainId": chain_id,
              "verifyingContract": ledger_contract_address,
          },
          "primaryType": "InternalTransfer",
          "message": {
              "receiver": receiver_bytes,
              "token": token,
              "amount": amount,
              "transferNonce": transfer_nonce,
          },
      }

      account = Account.from_key(private_key)
      signable_message = encode_typed_data(full_message=data)
      signed_message = account.sign_message(signable_message)
      return signed_message.signature.hex()

  def create_internal_transfer_v2(
      client,
      receiver_account_id: str,
      token: str,
      amount: str,
      transfer_nonce: int,
      chain_id: int,
      chain_type: str,
      signature: str,
      user_address: str,
      verifying_contract: str
  ):
      payload = {
          "message": {
              "receiver": receiver_account_id,
              "token": token,
              "amount": amount,
              "transferNonce": str(transfer_nonce),
              "chainId": str(chain_id),
              "chainType": chain_type,
          },
          "signature": signature,
          "userAddress": user_address,
          "verifyingContract": verifying_contract,
      }

      try:
          return client._sign_request("POST", "/v2/internal_transfer", payload=payload)
      except ClientError:
          return None
      except Exception:
          return None

  def test_internal_transfer_v2():
      try:
          client = Client(
              orderly_key=ORDERLY_KEY,
              orderly_secret=ORDERLY_SECRET,
              orderly_account_id=ACCOUNT_ID,
              wallet_secret=WALLET_SECRET,
              orderly_testnet=ORDERLY_TESTNET,
              debug=True
          )

          transfer_nonce = get_transfer_nonce(client)
          if not transfer_nonce:
              return None

          signature = sign_internal_transfer_message(
              receiver_account_id=RECEIVER_ACCOUNT_ID,
              token="USDC",
              amount=TRANSFER_AMOUNT,
              transfer_nonce=transfer_nonce,
              chain_id=CHAIN_ID,
              ledger_contract_address=LEDGER_CONTRACT_ADDRESS,
              private_key=WALLET_SECRET
          )

          if not signature:
              return None

          return create_internal_transfer_v2(
              client=client,
              receiver_account_id=RECEIVER_ACCOUNT_ID,
              token="USDC",
              amount=str(TRANSFER_AMOUNT),
              transfer_nonce=transfer_nonce,
              chain_id=CHAIN_ID,
              chain_type="EVM",
              signature=signature,
              user_address=USER_ADDRESS,
              verifying_contract=LEDGER_CONTRACT_ADDRESS
          )
      except Exception:
          return None

  if __name__ == "__main__":
      test_internal_transfer_v2()
  ```
</CodeGroup>
