> ## 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 1 wallet integration

> Create wallets and sign with Privy on chains with Tier 1 support

Tier 1 provides wallet creation, key export, and low-level signing. Your app builds and broadcasts
transactions using the chain's SDK or 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 chain's supported key type.
2. Build the transaction or message with the chain's SDK.
3. Produce the payload that the chain expects the wallet to sign.
4. Sign the payload with Privy's [low-level signing
   interface](/wallets/using-wallets/other-chains).
5. Attach the signature and broadcast the transaction through the chain's RPC provider.

Your app is responsible for chain-specific serialization, hashing, submission, and transaction
tracking. Include any prefixes, suffixes, or domain separators required by the chain before signing.

## Chain examples

### Bitcoin (segwit)

Bitcoin (segwit) supports the ECDSA signing algorithm using the secp256k1 curve. Use Privy's raw sign functionality to sign each input UTXO for your Bitcoin segwit transaction. For more details, see the [Bitcoin signing guide](/wallets/using-wallets/bitcoin/sign-transaction-inputs).

<Expandable title="Code example">
  ```typescript theme={"system"}
  import {p2wpkh, OutScript, getInputType, Transaction} from '@scure/btc-signer';
  import {getPrevOut} from '@scure/btc-signer/transaction.js';
  import {concatBytes} from '@scure/btc-signer/utils.js';
  import secp256k1 from 'secp256k1';

  const publicKey = "<the wallet's public key>";

  const publicKeyBuffer = Buffer.from(publicKey, 'hex');
  const tx = new Transaction({version: 1, allowLegacyWitnessUtxo: true});

  // add as many outputs as needed, in this example there is only one
  // note that the relay fee is sum(input amounts) - sum(output amounts)
  const outputAddress = '';
  const outputAmount = 0n;
  tx.addOutputAddress(outputAddress, outputAmount);

  const inputAmount = 0n;
  tx.addInput({
    txid: '', // buffer of utxo txid
    index: 0, // index of the output in the tx
    witnessUtxo: {
      amount: inputAmount, // this must match the amount of the input exactly
      script: p2wpkh(publicKeyBuffer).script
    }
  });

  for (let i = 0; i < tx.inputsLength; i++) {
    const input = tx.getInput(i);
    const inputType = getInputType(input, tx.opts.allowLegacyWitnessUtxo);
    const prevOut = getPrevOut(input);
    let script = inputType.lastScript;
    // P2WPKH sighash uses the "pkh" script for signing
    if (inputType.last.type === 'wpkh') {
      script = OutScript.encode({type: 'pkh', hash: inputType.last.hash});
    }
    const hash = tx.preimageWitnessV0(i, script, inputType.sighash, prevOut.amount);
    const signature = ''; // call Privy's raw sign function with bytesToHex(hash), returns '0x...'
    const signatureBuffer = Buffer.from(signature.slice(2), 'hex');
    // convert to DER format
    const derSig = secp256k1.signatureExport(signatureBuffer);
    tx.updateInput(
      i,
      {
        partialSig: [[publicKeyBuffer, concatBytes(derSig, new Uint8Array([inputType.sighash]))]]
      },
      true
    );
  }

  tx.finalize();
  // return tx
  ```
</Expandable>

### Bitcoin (taproot)

Bitcoin (taproot) uses the Schnorr signing algorithm (BIP-340) with the secp256k1 curve. Privy automatically applies the BIP-341 key tweak when signing with a taproot wallet, producing signatures valid for key-path spends against the wallet's P2TR output. For more details, see the [Bitcoin signing guide](/wallets/using-wallets/bitcoin/sign-transaction-inputs).

<Note>
  Privy's taproot support uses key-path spending only. The applied tweak assumes no custom Merkle
  roots or scripts, so script-path spends (e.g., custom Tapscript trees) are not supported.
</Note>

