Skip to content

Sign Messages with Custodial Wallets

Signing and verifying messages for smart accounts is different than with EOAs. There are a few reasons why:

  • With an EOA, the address is effectively the public key of the private key used for signing. Therefore, verifying a EOA signature is as simple as recovering the signature and compare the recovered public key with the address.

    • With a smart account, the address is the address of a smart contract that has no cryptographic link to the signing private key. Therefore, you must use ERC-1271 to validate the message.
  • With an EOA, you don't have to deploy the account. It just exists.

    • Since smart accounts need to be deployed, it may not be clear how you can validate messages against a smart account not yet deployed.

Signing messages

To sign messages:

// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.openfort.io/api-keys
const Openfort = require('@openfort/openfort-node').default;
const openfort = new Openfort(YOUR_SECRET_KEY);
 
const _domain = {
  name: "Openfort",
  version: "0.5",
  chainId: 13337,
  verifyingContract: "0x9b5AB198e042fCF795E4a0Fa4269764A4E8037D2",
};
const types = {
  Mail: [
    { name: "from", type: "Person" },
    { name: "to", type: "Person" },
    { name: "content", type: "string" },
  ],
  Person: [
    { name: "name", type: "string" },
    { name: "wallet", type: "address" },
  ],
};
const mail = {
  from: {
    name: "Alice",
    wallet: "0x2111111111111111111111111111111111111111",
  },
  to: {
    name: "Bob",
    wallet: "0x3111111111111111111111111111111111111111",
  },
  content: "Hello!",
};
const structHash = ethers.utils._TypedDataEncoder.hash(domain, types, mail);
 
const signature = await openfort.accounts.signPayload({ 
  id: "acc_4194ad24-c818-4e5c-b003-9cc2aa7df53b", 
  hash: structHash,
  domain: domain
  value: mail,
  types: types,
});

Validating signatures

You can validate signatures with ERC-1271. Here's an example with ethers:

const ethers = require("ethers");
 
async function verifySignature(hash, signature, address) {
  let provider = new ethers.providers.JsonRpcProvider(providerUrl);
  const ABI =   {
    inputs: [
      {
        internalType: "bytes32",
        name: "_hash",
        type: "bytes32",
      },
      {
        internalType: "bytes",
        name: "_signature",
        type: "bytes",
      },
    ],
    name: "isValidSignature",
    outputs: [
      {
        internalType: "bytes4",
        name: "",
        type: "bytes4",
      },
    ],
    stateMutability: "view",
    type: "function",
  };
  const iface = new ethers.utils.Interface(ABI);
  const encodedDataDeposit = iface.encodeFunctionData("isValidSignature", [
    hash,
    signature,
  ]);
 
  const tx = {
    to: address,
    data: encodedDataDeposit,
  };
  return await provider.call(tx);
}