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

# Signing

> Learn how to sign transactions and operations with OneBalance across different account types and blockchains

OneBalance uses different signing patterns depending on your account type and target blockchain. This guide covers all signing methods with complete code examples.

## Overview

OneBalance transactions require signing operations to authorize spending and execute actions. The signing process varies by:

* **Account type**: Role-based, Kernel 3.3 (EIP-7702), or native Solana accounts
* **Operation type**: Token transfers, contract calls, or cross-chain operations
* **Target blockchain**: EVM chains use different patterns than Solana

<Info>
  All OneBalance operations require proper signing before execution. The API provides structured data that must be signed using the correct method for your account type.
</Info>

## Account Types and Signing Methods

<CardGroup cols={3}>
  <Card title="Role-Based Accounts" icon="signature">
    Sign EIP-712 typed data using `signTypedData()` method
  </Card>

  <Card title="Kernel 3.3 Accounts (EIP-7702)" icon="key">
    Sign UserOperation hash using `signMessage()` method
  </Card>

  <Card title="Solana Accounts" icon="signature">
    Sign versioned transactions using wallet or private key
  </Card>
</CardGroup>

## TypeScript Types

<Tabs>
  <Tab title="Core Types">
    Essential types for implementing signing functionality:

    ```typescript theme={null}
    import { HashTypedDataParameters } from 'viem';

    export type Hex = `0x${string}`;

    export enum ContractAccountType {
      RoleBased = 'role-based',
      KernelV31 = 'kernel-v3.1-ecdsa',
      KernelV33 = 'kernel-v3.3-ecdsa',
      Solana = 'solana',
    }

    export interface ChainOperation {
      userOp: SerializedUserOperation;
      typedDataToSign: HashTypedDataParameters;
      assetType: string;
      amount: string;
      delegation?: Delegation;
    }
    ```
  </Tab>

  <Tab title="EIP-7702 Types">
    Types specific to EIP-7702 delegation signing:

    ```typescript theme={null}
    export enum DelegationSignatureType {
      Signed = 'Signed',
      Unsigned = 'Unsigned',
    }

    export interface DelegationSignature {
      chainId: number;
      contractAddress: Hex;
      nonce: number;
      r: Hex;
      s: Hex;
      v: Hex;
      yParity: number;
      type: DelegationSignatureType;
    }

    export interface Delegation {
      contractAddress: Hex;
      nonce: number;
      signature?: DelegationSignature;
    }
    ```
  </Tab>

  <Tab title="UserOperation Types">
    Types for UserOperation handling (Kernel 3.1/3.3 accounts):

    ```typescript theme={null}
    export interface SerializedUserOperation {
      sender: Hex;
      nonce: string;
      factory?: Hex;
      factoryData?: Hex;
      callData: Hex;
      callGasLimit: string;
      verificationGasLimit: string;
      preVerificationGas: string;
      maxFeePerGas: string;
      maxPriorityFeePerGas: string;
      paymaster?: Hex;
      paymasterVerificationGasLimit?: string;
      paymasterPostOpGasLimit?: string;
      paymasterData?: Hex;
      signature: Hex;
      initCode?: Hex;
      paymasterAndData?: Hex;
    }
    ```
  </Tab>

  <Tab title="Solana Types">
    Types specific to Solana operations:

    ```typescript theme={null}
    export interface SolanaOperation {
      type: 'solana';
      dataToSign: string; // Base64 encoded message data
      signature: string; // Base58 encoded signature (initially "0x")
      accountAddress: string;
      assetType: string;
      amount: string;
    }

    export interface SolanaAccount {
      type: 'solana';
      accountAddress: string;
    }

    export interface QuoteV3 {
      id: string;
      account?: EvmAccount | SolanaAccount;
      originChainsOperations: (ChainOperation | SolanaOperation)[];
      destinationChainOperation?: ChainOperation;
      validUntil?: string;
      validAfter?: string;
      expirationTimestamp: string;
      tamperProofSignature: string;
    }
    ```
  </Tab>

  <Tab title="Account & Quote Types">
    Account and quote-related types:

    ```typescript theme={null}
    export interface EvmAccount {
      type: 'kernel-v3.3-ecdsa';
      deploymentType: 'EIP7702';
      accountAddress: Hex;
      signerAddress: Hex; // Same as accountAddress for 7702
    }

    export interface Quote {
      id: string;
      account: EvmAccount;
      originChainsOperations: ChainOperation[];
      destinationChainOperation?: ChainOperation;
      validUntil?: string;
      validAfter?: string;
      expirationTimestamp: string;
      tamperProofSignature: string;
    }

    export type OperationStatus =
      | 'PENDING'
      | 'IN_PROGRESS' 
      | 'COMPLETED'
      | 'REFUNDED'
      | 'FAILED';
    ```
  </Tab>
