Multi-chain EVM Wallets

Who can use this feature

Overview

Levain currently supports creation of multi-chain EVM wallets programmatically. This means that you can create and manage wallets for multiple EVM-compatible blockchains using a single set of keys and salt. Currently, you can create such wallets programmatically through the Levain GraphQL APIs and not via Levain App.

You must be an Admin to proceed, as this guide requires you to create wallets.

Example

We currently have an example repository on GitHub at 005-create-wallet-multichain-evm.ts with the following example with TypeScript and Express.

Update your .env file with the API key, your organization ID, and other required environment variables listed in the file.

When you create wallets programmatically via the Levain GraphQL API, you are responsible for managing the keys and the wallets. You must ensure that you have backed up the keys and the salt used for creating the wallets. If you lose the keys or the salt, you will not be able to recover the wallets.

Most importantly, using the right salt is crucial for creating wallets with the same address across different EVM chains. This is because the salt is used to derive the address for the wallet across different chains. If you want to deploy a multi-sig wallet with the same address across different chains, you must use the same salt for all the wallets.

005-create-wallet-multichain-evm.tsTypeScript
import express from 'express';
import { levainGraph } from './client';
import { KeyType, WalletType } from '@levain/wallet-sdk';
import { generate } from '@levain/levain-client-keygen';
import { config } from './config';
import crypto from 'crypto';

const router = express.Router();

// Endpoint to create wallets programatically
router.get('/create-wallet-multichain-evm', async (req, res) => {
  try {
    const orgId = config.LEVAIN_ORG_ID;

    const networksCaip2Ids = [
      'caip2:eip155:11155111', // Ethereum Sepolia Testnet
      'caip2:eip155:80001', // Polygon Mumbai Testnet
      'caip2:eip155:97', // BNB Chain Testnet
    ];
    const walletPassword = 'aStrongEnoughPassword@1337!'; // Used for encrypting the user signing key for the first wallet approver
    const salt = crypto.randomBytes(32).toString('hex'); // Used for wallet creation for deterministic address across EVM chains; 32 bytes = 64 char

    // Create the user signing key & backup key
    const signingKeyPair = generate(walletPassword);
    const backupKeyPair = generate(walletPassword);

    console.log('Please backup the following together with your wallet password');
    console.log('Created user signing key:');
    console.log(signingKeyPair);
    console.log(JSON.stringify(signingKeyPair, null, 2));
    console.log('Created user backup key:');
    console.log(backupKeyPair);
    console.log(JSON.stringify(backupKeyPair, null, 2));

    console.log(
      'Salt used (please keep this so that you can deploy a multi-sig wallet with the same address across other chains):',
    );
    console.log(`0x${salt}`);

    for (let i = 0; i < networksCaip2Ids.length; i++) {
      // Submit the public keys to Levain
      const signingKey = await levainGraph.createKey({
        orgId,
        type: KeyType.ScalarNeutered,
        publicKey: signingKeyPair.publicKey,
        retrieveIfExists: false,
      });
      const backupKey = await levainGraph.createKey({
        orgId,
        type: KeyType.ScalarNeutered,
        publicKey: backupKeyPair.publicKey,
        retrieveIfExists: false,
      });
      const walletPasswordEncryptionKey = await levainGraph.createKey({
        orgId,
        type: KeyType.Rsa,
        retrieveIfExists: false,
      });

      const wallet = await levainGraph.createWallet({
        orgId,
        name: 'API-created Multi-chain EVM Wallet (with salt)',
        type: WalletType.EvmContractSimpleMultiSig,
        network: networksCaip2Ids[i],
        mainKey: {
          keyId: signingKey.keyId,
          passwordRecoveryKeyId: walletPasswordEncryptionKey.keyId,
          encryptedPrivateKey: signingKeyPair.encryptedPrivateKey,
        },
        backupKey: {
          keyId: backupKey.keyId,
        },
        use1167Proxy: true,
        salt: `0x${salt}`,
      });

      console.log(JSON.stringify(backupKeyPair, null, 2));
      console.log('Created encryption key:');
      console.log(JSON.stringify(walletPasswordEncryptionKey, null, 2));
      console.log(signingKey, backupKey, walletPasswordEncryptionKey);

      console.log('Created wallet:');
      console.log(JSON.stringify(wallet, null, 2));
    }

    // HTTP response
    res.status(200).json({
      message:
        'Successfully created multi-chain EVM wallets using your keys. Please go to https://app.levain.tech/ to see your wallets.',
    });
  } catch (error) {
    console.log(error);
    res.status(500).json({ error: error });
  }
});

export default router;