Recipes

In this section, we will check real-world examples. Of course, you can also check the code in many demos linked on the homepage. Let's see the most common cases here.

Remember that you can use the ElvenJS not only in static websites but let's focus only on such ones for simplicity. Check the linked demos on StackBlitz to learn how to use it, for example, with Vue or SolidJS.

How to login and logout with auth providers

ElvenJS offers two of four auth providers for now. They are the xPortal Mobile app, MultiversX browser extension, and MultiversX Web Wallet. There will also be support for the Ledger Nano and Ledger Nano.

To be able to login you need to initialize ElvenJs and then use the login function:

<html>
<body>
  <button class="button" id="button-login-extension" style="display: none;">Login with Extension</button>
  <button class="button" id="button-login-mobile" style="display: none;">Login
    with xPortal</button>
  <button class="button" id="button-logout" style="display: none;">Logout</button>

  <div id="qr-code-container" class="qr-code-container"></div>

  <script type="module">
    // Just for the demo - helpers
    import {
      uiLoggedInState,
      uiPending,
    } from './demo-ui-tools.js'

    // import ElvenJS parts from CDN 
    import {
      ElvenJS
    } from 'https://unpkg.com/elven.js@0.20.0/build/elven.js';

    // Init ElvenJs 
    const initElven = async () => {
      // Check all possible InitOptions at https://www.elvenjs.com/docs/sdk-reference.html#initialization
      await ElvenJS.init(...);
    }

    // Trigger the async init function 
    initElven();

    // Add event listener for extension login button 
    document
      .getElementById('button-login-extension')
      .addEventListener('click', async () => {
        try {
          await ElvenJS.login('browser-extension');
        } catch (e) {
          console.log('Login: Something went wrong, try again!', e?.message);
        }
      });

    // Add event listener for mobile login button 
    // You will need a container for the qr code
    document
      .getElementById('button-login-mobile')
      .addEventListener('click', async () => {
        try {
          await ElvenJS.login('mobile', {
            // You can also use the DOM element here: 
            // qrCodeContainer: document.querySelector('#qr-code-container')
            qrCodeContainer: 'qr-code-container',
          });
        } catch (e) {
          console.log('Login: Something went wrong, try again!', e?.message);
        }
      });

    // Add event listener for web login button 
    // You can pass the callback url - the landing page after login on web wallet website
    document
      .getElementById('button-login-web')
      .addEventListener('click', async () => {
        try {
          await ElvenJS.login('web-wallet', { callbackRoute: '/' });
        } catch (e) {
          console.log('Login: Something went wrong, try again!', e?.message);
        }
      });

    // Add event listener for logout button
    document
      .getElementById('button-logout')
      .addEventListener('click', async () => {
        try {
          // Trigger the ElvenJS logout
          const isLoggedOut = await ElvenJS.logout();
        } catch (e) {
          console.error(e.message);
        }
      });
  </script>
</body>
</html>

After using one of the login methods, your data will be kept in the localStorage for further usage and synchronization. No worries, nothing private.

From now on, you can sign and send transactions.

How to send EGLD

For this example, let's omit the code responsible for initialization and auth. You can check it above. Let's focus on the EGLD operations:

<html>
<body>
  <button class="button" id="button-tx" style="display: none;">EGLD transaction</button>
  <div id="tx-hash-or-query-result" class="tx-hash-or-query-result"></div>

  <script type="module">
    // Just for the demo - helpers
    import {
      uiPending,
      updateTxHashContainer,
    } from './demo-ui-tools.js'

    // import ElvenJS parts from CDN 
    import {
      ElvenJS,
      Transaction,
      Address,
      TokenTransfer
    } from 'https://unpkg.com/elven.js@0.20.0/build/elven.js';

    // Init ElvenJs 
    const initElven = async () => {
      // Check all possible InitOptions at https://www.elvenjs.com/docs/sdk-reference.html#initialization
      await ElvenJS.init(...);
    }

    // Trigger the async init function 
    initElven();

    const egldTransferAddress = 'erd17a4wydhhd6t3hhssvcp9g23ppn7lgkk4g2tww3eqzx4mlq95dukss0g50f';

    // Event listener for predefined EGLD transaction
    document
      .getElementById('button-tx')
      .addEventListener('click', async () => {
        updateTxHashContainer(false);
        const demoMessage = 'Transaction demo from Elven.js!';

        const isGuardian = ElvenJS.storage.get('activeGuardian');
        const isXalias = ElvenJS.storage.get('loginMethod') === 'x-alias';
        // Additional 50000 when there is an active guardian
        // See more about gas limit calculation here: https://docs.multiversx.com/developers/gas-and-fees/overview/
        const gasLimit = ((isGuardian || isXalias) ? 100000 : 50000) + 1500 * demoMessage.length;

        const textEncoder = new TextEncoder();
        
        // predefined transaction, this is how it is usually built
        const tx = new Transaction({
          // Get the actal nonce from storage
          nonce: ElvenJS.storage.get('nonce'),
          // Get the receiver of the EGLD
          receiver: new Address(egldTransferAddress),
          // Calculate gas limit (check MultiversX docs)
          // You will need additional 50000 when using guardians
          gasLimit: gasLimit,
          // Define the chain id (D for the devnet, T for the testnet, 1 for the mainnet)
          chainID: 'D',
          // Build transaction payload data, here very simple string
          data: textEncoder.encode(demoMessage),
          // EGLD value to send (using parseAmount from Elven.js)
          value: parseAmount({ amount: '0.001', decimals: 18 }),
          // Your address, we can get it from the storage, because you should be loggedin
          sender: new Address(ElvenJS.storage.get('address')),
        });

        try {
          // Send the transaction
          await ElvenJS.signAndSendTransaction(tx);
        } catch (e) {
          throw new Error(e?.message);
        }
      });
  </script>
</body>
</html>

As you can see, more logic is involved in building the transaction here. It could look not very easy, but generally, it is just an object created with a couple of helpers exported from sdk-js SDK. So it is very similar to how you would do this with sdk-js.

Transactions are handled in very similar ways. They only need different payload structures and builders. You will find the whole list of them in the SDK reference.

Oh, and by the way, the transaction here is predefined, but you could have your logic that could take all the values from some form, user action etc.

How to send ESDT

The same here. Let's not focus on initialization and login. You can check it above in the first point.

Below you will find an example of the ESDT transfer. What is ESDT? These are tokens on the MultiversX network that you can create for yourself. Please read more about them here.

<html>
<body>
  <button class="button" id="button-tx-esdt" style="display: none;">ESDT transaction*</button>
  <div id="tx-hash-or-query-result" class="tx-hash-or-query-result"></div>

  <script type="module">
    // Just for the demo - helpers
    import {
      uiPending,
      updateTxHashContainer,
      updateQueryResultContainer
    } from './demo-ui-tools.js'

    // import ElvenJS parts from CDN 
    import {
      ElvenJS,
      Address,
      TokenTransfer,
      Token,
      TransferTransactionsFactory,
      TransactionsFactoryConfig,
      parseAmount
    } from 'https://unpkg.com/elven.js@0.20.0/build/elven.js';

    // Init ElvenJs 
    const initElven = async () => {
      // Check all possible InitOptions at https://www.elvenjs.com/docs/sdk-reference.html#initialization
      await ElvenJS.init(...);
    }

    // Trigger the async init function 
    initElven();

    // ESDT address for demo purpose
    const esdtTransferAddress = 'erd17a4wydhhd6t3hhssvcp9g23ppn7lgkk4g2tww3eqzx4mlq95dukss0g50f';

    // Event listener for triggering the predefined ESDT transaction
    document
      .getElementById('button-tx-esdt')
      .addEventListener('click', async () => {
        updateTxHashContainer(false);

        // We need to build the payment here, we need to provide some data
        // Token id, amount and decimal places (check sdk-js cookbook for more info)
        const tokenTransfer = new TokenTransfer({
          token: new Token({ identifier: 'BUILDO-22c0a5' }),
          amount: parseAmount({ amount: '1', decimals: 18 }),
        });

        // Here we are preparing the transfer factory
        const factory = new TransferTransactionsFactory({
          config: new TransactionsFactoryConfig({ chainID: 'D' }),
        });

        // And here we have actual transaction
        // It doesn't need the value field, because we don't send the EGLD
        const tx = factory.createTransactionForESDTTokenTransfer({
          receiver: new Address(esdtTransferAddress),
          sender: new Address(ElvenJS.storage.get('address')),
          tokenTransfers: [tokenTransfer]
        });

        try {
          // We use the same function as previously
          const transaction = await ElvenJS.signAndSendTransaction(tx);
        } catch (e) {
          throw new Error(e?.message);
        }
      });
  </script>
</body>
</html>

How to mint NFT

Again, let's not focus on initialization and login. Check these above in the first point.