</Tabs>

## EVM Account Signing

### Role-Based Account Signing

Role-based accounts use EIP-712 typed data signing for all operations:

<Tabs>
  <Tab title="Using Viem">
    ```typescript theme={null}
    import { createWalletClient, custom } from 'viem';
    import { privateKeyToAccount } from 'viem/accounts';

    // Using private key
    const account = privateKeyToAccount('0x...' as Hex);
    const signature = await account.signTypedData(operation.typedDataToSign);

    // Using browser wallet
    const walletClient = createWalletClient({
      transport: custom(window.ethereum),
      account: '0x...'
    });
    const signature = await walletClient.signTypedData(operation.typedDataToSign);
    ```
  </Tab>

  <Tab title="Using Privy">
    ```typescript theme={null}
    import { createWalletClient, custom } from 'viem';
    import { ConnectedWallet } from '@privy-io/react-auth';

    export const signTypedDataWithPrivy = (embeddedWallet: ConnectedWallet) =>
      async (typedData: any) => {
        const provider = await embeddedWallet.getEthereumProvider();
        const walletClient = createWalletClient({
          transport: custom(provider),
          account: embeddedWallet.address as Address,
        });
        
        return walletClient.signTypedData(typedData);
      };
    ```
  </Tab>
</Tabs>

### EIP-7702 Account Signing

EIP-7702 specifically uses Kernel 3.3 accounts which require signing the UserOperation hash, not typed data:

<Warning>
  **Note**: Kernel 3.3 accounts (EIP-7702) must sign UserOperation hash using `signMessage()`, not typed data. Using the wrong signing method will cause transaction failures.
</Warning>

```typescript theme={null}
import { privateKeyToAccount } from 'viem/accounts';
import { entryPoint07Address, getUserOperationHash } from 'viem/account-abstraction';

function deserializeUserOp(userOp: SerializedUserOperation): UserOperation<'0.7'> {
  return {
    sender: userOp.sender,
    nonce: BigInt(userOp.nonce),
    factory: userOp.factory,
    factoryData: userOp.factoryData,
    callData: userOp.callData,
    callGasLimit: BigInt(userOp.callGasLimit),
    verificationGasLimit: BigInt(userOp.verificationGasLimit),
    preVerificationGas: BigInt(userOp.preVerificationGas),
    maxFeePerGas: BigInt(userOp.maxFeePerGas),
    maxPriorityFeePerGas: BigInt(userOp.maxPriorityFeePerGas),
    paymaster: userOp.paymaster,
    paymasterVerificationGasLimit: userOp.paymasterVerificationGasLimit
      ? BigInt(userOp.paymasterVerificationGasLimit)
      : undefined,
    paymasterPostOpGasLimit: userOp.paymasterPostOpGasLimit 
      ? BigInt(userOp.paymasterPostOpGasLimit) 
      : undefined,
    paymasterData: userOp.paymasterData,
    signature: userOp.signature,
  };
}

async function signKernel33Operation(
  operation: ChainOperation,
  privateKey: Hex
): Promise<ChainOperation> {
  const signerAccount = privateKeyToAccount(privateKey);
  const chainId = Number(operation.typedDataToSign.domain.chainId);
  
  // Handle delegation signing for EIP-7702 if needed
  if (operation.delegation) {
    const authTuple = {
      contractAddress: operation.delegation.contractAddress,
      nonce: operation.delegation.nonce,
      chainId: chainId,
    };
    const signedTuple = await signerAccount.signAuthorization(authTuple);
    
    if (signedTuple.yParity == null) {
      throw new Error('Y parity is required');
    }
    
    operation.delegation.signature = {
      chainId: chainId,
      contractAddress: signedTuple.address,
      nonce: signedTuple.nonce,
      r: signedTuple.r,
      s: signedTuple.s,
      v: `0x${Number(signedTuple.v).toString(16).padStart(2, '0')}` as Hex,
      yParity: signedTuple.yParity,
      type: 'Signed',
    };
  }
  
  // Sign UserOperation hash for Kernel accounts
  const deserializedUserOp = deserializeUserOp(operation.userOp);
  const userOpHash = getUserOperationHash<'0.7'>({
    userOperation: deserializedUserOp,
    entryPointAddress: entryPoint07Address,
    entryPointVersion: '0.7',
    chainId: chainId,
  });
  
  return {
    ...operation,
    userOp: { 
      ...operation.userOp, 
      signature: await signerAccount.signMessage({ message: { raw: userOpHash } }) 
    },
  };
}
```

