Sign a transaction with Polkadot{.js} extension in a browser environment

A transaction must be signed before it can be submitted for processing. This means that a transaction needs to be authenticated by a third-party entity, as a wallet, or an extension, which we'll refer to as "the signer." The signer's role is to validate the transaction, officially record it on the blockchain, and cover the transaction fees.

If you are not familiar with the process of creating a transaction on the Ternoa chain, we highly suggest first reading the Getting Started section of the documentation with a focus on:

If you've explored the Guides section, you may have noticed a frequently used parameter in many functions: the Keyring. The Keyring is essential for managing a collection of keypairs utilized for signing transactions. In our example, while we use the keyring to focus on the specific content we wish to illustrate, it's important to note that in most real-world decentralized application (dApp) scenarios, the approach differs. Generally, retrieving the keyring from a seed is not a recommended practice to incorporate in your project, and it's typically not favored by dApp users. Asking for or providing a seed phrase should be avoided unless the situation is entirely without risk.

Utilizing Polkadot extension, Next.js, and TypeScript:

In the code snippet below, we will step by step cover a signing use-case to create a basic NFT on the chain using the @polkadot/extension-dapp and uploading your File and Metadata on IPFS.

Please, note that this solution is not the only one. Feel free to use any provider that would suit best your dApp.

This code snipped is designed to work in a Next-js environment. According to Next-js server-side components rules, the import of the @polkadot/extension-dapp extension library might need to be slightly adapted to work in your javascript environment.

import {
  checkTransactionSuccess,
  createNftTx,
  Errors,
  getRawApi,
  NftMetadataType,
  query,
  submitTxBlocking,
  TernoaIPFS,
  WaitUntil,
  File,
  NFTCreatedEvent,
} from "ternoa-js";

export const createAndSignNFT = async (
  file: File,
  metadata: NftMetadataType,
  ipfsClient: TernoaIPFS,
  address: string
) => {
  try {
    //1 - Store the File and Metadata on IPFS
    const { Hash } = await ipfsClient.storeNFT(file, metadata);
    //2 - generate tx to sign
    const txHex = await createNftTx(Hash);

    //3.1 - Generate a nonce
    const nonce = (
      await query("system", "account", [address])
    ).nonce.toNumber();
    //3.2- Connect to the extension you want to use to get an injector (here we use directly Polkadot extension)
    const { web3FromAddress } = await import("@polkadot/extension-dapp");
    const injector = await web3FromAddress(address);
    const signer = injector?.signer;

    //4.1 - Retrieve Ternoa API
    const api = getRawApi();
    //4.2- Sign transaction using both the nonce and the signer
    const signedTx = (
      await api.tx(txHex).signAsync(address, { nonce, signer })
    ).toHex();

    //5.1 - Submit the signed transaction
    const { events } = await submitTxBlocking(
      signedTx,
      WaitUntil.BlockInclusion
    );
    //5.2 - Return the filtered NFTCreatedEvent
    return events.findEventOrThrow(NFTCreatedEvent);
  } catch (error) {
    console.log(error);
  }
};

We suggest breaking down certain portions of this function into multiple modular and reusable atomic functions. This example illustrates a comprehensive and complete workflow to enhance comprehension.

Last updated