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

# Tier 2 wallet integration

> Sign decoded transactions with Privy on chains with Tier 2 support

Tier 2 adds transaction-aware signing. Privy decodes and validates supported transactions before
signing, which enables transaction-level policy controls. Your app broadcasts signed transactions
through the chain's RPC provider.

<Info>
  For the complete support model and current chain list, see the [chain support
  overview](/wallets/overview/chains).
</Info>

## Integration steps

1. [Create a wallet](/wallets/wallets/create/create-a-wallet) for the target chain.
2. Build and serialize a transaction with the chain's SDK.
3. Request a signature through the chain's supported Privy signing interface.
4. Attach the signature to the transaction.
5. Broadcast the signed transaction through the chain's RPC provider and track its status.

When configuring [policies](/controls/policies/overview), use the transaction fields and policy
methods supported by the chain's signing interface.

## Aptos example

Below is a complete example of how to create a wallet, build a transaction, sign it with Privy's `rawSign` and broadcast it onchain. We use **Aptos** here, but the pattern is the same for all chains integrated through raw signing.

<Steps>
  <Step title="Create a wallet">
    ```typescript theme={"system"}
    import {PrivyClient} from '@privy-io/node';

    const privy = new PrivyClient({
      appId: process.env.PRIVY_APP_ID,
      appSecret: process.env.PRIVY_APP_SECRET
    });

    // Create an Aptos wallet for a user
    const wallet = await privy.wallets().create({
      user_id: 'your-user-id',
      chain_type: 'aptos'
    });

    console.log('Wallet ID:', wallet.id);
    console.log('Wallet Address:', wallet.address);
    console.log('Public Key:', wallet.public_key);
    ```
  </Step>

  <Step title="Build the transaction">
    ```typescript theme={"system"}
    import {
      Aptos,
      AptosConfig,
      Network,
      AccountAddress,
      generateSigningMessageForTransaction
    } from '@aptos-labs/ts-sdk';

    // Connect to Aptos network
    const aptos = new Aptos(
      new AptosConfig({
        network: Network.MAINNET
      })
    );

    const address = AccountAddress.from(wallet.address);

    // Build a transaction (e.g., transfer APT tokens)
    const transaction = await aptos.transaction.build.simple({
      sender: address,
      data: {
        function: '0x1::coin::transfer',
        typeArguments: ['0x1::aptos_coin::AptosCoin'],
        functionArguments: [
          '0xRecipientAddress...', // recipient
          100000000 // amount in Octas (0.1 APT)
        ]
      }
    });

    // Generate the message that needs to be signed
    const message = generateSigningMessageForTransaction(transaction);
    ```
  </Step>

  <Step title="Sign with Privy">
    ```typescript theme={"system"}
    import {toHex} from 'viem';

    // Sign the transaction using Privy's raw sign endpoint
    const signatureResponse = await privy.wallets().rawSign(wallet.id, {
      params: {
        hash: toHex(message)
      }
    });

    const signature = signatureResponse as unknown as string;
    console.log('Signature:', signature);
    ```
  </Step>

  <Step title="Broadcast the transaction">
    ```typescript theme={"system"}
    import {
      AccountAuthenticatorEd25519,
      Ed25519PublicKey,
      Ed25519Signature
    } from '@aptos-labs/ts-sdk';

    // Create the authenticator with public key and signature
    const authenticator = new AccountAuthenticatorEd25519(
      new Ed25519PublicKey(wallet.public_key),
      new Ed25519Signature(signature.slice(2))
    );

    // Submit the transaction
    const pendingTransaction = await aptos.transaction.submit.simple({
      transaction,
      senderAuthenticator: authenticator
    });

    // Wait for confirmation
    const executedTransaction = await aptos.waitForTransaction({
      transactionHash: pendingTransaction.hash
    });

    console.log('Transaction hash:', executedTransaction.hash);
    console.log('Transaction status:', executedTransaction.success);
    ```
  </Step>
</Steps>

## Chain-specific implementation examples

### Sui