### Universal EVM Signing Function

Here's a function that handles both account types automatically:

```typescript theme={null}
import { privateKeyToAccount } from 'viem/accounts';

enum ContractAccountType {
  RoleBased = 'role-based',
  KernelV31 = 'kernel-v3.1-ecdsa',
  KernelV33 = 'kernel-v3.3-ecdsa'
}

async function signOperation(
  operation: ChainOperation,
  privateKey: Hex,
  accountType: ContractAccountType = ContractAccountType.RoleBased,
): Promise<ChainOperation> {
  const signerAccount = privateKeyToAccount(privateKey);
  
  // Kernel 3.1 and 3.3 accounts sign UserOperation hash
  if (accountType === ContractAccountType.KernelV31 || 
      accountType === ContractAccountType.KernelV33) {
    
    if (!operation.userOp || !operation.typedDataToSign?.domain?.chainId) {
      throw new Error('UserOperation and Chain ID are required for Kernel signing.');
    }
    
    const chainId = Number(operation.typedDataToSign.domain.chainId);
    
    // Handle delegation signing for EIP-7702
    if (operation.delegation) {
      const authTuple = {
        contractAddress: operation.delegation.contractAddress,
        nonce: operation.delegation.nonce,
        chainId: chainId,
      };
      const signedTuple = await signerAccount.signAuthorization(authTuple);
      
      if (signedTuple.yParity == null) {
        throw new Error('Y parity is required');
      }
      
      operation.delegation.signature = {
        chainId: chainId,
        contractAddress: signedTuple.address,
        nonce: signedTuple.nonce,
        r: signedTuple.r,
        s: signedTuple.s,
        v: `0x${Number(signedTuple.v).toString(16).padStart(2, '0')}` as Hex,
        yParity: signedTuple.yParity,
        type: 'Signed',
      };
    }
    
    // Sign UserOperation hash for Kernel 3.1/3.3 accounts
    const deserializedUserOp = deserializeUserOp(operation.userOp);
    const userOpHash = getUserOperationHash<'0.7'>({
      userOperation: deserializedUserOp,
      entryPointAddress: entryPoint07Address,
      entryPointVersion: '0.7',
      chainId: chainId,
    });
    
    return {
      ...operation,
      userOp: { 
        ...operation.userOp, 
        signature: await signerAccount.signMessage({ message: { raw: userOpHash } }) 
      },
    };
  }
  
  // Role-based accounts sign typed data
  if (!operation.typedDataToSign) {
    throw new Error('TypedData is required for role-based account signing.');
  }
  
  return {
    ...operation,
    userOp: { 
      ...operation.userOp, 
      signature: await signerAccount.signTypedData(operation.typedDataToSign) 
    },
  };
}
```

## Solana Account Signing

Solana uses a different transaction structure and signing process than EVM chains:

<Info>
  **Solana vs EVM**: Solana operations provide `dataToSign` as base64-encoded message data, not typed data structures. This data must be deserialized into a VersionedTransaction for signing.
