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.
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.
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.
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>
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.
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>
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.
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>
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.
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.
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.
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
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.