Transaction Process for Custodial Accounts

Custodial account transactions are unique as these users cannot sign transactions using their web3 wallets. To ensure security and align with the characteristics of web3 transactions, we have developed a special application called the Custodial Signer. This application securely handles the transaction signing process by communicating with a server specifically designed to manage custodial account transactions.

Integration Guide

To use custodial accounts to complete transactions, tt requires users to implement a popup window and communicate with the custodial signer via post messages to obtain the signature. Then, the obtained signature is used to send the transaction to the blockchain.

Code Example: Barebones Solution

Step 1: Get Signature by Communicating with Custodial Signer

sync function sendTransaction() {
  if (transactionSignature == null || fromAccount == null) {
    return;
  }

  const rawTransactionWithSignature = {
    ...rawTransactionWithoutSignature,
    signature: transactionSignature,
    from: fromAccount,
    nonce,
  };

  const serializedSignedTransaction = ethers.Transaction.from(
    rawTransactionWithSignature
  ).serialized;

  const transactionResponse = await provider.broadcastTransaction(
    serializedSignedTransaction
  );
}

Step 2: Send Transaction to Blockchain

import { ethers } from 'ethers';

const rawTransactionWithoutSignature = {
  to: 'the destination wallet address',
  value: ethers.parseEther('0.01'),
  chainId: 'the chain id',
  gasLimit: 210000,
  gasPrice: ethers.parseUnits('10.0', 'gwei'),
};
let nonce = 0;

const custodialSignerUrl = 'the custodial signer service url';

async function signTransaction() {
  if (typeof window === 'undefined') {
    return;
  }

  const fromAccount = 'your own wallet address';

  const transactionCount = await provider.getTransactionCount(fromAccount);
  nonce = transactionCount + 1;

  const serializedUnsignedTransaction = ethers.Transaction.from({
    ...rawTransactionWithoutSignature,
    nonce,
  }).unsignedSerialized;

  const signTransactionPayload = {
    account: fromAccount,
    transaction: serializedUnsignedTransaction,
  };

  // If using native clients (games) payload can include callbackUrl to which signature will be sent
  // const signTransactionPayload = {
  //   account: fromAccount,
  //   transaction: serializedUnsignedTransaction,
  //   callbackUrl: 'http://localhost:3000/signature-callback' // <- your game callback URL here
  // }; 

  const id = 'client:2'; // must be formatted as `client:${ an identifier number }`
  const tag = 'fv/sign-tx'; // do not change this

  const encodedPayload = {
    id,
    tag,
    payload: signTransactionPayload,
  };

  window.open(
    `${custodialSignerUrl}?request=${base64UrlEncode(
      JSON.stringify(encodedPayload)
    )}`,
    'futureverse_wallet', // don't change this
    'popup,right=0,width=290,height=286,menubar=no,toolbar=no,location=no,status=0'
  );

  window.addEventListener('message', (ev) => {
    if (ev.origin === custodialSignerUrl) {
      const dataR = signMessageType.decode(ev.data);

      if (E.isRight(dataR)) {
        transactionSignature = dataR.right.payload.response.signature;
      }
    }
  });
}

Last updated

© 2023 -> ♾️