Here we will mint an NFT on the Elven Tools Minter Smart Contract deployed on the devnet.

<html>
<body>
  <button class="button" id="button-mint" style="display: none;">Mint NFT</button>
  <div id="tx-hash-or-query-result" class="tx-hash-or-query-result"></div>

  <script type="module">
    // Just for the demo - helpers
    import {
      uiPending,
      updateTxHashContainer,
      updateQueryResultContainer
    } from './demo-ui-tools.js'

    // import ElvenJS parts from CDN 
    import {
      ElvenJS,
      Transaction,
      Address,
      TransactionsFactoryConfig,
      SmartContractTransactionsFactory,
      U32Value,
      parseAmount
    } from 'https://unpkg.com/elven.js@0.20.0/build/elven.js';

    // Init ElvenJs 
    const initElven = async () => {
      // Check all possible InitOptions at https://www.elvenjs.com/docs/sdk-reference.html#initialization
      await ElvenJS.init(...);
    }

    // Trigger the async init function 
    initElven();

    // Here is the Elven Tools demo minter smart contract on the devnet
    // The one we will be calling to mint the NFT
    const nftMinterSmartContract = 'erd1qqqqqqqqqqqqqpgq5za2pty2tlfqhj20z9qmrrpjmyt6advcgtkscm7xep';
    
    // We need an event to trigger the mint
    document
      .getElementById('button-mint')
      .addEventListener('click', async () => {
        updateTxHashContainer(false);

        const contractAddress = new Address(nftMinterSmartContract);

        const isGuarded = ElvenJS.storage.get('activeGuardian');

        const gasLimit = isGuardian ? 14050000 : 14000000;

        const factory = new SmartContractTransactionsFactory({
          config: new TransactionsFactoryConfig({ chainID: 'D' }),
        });

        // Again, we are preparing the data payload using different helpers
        // The function on smart contract is called 'mint'
        // It also takes one argument which is the amount to mint
        const tx = factory.createTransactionForExecute({
          sender: new Address(ElvenJS.storage.get('address')),
          contract: new Address(contractAddress),
          function: 'mint',
          nativeTransferAmount: parseAmount({ amount: '0.01', decimals: 18 }),
          gasLimit: BigInt(gasLimit),
          arguments: [new U32Value(1)],
        });

        try {
          // We still use the same ElvenJS function for that, 
          // only the transaction instance is different
          const transaction = await ElvenJS.signAndSendTransaction(tx);
        } catch (e) {
          throw new Error(e?.message);
        }
      });
  </script>
</body>
</html>

You can check more about the NFT tokens on the MultiversX blockchain in the docs here. Also, check the Elven Tools if you want to run your own PFP NFT collection on the MultiversX blockchain. Free and open source smart contract, CLI tool, and dApp template.

How to sign a custom message

You can sign a message using your address as the key. But you don't have to worry about internals here. There is one function for that where you need to provide the message.

<html>
<body>
  <button id="button-tx">Sign a message</button>

  <script type="module">
    import {
      ElvenJS,
    } from 'https://unpkg.com/elven.js@0.20.0/build/elven.js';

    // Init ElvenJs 
    const initElven = async () => {
      // Check all possible InitOptions at https://www.elvenjs.com/docs/sdk-reference.html#initialization
      await ElvenJS.init(...);
    }

    // Trigger the async init function 
    initElven();

    document
      .getElementById('button-tx')
      .addEventListener('click', async () => {
        try {
          await ElvenJS.signMessage('Elven Family is awesome!');
        } catch (e) {
          throw new Error(e?.message);
        }
      });
  </script>
</body>
</html>

How to verify a custom message on backend

For that you can use UserVerifier from @multiversx/sdk-wallet and SignableMessage from @multiversx/sdk-core.

import { UserVerifier } from "@multiversx/sdk-wallet";
import { SignableMessage } from "@multiversx/sdk-core";

const userVerifier = UserVerifier.fromAddress(addressOfUser);
const message = new SignableMessage({ message: Buffer.from("hello") });
const serializedMessage = message.serializeForSigning();
const messageSignature = Buffer.from("{signature_hex_here}", "hex");

userVerifier.verify(serializedMessage, messageSignature)

You can also use other SDKs. For example sdk-py.

How to query a smart contract

Smart contracts can offer read-only endpoints/functions that you can query. Let's omit the initialization and login using ElvenJS here. Check it in the first point.

We will query the minter smart contract to get the number of NFTs already minted by the wallet address. Such a functionality is programmed in the Elven Tools Smart Contract, and everyone can do the query.

