Bitcoin

Asigna Bitcoin Connect SDK

The Asigna extension enables connecting user multisig wallets to your Bitcoin applications. This SDK provides the full logic required to connect a user's multisig wallet, build, sign, and execute a Partially Signed Bitcoin Transaction (PSBT), as well as sign messages and verify signatures. Within the Asigna SDK, the BitcoinJS Lib package is used for PSBTs.

Installation

To install the Bitcoin Connect SDK, run:

npm install @asigna/btc-connect

Workflow

1. Connect Asigna Extension with the Asigna Web Interface

To begin, download the Asigna extension and connect it to the Asigna web interface at https://btc.asigna.io. If you don’t already have a multisig wallet, create one to connect it to your Bitcoin application on your desired network.

2. Connect Bitcoin Application to the Asigna Extension

To connect your application (similar to other Bitcoin wallets), use the useAsignaExtension hook:

  • connect() - opens the connection modal:

const { connect } = useAsignaExtension();

const asignaAuthRequest = async () => {
  const info = await connect();
  // Save account data to storage
  localStorage.setItem('accountData', info);
  console.log(info);
};
  • disconnect() - disconnects the extension from your application:

const { disconnect } = useAsignaExtension();

const disconnectRequest = () => {
  disconnect();
  // Clear connected account data
  localStorage.setItem('accountData', undefined);
  console.log(info);
};

3. Retrieve Multisig Information

  • useAsignaAddress - returns the connected multisig address:

const { asignaAddress } = useAsignaAddress();
  • useAsignaSafeInfo - returns connected multisig data:

{
  address: string,
  asignaToken: string, // Token used for API requests within the package
  threshold: number,
  multisigType: 'WSH' | 'TAPROOT',
  users: [
    {
      address: string;
      publicKey?: string;
      xPub?: string;
      fingerPrint?: string;
      derivation?: string;
    }
  ],
  psbtInputConstructionFields: {
    witnessUtxo: {
      script: Buffer;
    },
    tapLeafScript: [
      {
        leafVersion: number;
        script: Buffer;
        controlBlock: Buffer;
      }
    ],
    witnessScript: {
      script: Buffer,
    }
  }
}

Example:

const { asignaSafeInfo } = useAsignaSafeInfo();
console.log('Connected:', asignaSafeInfo);

4. UI Modals for Signing PSBT or Messages

To display UI modals for signing a PSBT or message and wait for signatures, use the <AsignaSignActionModals /> component:

// openSignMessage and openSignPsbt calls are async
// You can await them directly in your async functions
// e.g., await openSignMessage(message);

function App() {
  return (
    <div>
      <AsignaSignActionModals
        onSignPsbt={(tx) => {
          console.log('PSBT Signature:', tx);
        }}
        onSignMessage={(signature) => {
          console.log('Message Signature:', signature);
          if (!asignaSafeInfo) return;
          
          console.log(
            'Is valid:', validateMessage(
              message,
              signature,
              asignaSafeInfo.multisigType, 
              asignaSafeInfo.address,
              asignaSafeInfo.users.map((x) => x.publicKey || ''),
              asignaSafeInfo.users.map((x) => x.address), 
              asignaSafeInfo.threshold,
              bitcoin.networks.testnet
            )
          );
        }}
      />
    </div>
  );
}

5. Sign Messages

Multisig wallets don't have a single private key to sign messages. Therefore, multiple owners must sign, and their signatures are merged for verification.

  • openSignMessage(message: string) - opens the modal to sign a message:

const { openSignMessage } = useAsignaExtension();

<div onClick={async () => await openSignMessage(message)}>
  Open Sign Message Modal
</div>
  • signMessage(message: string) - requests a message signature without waiting for the modal:

const { signMessage } = useAsignaExtension();

<div onClick={async () => await signMessage(message)}>
  Sign message without waiting for merged signature
</div>
  • validateMessage - validates the message signature using the full multisig construction information:

import { validateMessage, useAsignaSafeInfo } from '@asigna/core;

const { asignaSafeInfo } = useAsignaSafeInfo();
const message = 'test message';
const signature = await signMessage(message);

const isValid = validateMessage(
  message,
  signature,
  asignaSafeInfo.multisigType, 
  asignaSafeInfo.address,
  asignaSafeInfo.users.map((x) => x.publicKey || ''),
  asignaSafeInfo.users.map((x) => x.address), 
  asignaSafeInfo.threshold,
  bitcoin.networks.testnet
);

6. Create Transactions

To create a multisig PSBT, it's crucial to correctly add inputs and calculate fees:

  • useAsignaFee - calculates gas:

const { calculateGas } = useAsignaFee();
  • useAsignaInputs - maps UTXOs to PSBT inputs based on the multisig type:

const { createInput } = useAsignaInputs();

Example:

const { createInput } = useAsignaInputs();
const { calculateGas } = useAsignaFee();
const { asignaAddress } = useAsignaAddress();

function createTx() {
  if (!utxos || !asignaAddress) return;

  const psbt = new bitcoin.Psbt({ network: bitcoin.networks.testnet });
  let totalOutput = 0;

  utxos.forEach((utxo) => {
    const input = createInput(utxo);
    if (!input) return;
    psbt.addInput(input.data);
    totalOutput += input.utxo.satoshis;
  });

  const FEE_RATE = 5;
  const fee = (calculateGas(psbt) || 0) * FEE_RATE;
  const amountToSend = BigNumber(amount).shiftedBy(8);

  psbt.addOutput({ value: amountToSend.toNumber(), address });
  psbt.addOutput({
    value: totalOutput - amountToSend.toNumber() - fee,
    address: asignaAddress
  });

  openSignPsbt(psbt);
}

7. Sign PSBT

A PSBT requires multiple owner signatures, which may take time. There are two signing options:

  • signPsbt(psbt: bitcoin.Psbt) - creates a transaction for signature gathering in the multisig queue (does not return the signed PSBT):

const { signPsbt } = useAsignaExtension();
  • openSignPsbt(psbt: bitcoin.Psbt, execute?: boolean) - opens a waiting modal until enough signatures are gathered and the signed PSBT is returned to the app. Execute flag can send the transaction to mempool once there's enough signatures gathered and returns the txId back to the application.

const { openSignPsbt } = useAsignaExtension();

This method is recommended when the application needs to retrieve the signed PSBT, otherwise better to use the signPsbt approach.

8. Demo application

A simple app which has all the integration features can be found here:

It is also deployed at https://asigna-btc-connect-demo.vercel.app/

Last updated