</Info>

### Browser Wallet Signing

For browser wallets like Phantom or Solflare:

```typescript theme={null}
import { MessageV0, VersionedTransaction } from '@solana/web3.js';
import bs58 from 'bs58';

/**
 * Signs a Solana operation using a browser wallet
 * @param dataToSign - Base64 encoded data from quote response
 * @param wallet - Connected Solana wallet (Phantom, Solflare, etc.)
 * @returns Base58 encoded signature
 */
async function signSolanaOperation(dataToSign: string, wallet: any): Promise<string> {
  try {
    // 1. Convert base64 data to message buffer
    const msgBuffer = Buffer.from(dataToSign, 'base64');

    // 2. Deserialize into MessageV0
    const message = MessageV0.deserialize(msgBuffer);

    // 3. Create versioned transaction
    const transaction = new VersionedTransaction(message);

    // 4. Sign with wallet
    const signedTx = await wallet.signTransaction(transaction);

    // 5. Extract signature and encode as base58
    const signature = bs58.encode(Buffer.from(signedTx.signatures[signedTx.signatures.length - 1]));

    return signature;
  } catch (error) {
    console.error('Error signing Solana operation:', error);
    throw new Error(`Failed to sign Solana transaction: ${error}`);
  }
}
```

### Private Key Signing

For server-side operations or direct private key access:

```typescript theme={null}
import { MessageV0, VersionedTransaction, PublicKey } from '@solana/web3.js';
import bs58 from 'bs58';

/**
 * Signs a Solana chain operation with a private key
 * @param accountAddress - The address of the account to sign the chain operation
 * @param privateKey - The private key in base58 format
 * @param chainOp - The chain operation object from quote response
 * @returns The signed chain operation with signature added
 */
export function signSolanaChainOperation(
  accountAddress: string,
  privateKey: string,
  chainOp: SolanaOperation,
): SolanaOperation {
  const msgBuffer = Buffer.from(chainOp.dataToSign, 'base64');
  
  const message = MessageV0.deserialize(msgBuffer);
  
  const transaction = new VersionedTransaction(message);
  
  const decodedKey = bs58.decode(privateKey);
  transaction.sign([
    {
      publicKey: new PublicKey(accountAddress),
      secretKey: Buffer.from(decodedKey),
    },
  ]);
  
  const signature = bs58.encode(Buffer.from(transaction.signatures[transaction.signatures.length - 1]));
  return {
    ...chainOp,
    signature,
  };
}
```

### Quote-Level Solana Signing

For signing complete V3 quotes with multiple operations:

```typescript theme={null}
// Types available from the TypeScript Types section above

/**
 * Sign a Solana quote using the provided wallet
 * @param quote - Quote response to sign
 * @param solanaWallet - Solana wallet for signing
 * @returns Signed quote ready for execution
 */
export async function signSolanaQuote(quote: QuoteV3, solanaWallet: any): Promise<QuoteV3> {
  try {
    const signedOperations = await Promise.all(
      quote.originChainsOperations.map(async operation => {
        if ('type' in operation && operation.type === 'solana') {
          const solanaOp = operation as SolanaOperation;

          // Skip if already signed (signature is not empty or "0x")
          if (solanaOp.signature && solanaOp.signature !== '0x' && solanaOp.signature !== '') {
            return solanaOp;
          }

          const signature = await signSolanaOperation(solanaOp.dataToSign, solanaWallet);

          // Create the signed operation, ensuring we replace the signature properly
          const signedOp = {
            ...solanaOp,
            signature: signature, // Replace the "0x" placeholder with actual signature
          };

          return signedOp;
        }
        return operation;
      })
    );

    const signedQuote = {
      ...quote,
      originChainsOperations: signedOperations,
    };

    return signedQuote;
  } catch (error) {
    console.error('Error signing Solana quote:', error);
    throw error;
  }
}
```

## Common Patterns

### Sequential Quote Signing

For quotes with multiple operations that need to be signed in sequence:

