Problem

Composer actions and miniapps wish to request transactions from the user’s wallet, but they have no good mechanism to do so. It’s possible to use QR code based wallet linking inside the webview as a workaround, but this a very poor user experience, especially on mobile.

Goals

Proposal

Introduce a new miniapp-client message type, fc_requestWalletAction. Embedded apps may send a postMessage to the parent app to request a transaction:

window.parent.postMessage({
  jsonrpc: "2.0",
  id: "01ef6570-5a51-48fa-910c-f419400a6d0d",
  method: "fc_requestWalletAction",
  params: {
    action: {
      method: "eth_sendTransaction",
      chainId: "eip155:10",
      params: {
        abi: [...], // JSON ABI of the function selector and any errors
        to: "0x00000000fcCe7f938e7aE6D3c335bD6a1a7c593D",
        data: "0x783a112b0000000000000000000000000000000000000000000000000000000000000e250000000000000000000000000000000000000000000000000000000000000001",
        value: "984316556204476",
      },
    }
  }
}, "*");

The message must be a valid JSON-RPC Request object.

The message’s params.action must follow the same format as frame transaction requests, either a transaction or typed signature:

type RequestWalletActionMessage = {
  jsonrpc: "2.0";
  id: string | number | null;
  method: "fc_requestWalletAction";
  params: RequestWalletActionParams;
}

type RequestWalletActionParams = {
  action: EthSendTransactionAction | EthSignTypedDataV4Action;
}

type EthSendTransactionAction = {
  chainId: string;
  method: 'eth_sendTransaction';
  attribution?: boolean;
  params: {
    abi: Abi | [];
    to: string;
    value?: string;
    data?: string;
  };
};

type EthSignTypedDataV4Action = {
  chainId: string;
  method: 'eth_signTypedData_v4';
  params: {
    domain: {
      name?: string;
      version?: string;
      chainId?: number;
      verifyingContract?: string;
    };
    types: Record<string, unknown>;
    primaryType: string;
    message: Record<string, unknown>;
  };
};

See the Frame Specification for details on transaction and signature request types.

Farcaster clients must listen for requests of this type and present the transaction request to the user. When the user completes the transaction action, clients must send back a JSON-RPC Response message in the following format:

postMessage({
  jsonrpc: "2.0",
  id: "01ef6570-5a51-48fa-910c-f419400a6d0d",
  result: {
    address: "0x075b108fC0a6426F9dEC9A5c18E87eB577D1346a"
    transactionHash: "0x0e2b80fd7ecd4263de49d6979d68cc0d0e487a9b1ea8f95281c2d4e641318cd4"
    },
  }
}, "*");

Where address is the user’s connected wallet and transaction ID is the transaction hash or signature bytes. This is the same format as frame transaction responses.

Unlike frames, miniapps are more complex and may wish to recover from a failed or rejected transaction. In the event of a failed or cancelled transaction, the client app must send back a failure message:

postMessage({
  jsonrpc: "2.0",
  id: "01ef6570-5a51-48fa-910c-f419400a6d0d",
  error: {
    code: -32000
    message: "User rejected the request"
  }
}, "*");

Response types:

type TransactionResponse = 
  TransactionResponseSuccess | 
  TransactionResponseFailure;

type TransactionResponseSuccess = {
  jsonrpc: "2.0";
  id: string | number | null;
  result: TransactionSuccessBody
}

type TransactionSuccessBody = 
  EthSendTransactionSuccessBody | 
  EthSignTypedDataV4SuccessBody;
  
type EthSendTransactionSuccessBody = {
    address: string;
    transactionHash: string; 
}

type EthSignTypedDataV4SuccessBody = {
    address: string;
    signature: string; 
}

type TransactionResponseFailure = {
  jsonrpc: "2.0";
  id: string | number | null;
  error: {
    code: number;
    message: string;
  }
}

Migration to JSON-RPC message format