Skip to content

About

A library to sign transactions with Lute Wallet

Topics

Resources

Contributing

Stars

10 stars

Watchers

1 watching

Forks

Repository files navigation

lute-connect

npm version License

Overview

Lute Connect is a Javascript library to securely sign transactions with Lute, an Algorand web wallet and Chrome extension.

Installation

The package can be installed via your favorite package manager:

npm i @galaxypay/lute-connect
yarn add @galaxypay/lute-connect
pnpm add @galaxypay/lute-connect

API Usage

Quick start

import LuteConnect from "@galaxypay/lute-connect";
const lute = new LuteConnect();

Web mode (no extension, or forceWeb) opens a popup to lute.app. If your site sends Cross-Origin-Opener-Policy, use same-origin-allow-popups; same-origin blocks the popup from replying.

Connect to Lute

// Warning: Browser will block pop-up if user doesn't trigger lute.connect() with a button click
async function connect() {
  try {
    const genesis = await algodClient.genesis().do();
    const genesisID = `${genesis.network}-${genesis.id}`;
    const addresses = await lute.connect(genesisID);
    // TODO: handle user address selection and storage
  } catch (err) {
    console.error(`[LuteWallet] Error connecting: ${err.message}`);
    throw err;
  }
}

To also learn how each account signs, use connectAccounts. It returns the addresses plus each account's empty signature: base64 of the canonical msgpack encoding of a SignedTransaction without txn, with placeholder signature bytes. Use them to simulate fees with correctly shaped signatures, for example for Falcon (post-quantum) and multisig accounts. An address missing from emptySignatures is of unknown type.

const { addrs, emptySignatures } = await lute.connectAccounts(genesisID);

Sign transactions

// Warning: Browser will block pop-up if user doesn't trigger lute.signTxns() with a button click
async function signTransactions(txns) {
  try {
    const signedTxns = await lute.signTxns(txns);
    // TODO: handle signedTxns (e.g. submit to algodClient)
  } catch (err) {
    console.error(
      "[LuteWallet] Error signing transactions: " +
        (err instanceof SignTxnsError
          ? `${err.message} (code: ${err.code})`
          : err.message),
    );
    throw err;
  }
}

Sign data

// Warning: Browser will block pop-up if user doesn't trigger lute.signData() with a button click
import { Address } from "algosdk";
import { canonify } from "canonify";

async function authenticate() {
  try {
    const domain = location.host;
    const acctInfo = await algodClient.accountInformation(activeAddress).do();
    const siwaRequest: Siwa = {
      domain,
      chain_id: activeNetworkConfig.caipChainId || "algorand:localnet",
      account_address: activeAddress,
      type: "ed25519",
      statement:
        "Put your own statement here, for example: I accept the ExampleOrg Terms of Service.",
      uri: location.origin,
      version: "1",
      nonce: Buffer.from(randomBytes(12)).toString("base64"),
      "issued-at": new Date().toISOString(),
    };
    const dataString = canonify(siwaRequest);
    if (!dataString) throw Error("Invalid JSON");
    const data = btoa(dataString);
    const enc = new TextEncoder();
    const authenticatorData = await sha256(enc.encode(domain));
    const signer =
      acctInfo.authAddr?.publicKey ??
      Address.fromString(activeAddress).publicKey;
    const sdtSignData: StdSignData = {
      data,
      signer,
      domain,
      authenticatorData,
    };
    const metadata: SignMetadata = {
      scope: ScopeType.AUTH,
      encoding: "base64",
    };
    const signerResponse = await lute.signData(sdtSignData, metadata);
    // TODO: verify signerResponse
  } catch (err) {
    console.error(
      "[LuteWallet] Error signing data: " +
        (err instanceof SignDataError
          ? `${err.message} (code: ${err.code})`
          : err.message),
    );
    throw err;
  }
}

Falcon-1024 accounts

A Falcon address is a hash of its public key, so the dApp must send the key itself. Take it from the account's empty signature in the connect result (pqsig.pk). Set type: "falcon-1024-det" on the SIWA request and scheme: "falcon-1024-det" on the StdSignData:

import algosdk from "algosdk";
import { verifyCompressed } from "falcon-1024";

const { pqsig } =
  algosdk.decodeSignedTransaction(/* emptySignatures[addr] with a placeholder txn */);
const siwaRequest: Siwa = { ...siwa, type: "falcon-1024-det" };
const sdtSignData: StdSignData = {
  data,
  signer: pqsig.pk,
  domain,
  authenticatorData,
  scheme: "falcon-1024-det",
};
const resp = await lute.signData(sdtSignData, metadata);

// Verify: the key must hash to the address (or its auth address), and the
// signature must cover sha256(data) || sha256(authenticatorData).
const { address } = algosdk.addressFromPQKey(
  algosdk.FALCON_1024_SCHEME,
  resp.signer,
);
const toSign = new Uint8Array([
  ...(await sha256(enc.encode(dataString))),
  ...(await sha256(authenticatorData)),
]);
const ok =
  address.toString() === activeAddress &&
  verifyCompressed(resp.signer, resp.signature, toSign);

Add Network

// Warning: Browser will block pop-up if user doesn't trigger lute.addNetwork() with a button click
async function addCustomNetwork() {
  try {
    const customNetwork: Network = {
      name: "ExampleNet",
      algod: {
        url: "https://examplenet-api.4160.nodely.dev",
        port: "",
        token: "",
      },
      genesisID: "examplenet-v1.0",
    };
    await lute.addNetwork(customNetwork);
  } catch (err) {
    console.error(
      "[LuteWallet] Error adding network: " +
        (err instanceof AddNetworkError
          ? `${err.message} (code: ${err.code})`
          : err.message),
    );
    throw err;
  }
}

About

A library to sign transactions with Lute Wallet

Topics

Resources

Contributing

Stars

10 stars

Watchers

1 watching

Forks

Used by

Contributors

Languages