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

# Sign the Transaction Offline

> This guide covers some crucial details on the offline transaction signing flow, such as new extended request parameter with additional metadata.

For Polkadot signing instructions, refer to the [transaction signing](/docs/signing-transaction-offline-polkadot#offline-transaction-signing) section.

## Extended metadata

All Polkadot endpoints generating staking transactions now support an optional boolean parameter `extended`.

When it is set to `true`, the response includes additional metadata, which can be required for offline transaction signing and external processing.

### Examples

Example request with `extended: true` to [/api/v1/polkadot/\{network}/staking/bond](/reference/polkadot-staking-bond):

<CodeGroup>
  ```bash curl theme={null}
  curl --request POST \
  --url https://api.p2p.org/api/v1/polkadot/westend/staking/bond \
  --header 'accept: application/json' \
  --header 'authorization: Bearer <token>' \
  --header 'content-type: application/json' \
  --data '
  {
  "stashAccountAddress": "5Eh1C69jwNgBPtp2oKxJ7Zy5dS6LJn3XZ6CJHPwcAB2iFpZQ",
  "rewardDestinationType": "account",
  "rewardDestination": "5Eh1C69jwNgBPtp2oKxJ7Zy5dS6LJn3XZ6CJHPwcAB2iFpZQ",
  "amount": 1,
  "extended": true
  }'
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch("https://api.p2p.org/api/v1/polkadot/westend/staking/bond", {
    method: "POST",
    headers: {
      "Authorization": "Bearer <token>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      stashAccountAddress: "5Eh1C69jwNgBPtp2oKxJ7Zy5dS6LJn3XZ6CJHPwcAB2iFpZQ",
      rewardDestinationType: "account",
      rewardDestination: "5Eh1C69jwNgBPtp2oKxJ7Zy5dS6LJn3XZ6CJHPwcAB2iFpZQ",
      amount: 1,
      extended: true,
    }),
  });
  const data = await response.json();
  console.log(data);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.p2p.org/api/v1/polkadot/westend/staking/bond",
      headers={
          "Authorization": "Bearer <token>",
          "Content-Type": "application/json",
      },
      json={
          "stashAccountAddress": "5Eh1C69jwNgBPtp2oKxJ7Zy5dS6LJn3XZ6CJHPwcAB2iFpZQ",
          "rewardDestinationType": "account",
          "rewardDestination": "5Eh1C69jwNgBPtp2oKxJ7Zy5dS6LJn3XZ6CJHPwcAB2iFpZQ",
          "amount": 1,
          "extended": True,
      },
  )
  print(response.json())
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"io"
  	"net/http"
  )

  func main() {
  	payload := map[string]interface{}{
  		"stashAccountAddress":   "5Eh1C69jwNgBPtp2oKxJ7Zy5dS6LJn3XZ6CJHPwcAB2iFpZQ",
  		"rewardDestinationType": "account",
  		"rewardDestination":     "5Eh1C69jwNgBPtp2oKxJ7Zy5dS6LJn3XZ6CJHPwcAB2iFpZQ",
  		"amount":                1,
  		"extended":              true,
  	}
  	body, _ := json.Marshal(payload)
  	req, _ := http.NewRequest("POST", "https://api.p2p.org/api/v1/polkadot/westend/staking/bond", bytes.NewBuffer(body))
  	req.Header.Set("Authorization", "Bearer <token>")
  	req.Header.Set("Content-Type", "application/json")
  	resp, _ := http.DefaultClient.Do(req)
  	defer resp.Body.Close()
  	respBody, _ := io.ReadAll(resp.Body)
  	fmt.Println(string(respBody))
  }
  ```
</CodeGroup>

Example response:

<CodeGroup>
  ```json theme={null}
  {
        "error": null,
        "result": {
            "unsignedTransaction": "0xa8040600070010a5d4e8037427a7dee592c47c6875266357ee7afd763ddfdf6bd1634e410b7335fc38331e",
            "unsignedTransactionSerialized": "7b2261646472657373223a22354568314336396a774e6742507470326f4b784a375a79356453364c4a6e33585a36434a485077634142326946705a51222c22626c6f636b48617368223a22307835333039616433373261623530396633363331666664666163633130633066386433396138303339616664666432353265393564653263336636653064306236222c22626c6f636b4e756d626572223a2230783031383734346262222c22657261223a22307862353033222c2267656e6573697348617368223a22307865313433663233383033616335306538663666386536323639356431636539653465316436386161333663316364326366643135333430323133663334323365222c226d65746164617461527063223a223078222c226d6574686f64223a22307830363030303730303130613564346538303337343237613764656535393263343763363837353236363335376565376166643736336464666466366264313633346534313062373333356663333833333165222c226e6f6e6365223a2230783030303030303136222c227369676e6564457874656e73696f6e73223a5b22436865636b4e6f6e5a65726f53656e646572222c22436865636b5370656356657273696f6e222c22436865636b547856657273696f6e222c22436865636b47656e65736973222c22436865636b4d6f7274616c697479222c22436865636b4e6f6e6365222c22436865636b576569676874222c224368617267655472616e73616374696f6e5061796d656e74222c22436865636b4d6574616461746148617368222c225765696768745265636c61696d225d2c227370656356657273696f6e223a2230783030306638383932222c22746970223a2230783030303030303030303030303030303030303030303030303030303030303030222c227472616e73616374696f6e56657273696f6e223a2230783030303030303162222c2276657273696f6e223a347d",
            "unsignedTransactionPayload": "0x0600070010a5d4e8037427a7dee592c47c6875266357ee7afd763ddfdf6bd1634e410b7335fc38331e",
            "unsignedTransactionObject": {
                "blockHash": "0x339985858981806fa80352e0ea533f94e5f92855296aa24b63b00fee7e40a448",
                "eraPeriod": 64,
                "currentEra": 9186,
                "genesisHash": "0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e",
                "metadataRpc": "0x",
                "method": {
                    "args": {
                        "value": "1,000,000,000,000",
                        "payee": {
                            "Account": "5Eh1C69jwNgBPtp2oKxJ7Zy5dS6LJn3XZ6CJHPwcAB2iFpZQ"
                        }
                    },
                    "method": "bond",
                    "section": "staking"
                },
                "nonce": 22,
                "specVersion": 1018002,
                "transactionVersion": 27,
                "tip": 0
            },
            "stashAccountAddress": "5Eh1C69jwNgBPtp2oKxJ7Zy5dS6LJn3XZ6CJHPwcAB2iFpZQ",
            "rewardDestinationType": "account",
            "rewardDestination": "5Eh1C69jwNgBPtp2oKxJ7Zy5dS6LJn3XZ6CJHPwcAB2iFpZQ",
            "amount": 1,
            "createdAt": "2025-04-17T13:25:14.508Z"
        }
    }
  ```
</CodeGroup>

* `unsignedTransaction` — <Tooltip tip="A transaction that must be signed and broadcasted to the blockchain network.">unsigned transaction</Tooltip> in the hexadecimal format.

* `unsignedTransactionSerialized` — unsigned serialized transaction containing all the data.

* `unsignedTransactionPayload` — only raw payload of the unsigned transaction in the hexadecimal format.

* `unsignedTransactionObject` — full decoded transaction structure with all the fields and metadata:

  * `blockHash`— hash of the checkpoint block in which the transaction was included.
  * `eraPeriod` — validity period of the transaction, representing the number of blocks following after the checkpoint for which the transaction is valid.
  * `currentEra` — current staking era of the transaction.
  * `genesisHash` — hash of the genesis block.
  * `metadataRpc` — serialized metadata used for offline decoding and transaction signing.
  * `method` is the list of data fields containing information on the method called to construct a transaction.
  * `nonce` — nonce of the transaction.
  * `specVersion` — current version of the chain specification for the runtime.
  * `transactionVersion` — current version of the transaction format.
  * `tip` — optional fee used to increase the transaction priority.

* `stashAccountAddress` — main stash account address which keeps tokens for bonding.

* `rewardDestinationType` — rewards destination type:

  * `staked` — rewards will be sent to the stash account and added to the current bond (compounding rewards).
  * `stash` — rewards will be sent to the stash account as a transferrable balance (not compounding rewards).
  * `account` — rewards will be sent to any account specified as a transferrable balance.

* `rewardDestination` — rewards destination account address.

* `amount` — amount of tokens to bond. DOT is used for the main network, KSM for Kusama, and WND for Westend.

* `createdAt` — timestamp of the transaction in the ISO 8601 format.

### Supported endpoints

The `extended` parameter is available for the following endpoints:

* [/staking/bond](/reference/polkadot-staking-bond)
* [/staking/nominate](/reference/polkadot-staking-nominate)
* [/staking/pool/bond](/reference/polkadot-pool-bond)
* [/staking/pool/set-claim-permission](/reference/polkadot-pool-set-claim-permission)
* [/staking/pool/claim-payout](/reference/polkadot-pool-claim-payout)
* [/account/add](/reference/polkadot-account-add)

## Offline transaction signing

To sign the transaction offline, follow these steps:

1. Prepare the `unsignedTransactionSerialized` object.

2. Sign the transaction in your preferred way of signing.

   Example (for `westend` network):

<CodeGroup>
  ```javascript theme={null}
  /*
    Instructions:
    1. Install Sidecar and run with the environment variable for Substrate URL:
   yarn global add @substrate/api-sidecar
       SAS_SUBSTRATE_URL=wss://westend-ws-proxy.polka.p2p.world substrate-api-sidecar
    2. Install dependencies:
       yarn install
    3. Run the script:
       npx ts-node test.ts
  */
  const API_BASE_URL = 'https://api.p2p.org';
  const TOKEN = '*******';
  const WS_PROVIDER_URL = 'wss://westend-ws-proxy.polka.p2p.world';
  const SECRET_FILE_PATH = 'secret.json';
  const TEST_PASSWORD = 'testtest';
  const SIDECAR_URL = 'http://localhost:8080/transaction';
  const STASH_ACCOUNT_ADDRESS =
    '5GW6GmWBPhbpWPwRkria91Dn8EjTxEoqVorZxE9gkvTCHVN8';

  import { readFileSync } from 'fs';
  import { Keyring, WsProvider } from '@polkadot/api';
  import { cryptoWaitReady } from '@polkadot/util-crypto';
  import {
    construct,
    getRegistry,
    KeyringPair,
    UnsignedTransaction,
  } from '@substrate/txwrapper-polkadot';
  import { ApiPromise } from '@polkadot/api/cjs/bundle';

  interface ApiResponse {
    result: {
      unsignedTransactionSerialized: string;
    };
  }

  interface TransactionResponse {
    hash: string;
  }

  async function getBondTx(data: any): Promise<ApiResponse> {
    return fetchData(
      `${API_BASE_URL}/api/v1/polkadot/westend/staking/bond`,
      data
    );
  }

  async function getBondExtraTx(data: any): Promise<ApiResponse> {
    return fetchData(
      `${API_BASE_URL}/api/v1/polkadot/westend/staking/bond-extra`,
      data
    );
  }

  async function fetchData(url: string, data: any): Promise<ApiResponse> {
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json',
          Authorization: 'Bearer ' + TOKEN,
        },
        body: JSON.stringify(data),
      });
      return (await response.json()) as ApiResponse;
    } catch (error) {
      console.error('Error fetching data:', error);
      throw error;
    }
  }

  async function fetchMetadata(): Promise<string> {
    const provider = new WsProvider(WS_PROVIDER_URL);
    const api = await ApiPromise.create({ provider });

    const metadataRpc = await api.rpc.state.getMetadata();
    await api.disconnect();

    return metadataRpc.toHex();
  }

  function createKeypair(): KeyringPair {
    const keyring = new Keyring({ type: 'sr25519' });
    const keyInfo = JSON.parse(readFileSync(SECRET_FILE_PATH, 'utf8'));
    const sender = keyring.addFromJson(keyInfo);
    sender.decodePkcs8(TEST_PASSWORD);

    return sender;
  }

  function signTransaction(unsignTx: string, metadataRpc: `0x{string}`): string {
    const keypair = createKeypair();
    const registry = getRegistry({
      chainName: 'Polkadot',
      specName: 'westend',
      specVersion: 1018001,
      metadataRpc,
    });

    const serialized = unsignTx;
    const jsonString = Buffer.from(serialized, 'hex').toString('utf-8');
    const unsigned = JSON.parse(jsonString);

    const extrinsicPayload = registry.createType('ExtrinsicPayload', unsigned, {
      version: unsigned.version,
    });

    const signature = extrinsicPayload.sign(keypair).signature;

    const rawT = construct.signedTx(
      unsigned as unknown as UnsignedTransaction,
      signature,
      {
        metadataRpc,
        registry,
        userExtensions: { SetEvmOrigin: { payload: {}, extrinsic: {} } },
      }
    );
    return rawT;
  }

  async function sendTransaction(
    transactionData: string
  ): Promise<TransactionResponse> {
    try {
      const response = await fetch(SIDECAR_URL, {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ tx: transactionData }),
      });
      return (await response.json()) as TransactionResponse;
    } catch (error) {
      console.error('Error sending transaction:', error);
      throw error;
    }
  }

  void (async () => {
    await cryptoWaitReady();

    // Get Bond Transaction
    const unsignTx = await getBondTx({
      stashAccountAddress: STASH_ACCOUNT_ADDRESS,
      rewardDestinationType: 'account',
      rewardDestination: STASH_ACCOUNT_ADDRESS,
      amount: 1,
      extended: true,
    });
    /*
    // Get Bond Extra Transaction
    const unsignTx = await getBondExtraTx({
      stashAccountAddress: STASH_ACCOUNT_ADDRESS,
      amount: 0.001,
      extended: true,
    });
    */
    console.log('unsignTx', unsignTx);

    // Fetch Metadata
    const metadataRpc = await fetchMetadata();

    // Sign Transaction
    const signTx = signTransaction(
      unsignTx.result.unsignedTransactionSerialized,
      metadataRpc as `0x{string}`
    );
    console.log('signTx', signTx);

    // Send Signed Transaction
    const result = await sendTransaction(signTx);
    console.log('result', result);
  })();
  ```
</CodeGroup>


## Related topics

- [Getting Started](/docs/staking-polkadot.md)
- [Withdrawal](/docs/withdrawal-polkadot.md)
- [Staking API reference](/reference/polkadot-staking-bond.md)
