Skip to main content
Turnkey provides institutional-grade key management for your applications. This guide shows you how to combine Turnkey’s secure signing with Biconomy’s MEE using the AbstractJS SDK for gasless, cross-chain transactions.

What We’re Building

By the end of this tutorial, you’ll have a backend service that can:
  1. Securely manage private keys with Turnkey
  2. Execute transactions without holding ETH for gas
  3. Perform operations across multiple chains
How it works: Turnkey stores and manages private keys in secure enclaves. When you need to sign a transaction, Turnkey signs it without ever exposing the private key. We then route the signed transaction through Biconomy’s MEE for gas abstraction.

Step 1: Install Dependencies

npm install @turnkey/sdk-server @turnkey/viem @biconomy/abstractjs viem dotenv
What these packages do:
  • @turnkey/sdk-server — Turnkey’s server SDK for key management
  • @turnkey/viem — Adapter to use Turnkey with viem
  • @biconomy/abstractjs — Biconomy’s SDK for smart accounts and MEE
  • viem — Ethereum library for building transactions

Step 2: Set Up Environment Variables

Create a .env file with your Turnkey credentials:
# Turnkey API endpoint
BASE_URL=https://api.turnkey.com

# Your API keys from Turnkey dashboard
API_PRIVATE_KEY=your_api_private_key
API_PUBLIC_KEY=your_api_public_key

# Your organization ID
ORGANIZATION_ID=your_organization_id

# The private key ID to sign with
SIGN_WITH=your_private_key_id
Never commit API keys to version control. Use environment variables or a secrets manager.

Step 3: Initialize Turnkey Client

Set up the Turnkey client that will handle all signing operations:
import { Turnkey } from "@turnkey/sdk-server";
import * as dotenv from "dotenv";

dotenv.config();

const turnkeyClient = new Turnkey({
  apiBaseUrl: process.env.BASE_URL,
  apiPrivateKey: process.env.API_PRIVATE_KEY,
  apiPublicKey: process.env.API_PUBLIC_KEY,
  defaultOrganizationId: process.env.ORGANIZATION_ID,
});

Step 4: Create a Wallet Client

Create a viem wallet client using the Turnkey account:
import { createWalletClient, http } from "viem";
import { createAccount } from "@turnkey/viem";
import { optimism } from "viem/chains";

// Create a Turnkey account adapter
const account = await createAccount({
  client: turnkeyClient.apiClient(),
  organizationId: process.env.ORGANIZATION_ID,
  signWith: process.env.SIGN_WITH,
});

// Create the wallet client for signing
const walletClient = createWalletClient({
  account,
  chain: optimism,
  transport: http(),
});
What’s happening here? The createAccount function creates an adapter that lets viem use Turnkey for signing. The actual private key never leaves Turnkey’s secure infrastructure.

Step 5: Sign EIP-7702 Authorization

Sign the authorization that upgrades the EOA to a smart account:
// Biconomy Nexus smart account implementation
const NEXUS_IMPLEMENTATION = "0x000000004F43C49e93C970E84001853a70923B03";

// Sign authorization to delegate to Nexus
const authorization = await walletClient.signAuthorization({
  contractAddress: NEXUS_IMPLEMENTATION,
  account,
});
What is this authorization? It’s a signed message saying “I authorize my wallet address to execute code from this smart contract.” This enables smart account features while keeping the same address.

Step 6: Create the Smart Account

Create a Nexus account that works across multiple chains:
import { 
  toMultichainNexusAccount, 
  getMEEVersion, 
  MEEVersion 
} from "@biconomy/abstractjs";
import { optimism, base } from "viem/chains";

const nexusAccount = await toMultichainNexusAccount({
  chainConfigurations: [
    {
      chain: optimism,
      transport: http(),
      version: getMEEVersion(MEEVersion.V2_1_0),
      // Use EOA address for EIP-7702 mode
      accountAddress: account.address,
    },
    {
      chain: base,
      transport: http(),
      version: getMEEVersion(MEEVersion.V2_1_0),
      accountAddress: account.address,
    },
  ],
  signer: walletClient,
});

Step 7: Create the MEE Client

The MEE client handles transaction execution with gas abstraction:
import { createMeeClient } from "@biconomy/abstractjs";

const meeClient = await createMeeClient({
  account: nexusAccount,
});

Step 8: Execute a Gasless Transaction