<Expandable title="Code example">
  ```typescript {skip-check} theme={"system"}
  import {p2tr} from '@scure/btc-signer/payment';
  import {Transaction, getInputType, getPrevOut} from '@scure/btc-signer/transaction';

  // The wallet's `public_key` from the Privy Wallet object (33-byte compressed secp256k1 key, hex)
  const publicKey = wallet.public_key;

  // Extract the 32-byte x-only public key (strip the compression prefix byte)
  const pubKeyBytes = Buffer.from(publicKey, 'hex');
  const xOnlyPubKey = pubKeyBytes.subarray(1);

  // Build the P2TR output for this wallet
  const taprootOutput = p2tr(xOnlyPubKey);

  const tx = new Transaction({allowLegacyWitnessUtxo: true});

  // Add outputs
  const outputAddress = '';
  const outputAmount = 0n;
  tx.addOutputAddress(outputAddress, outputAmount);

  // Add a taproot input
  const inputAmount = 0n;
  tx.addInput({
    txid: '', // buffer of UTXO txid
    index: 0, // index of the output in the funding tx
    witnessUtxo: {
      amount: inputAmount, // must match the UTXO amount exactly
      script: taprootOutput.script
    },
    tapInternalKey: xOnlyPubKey
  });

  for (let i = 0; i < tx.inputsLength; i++) {
    const input = tx.getInput(i);
    const inputType = getInputType(input, true);
    const prevOut = getPrevOut(input);

    // Compute the BIP-341 sighash (witness v1)
    const sighash = tx.preimageWitnessV1(i, [prevOut.script], inputType.sighash, [prevOut.amount]);

    const signature = ''; // call Privy's raw sign function with bytesToHex(sighash), returns '0x...'

    // Attach the 64-byte Schnorr signature as tapKeySig
    const signatureBytes = Buffer.from(signature.slice(2), 'hex');
    tx.updateInput(i, {tapKeySig: signatureBytes}, true);
  }

  // finalize() validates the Schnorr signature against the tweaked output key
  tx.finalize();
  ```
</Expandable>

### Cosmos

Cosmos utilizes the ECDSA signing algorithm with the secp256k1 curve. Below is an implementation example for signing hashes on Cosmos:

<Expandable title="Code example">
  ```typescript theme={"system"}
  import {Secp256k1, Secp256k1Signature} from '@cosmjs/crypto';

  // 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`

  // Retrieve the wallet's public key from Privy
  const publicKey = '...'; // the wallet's public key from Privy

  // Verify the signature
  const signatureBytes = Secp256k1Signature.fromFixedLength(
    Buffer.from(rawSignature.slice(2), 'hex')
  );

  const verified = await Secp256k1.verifySignature(
    signatureBytes,
    Buffer.from(hash.slice(2), 'hex'),
    Buffer.from(publicKey, 'hex')
  );
  console.log('Signature valid?', verified); // true
  ```
</Expandable>

### Ton

All wallets on Ton are smart contract accounts, and Ed25519 keypairs are used to sign transactions on behalf of the smart contracts. When creating a wallet via Privy, Privy will generate the Ed25519 keypair and predetermine the address of the wallet contract, assuming that the wallet uses `WalletContractV4` with a `workchain` of `0`. Privy will *not* deploy the contract itself; that is the responsibility of the developer.
If you'd like to deploy a different wallet contract with the same keypair, the address will be different, but the request to Privy's API will remain the same.

<Expandable title="code example of creating and signing a transfer">
  ```typescript theme={"system"}
  import {Cell, TonClient, WalletContractV4, internal} from '@ton/ton';
  import {toHex} from 'viem';

  // Create Client
  const client = new TonClient({
    endpoint: 'https://toncenter.com/api/v2/jsonRPC'
  });

  const walletId = "<wallet's wallet ID>";
  const publicKey = "<wallet's public key>";

  const trimmedPublicKey = Buffer.from(publicKey.slice(2), 'hex');
  // Create wallet contract
  let workchain = 0; // Usually you need a workchain 0
  let wallet = WalletContractV4.create({
    workchain,
    publicKey: trimmedPublicKey
  });
  let contract = client.open(wallet);

  // Create a transfer
  let seqno: number = await contract.getSeqno();
  const transfer = await contract.createTransfer({
    seqno,
    messages: [
      internal({
        value: '1',
        to: 'to_address',
        body: 'Hello world'
      })
    ],
    signer: async (msg: Cell) => {
      let hash = msg.hash();
      let signature = '...'; // call Privy's raw sign function with toHex(hash), returns '0x...'
      return Buffer.from(signature.slice(2), 'hex');
    }
  });
  ```
</Expandable>

### Starknet

On Starknet, all wallets are smart contract accounts. The wallet address is the contract address, and therefore is derived from account-specific data--namely, the account class hash, the constructor data, and the public key returned from the Privy API.
The address returned from the Privy API assumes the use of [Ready's v0.5.0 account](https://github.com/argentlabs/argent-contracts-starknet/blob/6243bcf39fac0df25cff183056a9bc8f1e15ef28/deployments/account.txt#L1) class hash and the constructor call data, as shown below in the example.
After creating a starknet wallet with Privy, STRK tokens must be sent to the address for the wallet. Then, the developer must deploy the account.

If you wish to use a different account contract than Ready 0.5.0, we suggest maintaining the address-to-Privy-wallet mapping yourself at this time and ignoring the address returned from the Privy API.

<Expandable title="code example of deploying your Starknet account and sending a transfer">
  ```typescript theme={"system"}
  import {
    RpcProvider,
    SignerInterface,
    hash,
    CallData,
    CairoOption,
    CairoOptionVariant,
    CairoCustomEnum,
    Account,
    cairo,
    TypedData,
    Signature,
    Call,
    InvocationsSignerDetails,
    DeployAccountSignerDetails,
    DeclareSignerDetails
  } from 'starknet';

  // connect RPC 0.8 provider
  const provider = new RpcProvider({
    nodeUrl: 'https://starknet-sepolia.public.blastapi.io/rpc/v0_8'
  });

  //new Argent X account v0.5.0
  const ARGENT_X_ACCOUNT_CLASS_HASH_V0_5_0 =
    '0x073414441639dcd11d1846f287650a00c60c416b9d3ba45d31c651672125b2c2';

  const publicKey = 'your public key';

  // Calculate future address of the ArgentX account
  const axSigner = new CairoCustomEnum({Starknet: {pubkey: publicKey}});
  const axGuardian = new CairoOption<unknown>(CairoOptionVariant.None);
  const AXConstructorCallData = CallData.compile({
    owner: axSigner,
    guardian: axGuardian
  });
  const AXcontractAddress = hash.calculateContractAddressFromHash(
    publicKey,
    ARGENT_X_ACCOUNT_CLASS_HASH_V0_5_0,
    AXConstructorCallData,
    0
  );

  // Use a RawSigner wrapper class around Signer. Example: https://github.com/argentlabs/argent-contracts-starknet/blob/6243bcf39fac0df25cff183056a9bc8f1e15ef28/lib/signers/signers.ts#L38
  export abstract class RawSigner extends SignerInterface {
    abstract signRaw(messageHash: string): Promise<string[]>;

    public async getPubKey(): Promise<string> {
      throw new Error('Example');
    }

    public async signMessage(
      typedDataArgument: TypedData,
      accountAddress: string
    ): Promise<Signature> {
      throw new Error('Example');
    }

    public async signTransaction(
      transactions: Call[],
      details: InvocationsSignerDetails
    ): Promise<Signature> {
      throw new Error('Example');
    }

    public async signDeployAccountTransaction(
      details: DeployAccountSignerDetails
    ): Promise<Signature> {
      throw new Error('Example');
    }

    public async signDeclareTransaction(details: DeclareSignerDetails): Promise<Signature> {
      throw new Error('Example');
    }
  }

  const account = new Account(
    provider,
    AXcontractAddress,
    new (class extends RawSigner {
      public async signRaw(messageHash: string): Promise<string[]> {
        console.log('messageHash=', messageHash);
        // Get the signature using the privy raw sign method
        const sig = '..';
        const sigWithout0x = sig.slice(2);
        const r = `0x${sigWithout0x.slice(0, 64)}`;
        const s = `0x${sigWithout0x.slice(64)}`;
        return [r, s];
      }
    })()
  );

  // The account address must hold STRK tokens to deploy the account.

  const accountDeployResult = await account.deployAccount({
    classHash: ARGENT_X_ACCOUNT_CLASS_HASH_V0_5_0,
    contractAddress: AXcontractAddress,
    constructorCalldata: AXConstructorCallData,
    addressSalt: publicKey
  });

  console.log('accountDeployResult=', accountDeployResult);

  // Transfer 1 STRK unit to your recipient address
  const STRK_TOKEN_ADDRESS = '0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d';

  const amount = cairo.uint256(1);

  // Simple transfer call using account.execute
  const transferCall = {
    contractAddress: STRK_TOKEN_ADDRESS,
    entrypoint: 'transfer',
    calldata: CallData.compile({
      recipient: 'your recipient address',
      amount: amount
    })
  };

  const result = await account.execute(transferCall);
  await provider.waitForTransaction(result.transaction_hash);
  ```
</Expandable>