```typescript theme={null}
// Helper to run an array of lazy promises in sequence
export const sequentialPromises = (promises: (() => Promise<any>)[]): Promise<any[]> => {
  return promises.reduce<Promise<any[]>>(
    (acc, curr) => acc.then(results => curr().then(result => [...results, result])),
    Promise.resolve([])
  );
  };

export const signQuote = async (quote: Quote, embeddedWallet: ConnectedWallet) => {
  const signWithEmbeddedWallet = signOperation(embeddedWallet);

  const signedQuote = {
    ...quote,
  };

  signedQuote.originChainsOperations = await sequentialPromises(
    quote.originChainsOperations.map(signWithEmbeddedWallet)
  );

  if (quote.destinationChainOperation) {
    signedQuote.destinationChainOperation = await signWithEmbeddedWallet(
      quote.destinationChainOperation
    )();
  }

  return signedQuote;
};
```

### Error Handling Best Practices

```typescript theme={null}
export function getReadableStatus(status: string): string {
  switch (status) {
    case 'PENDING':
      return 'Transaction pending...';
    case 'IN_PROGRESS':
      return 'Processing transaction...';
    case 'COMPLETED':
      return 'Transaction completed successfully!';
    case 'FAILED':
      return 'Transaction failed';
    case 'REFUNDED':
      return 'Transaction refunded';
    default:
      return `Status: ${status}`;
  }
}

// Always wrap signing operations in try-catch
async function safeSignOperation(operation: any, signer: any) {
  try {
    return await signOperation(operation, signer);
  } catch (error) {
    console.error('Signing failed:', error);
    throw new Error(`Failed to sign operation: ${error.message}`);
  }
}
```

## Troubleshooting

### Common Issues

<AccordionGroup>
  <Accordion title="Wrong signing method for account type">
    **Problem**: Using `signTypedData()` for Kernel 3.3 accounts or `signMessage()` for role-based accounts.

    **Solution**: Check your account type and use the correct signing method:

    * Role-based: Use `signTypedData()`
    * Kernel 3.3 (EIP-7702): Use `signMessage()` with UserOperation hash
  </Accordion>

  <Accordion title="Invalid Solana signature format">
    **Problem**: Solana signatures are not in base58 format or wrong length.

    **Solution**: Ensure you extract the signature correctly:

    ```typescript theme={null}
    const signature = bs58.encode(Buffer.from(signedTx.signatures[signedTx.signatures.length - 1]));
    ```
  </Accordion>

  <Accordion title="Missing delegation signature for EIP-7702">
    **Problem**: EIP-7702 operations fail due to missing delegation signatures.

    **Solution**: Always check if delegation is required and sign it first:

    ```typescript theme={null}
    if (operation.delegation) {
      const signedTuple = await signerAccount.signAuthorization(authTuple);
      // ... handle delegation signature
    }
    ```
  </Accordion>

  <Accordion title="UserOperation hash mismatch">
    **Problem**: Kernel 3.3 account signatures fail due to incorrect UserOperation hash calculation.

    **Solution**: Ensure proper deserialization and use correct entrypoint:

    ```typescript theme={null}
    const deserializedUserOp = deserializeUserOp(operation.userOp);
    const userOpHash = getUserOperationHash<'0.7'>({
      userOperation: deserializedUserOp,
      entryPointAddress: entryPoint07Address,
      entryPointVersion: '0.7',
      chainId: chainId,
    });
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="EIP-7702 Guide" icon="arrow-right" href="/guides/eip-7702/getting-started">
    Learn about EIP-7702 delegation and Kernel account setup
  </Card>

  <Card title="Solana Guide" icon="arrow-right" href="/guides/solana/getting-started">
    Get started with Solana integration and cross-chain operations
  </Card>

  <Card title="Contract Calls" icon="arrow-right" href="/guides/contract-calls/getting-started">
    Execute smart contract calls using proper signing patterns
  </Card>

  <Card title="Chain Abstracted Swap" icon="arrow-right" href="/getting-started/chain-abstracted-swap">
    Build a complete cross-chain swap with signing implementation
  </Card>
</CardGroup>