Now execute a transaction with gas paid in any ERC-20:
const USDC_OPTIMISM = "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85";

const { hash } = await meeClient.execute({
  // Required for EIP-7702 flow
  authorization,
  delegate: true,
  
  // Pay gas with USDC
  feeToken: {
    address: USDC_OPTIMISM,
    chainId: optimism.id,
  },
  
  // Your transaction instructions
  instructions: [
    {
      chainId: optimism.id,
      calls: [
        {
          to: "0xRecipientAddress",
          value: 0n,
        },
      ],
    },
  ],
});

console.log("Transaction submitted:", hash);

Step 9: Wait for Confirmation

const receipt = await meeClient.waitForSupertransactionReceipt({ hash });
console.log("Transaction confirmed:", receipt.hash);

Complete Example

Here’s a full Node.js script:
import { Turnkey } from "@turnkey/sdk-server";
import { createAccount } from "@turnkey/viem";
import { createWalletClient, http } from "viem";
import { optimism, base } from "viem/chains";
import {
  createMeeClient,
  toMultichainNexusAccount,
  getMEEVersion,
  MEEVersion,
} from "@biconomy/abstractjs";
import * as dotenv from "dotenv";

dotenv.config();

const NEXUS_IMPLEMENTATION = "0x000000004F43C49e93C970E84001853a70923B03";
const USDC_OPTIMISM = "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85";

async function main() {
  // 1. Initialize Turnkey
  const turnkey = new Turnkey({
    apiBaseUrl: process.env.BASE_URL!,
    apiPrivateKey: process.env.API_PRIVATE_KEY!,
    apiPublicKey: process.env.API_PUBLIC_KEY!,
    defaultOrganizationId: process.env.ORGANIZATION_ID!,
  });

  // 2. Create Turnkey account adapter
  const account = await createAccount({
    client: turnkey.apiClient(),
    organizationId: process.env.ORGANIZATION_ID!,
    signWith: process.env.SIGN_WITH!,
  });

  console.log("Using address:", account.address);

  // 3. Create wallet client
  const walletClient = createWalletClient({
    account,
    chain: optimism,
    transport: http(),
  });

  // 4. Sign EIP-7702 authorization
  const authorization = await walletClient.signAuthorization({
    contractAddress: NEXUS_IMPLEMENTATION,
    account,
  });

  console.log("Authorization signed");

  // 5. Create multichain Nexus account
  const nexusAccount = await toMultichainNexusAccount({
    chainConfigurations: [
      {
        chain: optimism,
        transport: http(),
        version: getMEEVersion(MEEVersion.V2_1_0),
        accountAddress: account.address,
      },
      {
        chain: base,
        transport: http(),
        version: getMEEVersion(MEEVersion.V2_1_0),
        accountAddress: account.address,
      },
    ],
    signer: walletClient,
  });

  // 6. Create MEE client
  const meeClient = await createMeeClient({ account: nexusAccount });

  // 7. Execute gasless transaction
  const { hash } = await meeClient.execute({
    authorization,
    delegate: true,
    feeToken: {
      address: USDC_OPTIMISM,
      chainId: optimism.id,
    },
    instructions: [
      {
        chainId: optimism.id,
        calls: [
          {
            to: "0x0000000000000000000000000000000000000000",
            value: 0n,
          },
        ],
      },
    ],
  });

  console.log("Transaction submitted:", hash);

  // 8. Wait for confirmation
  const receipt = await meeClient.waitForSupertransactionReceipt({ hash });
  console.log("Transaction confirmed:", receipt.hash);
}

main().catch(console.error);

Cross-Chain Execution

Execute on multiple chains with one signature:
const { hash } = await meeClient.execute({
  authorization,
  delegate: true,
  instructions: [
    {
      chainId: optimism.id,
      calls: [{ to: "0x...", value: 0n }],
    },
    {
      chainId: base.id,
      calls: [{ to: "0x...", value: 0n }],
    },
  ],
});
The MEE infrastructure handles routing transactions to each chain and managing execution order.

Key Takeaways

  1. Turnkey manages keys securely — Private keys never leave Turnkey’s secure infrastructure
  2. EIP-7702 adds smart features — The wallet gets upgraded without deploying a new contract
  3. MEE handles gas — Pay with any ERC-20 token or sponsor gas entirely
  4. Cross-chain ready — Add chains to chainConfigurations and include them in instructions

Next Steps