Sui supports multiple cryptographic schemes, with Privy's implementation utilizing the Ed25519 curve and EdDSA signing algorithm. The following example demonstrates transaction signing for Sui,
please note that the transaction bytes should be the full [intentMessage](https://docs.sui.io/concepts/transactions/transaction-auth/intent-signing):

<Expandable title="Code example">
  ```typescript theme={"system"}
  import {messageWithIntent, toSerializedSignature, PublicKey} from '@mysten/sui/cryptography';
  import {Transaction} from '@mysten/sui/transactions';
  import {verifyTransactionSignature, publicKeyFromRawBytes} from '@mysten/sui/verify';
  import {toHex} from '@mysten/sui/utils';
  import {base58} from '@scure/base';

  const tx = new Transaction();
  // ... add some transactions...
  const rawBytes = new Uint8Array(); // build transaction bytes with your configured Sui client

  const intentMessage = messageWithIntent('TransactionData', rawBytes);
  const bytes = Buffer.from(intentMessage).toString('hex');

  const address = '';
  // get public key from privy wallet and decode to Uint8Array
  const publicKey = publicKeyFromRawBytes('ED25519', base58.decode('<public key string>'));

  // Obtain the raw signature from Privy's raw_sign endpoint
  // call privy raw_sign on `bytes`, `encoding` (`hex` or `base64`) and `hash_function` (`blake2b256`) and decode as Uint8Array
  const rawSignature = new Uint8Array();

  // Create and verify the transaction signature
  const txSignature = toSerializedSignature({
    signature: rawSignature,
    signatureScheme: 'ED25519',
    publicKey
  });
  const signer = await verifyTransactionSignature(rawBytes, txSignature, {address});
  console.log(signer.toSuiAddress() === address); // true
  ```
</Expandable>

Privy's ["raw sign"](/wallets/using-wallets/other-chains) endpoint supports policy evaluation for
`field_source` of `sui_transaction_command` and `sui_transfer_objects_command` with `bytes`,
`encoding`, and `hash_function`. Configure those decoded transaction rules with the
`signTransactionBytes` policy method. Message signing policies are also supported using
`field_source` of `message` with the `signRawMessageBytes` method when signing with `hash`.
See [example of Sui policies](/controls/policies/example-policies/sui).

<Note>
  When an `amount` condition is configured on the `sui_transfer_objects_command` field\_source,
  always configure the `sui_transaction_command` to allow `MergeCoins`, `SplitCoins` and
  `TransferObjects` only. Transactions containing commands like `MakeMoveVec`, `MoveCall`,
  `Publish`, or `Upgrade` are not supported for now.
</Note>

### Tron

Tron implements the ECDSA signing algorithm using the secp256k1 curve. Privy's implementation returns 64-byte ECDSA signatures (r || s), while Tron requires 65-byte signatures that include a recovery ID (v) as the final byte.

The recovery ID is essential because a 64-byte signature could correspond to two different addresses/private keys. The 65th byte, which can be either 0x1b or 0x1c (derived from 0 or 1 plus 27, following Ethereum standards), resolves this ambiguity.

The following example demonstrates message signing and verification for Tron:

<Expandable title="code example of message signing and verification">
  ```typescript theme={"system"}
  import {TronWeb} from 'tronweb';
  import {hashMessage} from 'tronweb/utils';

  // Initialize with the wallet's Tron address
  const address = "<the wallet's tron address>";

  // Determine the recovery ID for signature verification
  const getRecoveryId = async ({message, rawSignature}: {message: string; rawSignature: string}) => {
    return (await tronWeb.trx.verifyMessageV2(message, rawSignature + '1b')) === address
      ? '1b'
      : '1c';
  };

  // Initialize TronWeb
  const tronWeb = new TronWeb({
    fullHost: 'xxx'
  });

  // Prepare and sign the message
  const message = 'Hello world';
  const hash = hashMessage(message);

  // Obtain the raw signature from Privy's raw_sign endpoint
  const rawSignature = '...'; // call privy raw_sign on `hash`

  // Verify the signature with the recovery ID
  const signerAddress = await tronWeb.trx.verifyMessageV2(
    message,
    rawSignature + (await getRecoveryId({message, rawSignature}))
  );
  console.log(signerAddress === address); // true
  ```
</Expandable>

<Expandable title="code example of sending and signing a transaction">
  ```typescript theme={"system"}
  import {TronWeb, Types} from 'tronweb';

  const tronWeb = new TronWeb({
    fullHost: 'https://api.shasta.trongrid.io'
  });

  const walletId = "<wallet's wallet ID>";

  const from = "<wallet's tron address>";
  const to = "<recipient's tron address>";
  const amount = 1;

  const tx = (await tronWeb.transactionBuilder.sendTrx(
    to,
    amount,
    from
  )) as Types.SignedTransaction<Types.TransferContract>;

  const rawTxBytes = tronWeb.utils.code.hexStr2byteArray(tx.txID);
  const rawTxHex = '0x' + tronWeb.utils.code.byteArray2hexStr(rawTxBytes);

  const signature = '...'; // call Privy's raw sign function with rawTxHex, returns '0x...'
  (tx as Types.SignedTransaction<Types.TransferContract>).signature = [signature + '1b'];
  if (tronWeb.trx.ecRecover(tx) !== from) {
    (tx as Types.SignedTransaction<Types.TransferContract>).signature = [signature + '1c'];
  }

  const result = await tronWeb.trx.sendRawTransaction(tx);
  console.log('result', result);
  ```
</Expandable>

Privy's ["raw sign"](/wallets/using-wallets/other-chains) endpoint supports policy evaluation for
`TransferContract` and `TriggerSmartContract` transactions with `bytes`, `encoding`, and
`hash_function`. Configure those decoded transaction rules with the `signTransactionBytes` policy
method. Use the `signRawMessageBytes` policy method for unparsed raw signing requests that evaluate
only system conditions. For structured Tron RPC transaction requests, it's preferable to configure
`tron_signTransaction` or `tron_sendTransaction` policy rules instead. See
[example of Tron policies](/controls/policies/example-policies/tron).

### Stellar

Stellar implements the EdDSA signing algorithm using the Ed25519 curve. The following example demonstrates hash signing for Stellar transactions:

<Expandable title="Code example">
  ```typescript theme={"system"}
  import {Keypair} from '@stellar/stellar-sdk';

  // Initialize with the wallet's Stellar address
  const address = "<the wallet's stellar address>";
  const keypair = Keypair.fromPublicKey(address);

  // Prepare the hash for signing
  const hash = '0x6503b027a625549f7be691646404f275f149d17a119a6804b855bac3030037aa';

  // Obtain the raw signature from Privy's raw_sign endpoint
  const rawSignature = '...'; // call privy raw_sign on `hash`

  // Verify the signature
  const hashBytes = Buffer.from(hash.slice(2), 'hex');
  const signatureBytes = Buffer.from(rawSignature.slice(2), 'hex');
  const verified = keypair.verify(hashBytes, signatureBytes);
  console.log(verified); // true
  ```
</Expandable>

### Aptos

Aptos is a Move VM chain which uses ed25519 keypairs for signing transactions. Below is an example of how to sign and send a transaction using Privy. See more developer docs [here](https://aptos.dev/build/sdks/ts-sdk/building-transactions).

<Expandable title="code example of signing and sending a transaction">
  ```typescript theme={"system"}
  import {
    Aptos,
    AptosConfig,
    Network,
    AccountAddress,
    AccountAuthenticatorEd25519,
    Ed25519PublicKey,
    Ed25519Signature,
    generateSigningMessageForTransaction
  } from '@aptos-labs/ts-sdk';
  import {toHex} from 'viem';

  // 1) Wire up the client for the chain
  const aptos = new Aptos(
    new AptosConfig({
      network: Network.MAINNET
    })
  );
  const walletId = '<wallet ID from Privy>';
  const publicKey = '<public key of wallet>'; // 32-byte ed25519 public key hex
  const address = AccountAddress.from('<wallet address>');

  // 2) Build the raw transaction (SDK fills in seq#, chainId, gas if you let it)
  const rawTxn = await aptos.transaction.build.simple({
    sender: address,
    data: {
      function: '0x1::coin::transfer',
      typeArguments: ['0x1::aptos_coin::AptosCoin'],
      functionArguments: ['<recipient address>', 1] // amount in Octas
    }
  });

  const message = generateSigningMessageForTransaction(rawTxn);

  const signature = '...'; // call Privy's raw sign function with txHash, returns '0x...'

  // 5) Wrap pk + signature in an authenticator and submit
  const senderAuthenticator = new AccountAuthenticatorEd25519(
    new Ed25519PublicKey(publicKey),
    new Ed25519Signature(signature.slice(2))
  );

  const pending = await aptos.transaction.submit.simple({
    transaction: rawTxn,
    senderAuthenticator
  });

  const executed = await aptos.waitForTransaction({
    transactionHash: pending.hash
  });
  console.log('Executed:', executed.hash);
  ```
</Expandable>

### Near

With Privy, you can create [Near-implicit accounts](https://docs.near.org/protocol/account-model) and sign over arbitrary data. Below is an example of how to create, sign, and send a Near transaction using Privy. (Note that Near requires accounts to be funded sending transactions.)

<Expandable title="code example of creating, signing, and sending a transaction">
  ```typescript theme={"system"}
  import {
    JsonRpcProvider,
    baseDecode,
    PublicKey,
    parseNearAmount,
    actions,
    createTransaction,
    SignedTransaction,
    Signature
  } from 'near-api-js';
  import {sha256} from '@noble/hashes/sha256';
  import {base58} from '@scure/base';
  import {toHex} from 'viem';

  const nodeUrl = 'https://rpc.mainnet.near.org';
  const provider = new JsonRpcProvider({url: nodeUrl});
  const receiverId = 'receiver.near';
  const amount = '1.5';
  const nonce = 0; // If this is not the wallet's first transaction, set as current nonce

  const {
    header: {hash}
  } = await provider.viewBlock({finality: 'final'});
  const blockHash = baseDecode(hash);

  const accountId = "<wallet's near-implicit address / account ID>";

  const base58PublicKey = base58.encode(Buffer.from(accountId, 'hex'));
  const publicKey = PublicKey.fromString(`ed25519:${base58PublicKey}`);

  const amountYocto = parseNearAmount(Number(amount));
  const transferActions = [actions.transfer(BigInt(amountYocto ?? 0))];
  const tx = createTransaction(accountId, publicKey, receiverId, nonce, transferActions, blockHash);

  const serializedTx = tx.encode();

  const txHash = toHex(sha256(serializedTx));

  const signature = '...'; // call Privy's raw sign function with txHash, returns '0x...'

  const signedTx = new SignedTransaction({
    transaction: tx,
    signature: new Signature({
      keyType: tx.publicKey.keyType,
      data: Uint8Array.from(Buffer.from(signature.slice(2), 'hex'))
    })
  });

  const signedSerializedTx = signedTx.encode();
  const result = await provider.sendJsonRpc('broadcast_tx_commit', [
    Buffer.from(signedSerializedTx).toString('base64')
  ]);
  ```
</Expandable>

### Movement

Movement is a Move VM chain that uses the Aptos chain standards. Below is an example of how to sign and send a transaction using Privy. See more developer docs [here](https://docs.movementnetwork.xyz/devs/interactonchain/wallet-adapter/aptos_wallet_standard#13-supporting-multiple-accounts-the-advanced-route).

<Expandable title="code example of signing and sending a transaction">
  ```typescript theme={"system"}
  import {
    Aptos,
    AptosConfig,
    Network,
    AccountAddress,
    AccountAuthenticatorEd25519,
    Ed25519PublicKey,
    Ed25519Signature,
    generateSigningMessageForTransaction
  } from '@aptos-labs/ts-sdk';
  import {toHex} from 'viem';
  import {PrivyClient} from '@privy-io/node';

  // Initialize your Privy client
  const privy = new PrivyClient({
    appId: 'your-privy-app-id',
    appSecret: 'your-privy-app-secret'
  });

  // 1) Wire up the client for the Movement chain
  const aptos = new Aptos(
    new AptosConfig({
      network: Network.TESTNET,
      fullnode: 'https://full.testnet.movementinfra.xyz/v1'
    })
  );
  const walletId = '<wallet ID from Privy>';
  const publicKey = '<public key of wallet>'; // 32-byte ed25519 public key hex
  const address = AccountAddress.from('<wallet address>');

  // 2) Build the raw transaction (SDK fills in seq#, chainId, gas if you let it)
  const rawTxn = await aptos.transaction.build.simple({
    sender: address,
    data: {
      function: '0x1::coin::transfer',
      typeArguments: ['0x1::aptos_coin::AptosCoin'],
      functionArguments: ['<recipient address>', 1] // amount in Octas
    }
  });

  const message = generateSigningMessageForTransaction(rawTxn);
  const signatureResponse = await privy.wallets().rawSign(walletId, {params: {hash: toHex(message)}});
  const signature = signatureResponse as unknown as string;

  // 5) Wrap pk + signature in an authenticator and submit
  const senderAuthenticator = new AccountAuthenticatorEd25519(
    new Ed25519PublicKey(publicKey),
    new Ed25519Signature(signature.slice(2))
  );

  const pending = await aptos.transaction.submit.simple({
    transaction: rawTxn,
    senderAuthenticator
  });

  const executed = await aptos.waitForTransaction({
    transactionHash: pending.hash
  });
  console.log('Executed:', executed.hash);
  ```
</Expandable>