<html>
<body>
  <button class="button" id="button-query" style="display: none;">Query SC</button>
  <div id="tx-hash-or-query-result" class="tx-hash-or-query-result"></div>

  <script type="module">
    // Just for the demo - helpers
    import {
      uiPending,
      updateTxHashContainer,
      updateQueryResultContainer
    } from './demo-ui-tools.js'

    // import ElvenJS parts from CDN 
    import {
      ElvenJS,
      Address,
      AddressValue,
      ContractFunction,
    } from 'https://unpkg.com/elven.js@0.20.0/build/elven.js';

    // Init ElvenJs 
    const initElven = async () => {
      // Check all possible InitOptions at https://www.elvenjs.com/docs/sdk-reference.html#initialization
      await ElvenJS.init(...);
    }

    // Trigger the async init function 
    initElven();

    // Here the same minter smart contract address as above. We will qery its function
    const nftMinterSmartContract = 'erd1qqqqqqqqqqqqqpgq5za2pty2tlfqhj20z9qmrrpjmyt6advcgtkscm7xep';
    
    document
      .getElementById('button-query')
      .addEventListener('click', async () => {
        try {
          updateQueryResultContainer();
          uiPending(true);

          // Here we use the queryContract function from ElvenJS
          // we need to pass required values
          const results = await ElvenJS.queryContract({
            address: new Address(nftMinterSmartContract),
            // The function on smart contract is called 'getMintedPerAddressTotal'
            func: new ContractFunction('getMintedPerAddressTotal'),
            // As an argument we need to pass our address to check in TypedValue type
            // Check whole list in the SDK reference section
            args: [new AddressValue(new Address(ElvenJS.storage.get('address')))]
          });

          uiPending(false);

          // Manual decoding of a simple type (number here), 
          // there will be additional tools for that
          // We know that it should be a number, so we can simply decode it using
          // helper functions like base64 to decimal hex
          // and then parse the hex number
          // You'll find an example of such helper tool in the example directory in the repository
          const hexVal = base64ToDecimalHex(results?.returnData?.[0]);
          updateQueryResultContainer(`➡️ The result of the query is: ${parseInt(hexVal, 16)}`);
        } catch (e) {
          uiPending(false);
          throw new Error(e?.message);
        }
      });
  </script>
</body>
</html>

How to verify the user on the backend side

You don't need to worry about this section if you don't plan to do any verification on the backend side of your application.

By default elven.js uses @multiversx/sdk-native-auth-client under the hood.

When logging in using one of the signing providers, a loginToken will be generated for you. It will then be used to acquire the signature. All with your account address will be then used to create accessToken. With that token, you can verify the user on the backend side, for example, using @multiversx/sdk-native-auth-server.

Transactions states and execution flow

When you use ElvenJS.signAndSendTransaction, a couple of callbacks will be called (You can define them in the ElvenJS.init), depending on the progress of current transactions.

These are:

  • onTxStart?: (transaction: Transaction) => void;
  • onTxSent?: (transaction: Transaction) => void;
  • onTxFinalized?: (transaction: Transaction) => void;
  • onTxFailure?: (transaction: Transaction, error: string) => void;

They are self-explanatory. onTxSent will fire after sending (the transaction object will not contain the signature yet). The onTxFinalized will fire after the transaction is finalized on chain (the transaction object will contain the signature).

In case of the error, the onTxFailure will additionally contain the error message.

Styling elements

There are a couple of elements that use external styles. Feel free to copy styles if needed. You can check the examples in the example directory.

No styles are attached to QR code elements and WalletConnect pairings list by default. But each piece has CSS classes that you can use.

The list of classes:

  • .elven-qr-code-deep-link
  • .elven-wc-pairings
  • .elven-wc-pairings-header
  • .elven-wc-pairing-item
  • .elven-wc-pairings-remove-btn
  • .elven-wc-pairing-item-description
  • .elven-wc-pairing-item-confirm-msessage

For more info, check the demo in the example directory. Please let us know if you need more styling flexibility and options. Describe your use cases here.

xPortal Discover integration

The Elven.js dApps can integrate with the xPortal Discover. You can read more on how to get your app accepted here.

You don't have to do anything. It should just work when your app is accepted and published in the xPortal Discover.

To test you integration first, check a special wrapper app: multiversx-apps-hub-testing.netlify.app

Working demos

The demos are linked on the homepage, but let's bring them also here:

You should be able to find the source code of each under the links.

Contents