Introduction

You can use the SDK to headlessly export the private key or key shares of an embedded wallet. You can also export the private key in ‘offline mode’.

We will use methods available on the wallet connector, so your setup code should look something like this:

import { useDynamicContext } from '@dynamic-labs/sdk-react-core';

import { DynamicWaasEVMConnector } from "@dynamic-labs/waas-evm";

const { primaryWallet } = useDynamicContext();

if (!primaryWallet?.address) {
  setErrorMessage("Please create a wallet first");
  return;
}

try {
  const connector = primaryWallet?.connector as DynamicWaasEVMConnector;
  // Add your method calls here
} catch (error) {
  setErrorMessage("Error...");
}

Export Keyshares

const keyShares = await connector.exportClientKeyshares({
  accountAddress: primaryWallet?.address,
});

Export Private Key

const privateKey = await connector.exportPrivateKey({
  accountAddress: primaryWallet?.address,
});

Export Private Key in Offline Mode

const privateKey = await connector.offlineExportPrivateKey({
  accountAddress: primaryWallet?.address,
});

Full code example

import React, { useState } from "react";
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";
import { useDynamicWaas } from "@dynamic-labs/sdk-react-core";
import { DynamicWaasEVMConnector } from "@dynamic-labs/waas-evm";
import { isEthereumWallet } from "@dynamic-labs/ethereum";

const MPCDemo: React.FC = () => {
  const { user, handleLogOut, primaryWallet } = useDynamicContext();
  const { createWalletAccount, importPrivateKey } = useDynamicWaas();

  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [errorMessage, setErrorMessage] = useState<string>("");
  const [privateKeyInput, setPrivateKeyInput] = useState<string>("");
  const [exportedKeyShares, setExportedKeyShares] = useState<string>("");
  const [exportedPrivateKey, setExportedPrivateKey] = useState<string>("");
  const [signedMessage, setSignedMessage] = useState<string>("");
  const handleCreateWallet = async () => {
    try {
      setIsLoading(true);
      setErrorMessage("");
      await createWalletAccount();
    } catch (error: any) {
      console.error("Error creating wallet:", error);
      setErrorMessage(`Error creating wallet: ${error.message}`);
    } finally {
      setIsLoading(false);
    }
  };

  const handleSignMessage = async () => {
    if (!primaryWallet?.address || !isEthereumWallet(primaryWallet)) {
      setErrorMessage("Please create a wallet first");
      return;
    }

    try {
      setIsLoading(true);
      setErrorMessage("");
      const provider = await primaryWallet.getWalletClient();

      const message = "Hello, world!";
      const signature = await provider?.signMessage({
        message: message,
      });
      setSignedMessage(signature ?? "");
    } catch (error: any) {
      console.error("Error signing message:", error);
      setErrorMessage(`Error signing message: ${error.message}`);
    } finally {
      setIsLoading(false);
    }
  };

  const handleImportPrivateKey = async () => {
    if (!privateKeyInput || !primaryWallet?.address) {
      setErrorMessage(
        "Please enter a private key and ensure wallet is created"
      );
      return;
    }

    try {
      setIsLoading(true);
      setErrorMessage("");
      await importPrivateKey({
        chainName: "EVM",
        privateKey: privateKeyInput,
      });
      setErrorMessage("Private key imported successfully");
    } catch (error: any) {
      console.error("Error importing private key:", error);
      setErrorMessage(`Error importing private key: ${error.message}`);
    } finally {
      setIsLoading(false);
    }
  };

  const handleExportKeyShares = async () => {
    if (!primaryWallet?.address) {
      setErrorMessage("Please create a wallet first");
      return;
    }

    try {
      setIsLoading(true);
      setErrorMessage("");
      const connector = primaryWallet?.connector as DynamicWaasEVMConnector;
      const keyShares = await connector.exportClientKeyshares({
        accountAddress: primaryWallet?.address,
      });
      setExportedKeyShares(JSON.stringify(keyShares, null, 2));
    } catch (error: any) {
      console.error("Error exporting key shares:", error);
      setErrorMessage(`Error exporting key shares: ${error.message}`);
    } finally {
      setIsLoading(false);
    }
  };

  const handleExportPrivateKey = async () => {
    if (!primaryWallet?.address) {
      setErrorMessage("Please create a wallet first");
      return;
    }

    try {
      setIsLoading(true);
      setErrorMessage("");
      const connector = primaryWallet?.connector as DynamicWaasEVMConnector;
      const privateKey = await connector.exportPrivateKey({
        accountAddress: primaryWallet?.address,
      });
      setExportedPrivateKey(privateKey);
    } catch (error: any) {
      console.error("Error exporting private key:", error);
      setErrorMessage(`Error exporting private key: ${error.message}`);
    } finally {
      setIsLoading(false);
    }
  };

  if (!user) {
    return (
      <div className="card">
        <h2>Please log in to use the MPC wallet features</h2>
        <p>
          You need to authenticate with Dynamic to access MPC wallet
          functionality
        </p>
      </div>
    );
  }

  return (
    <div>
      <div className="card">
        <h2>TSS-MPC Wallet Demo</h2>
        <p>Logged in as: {user.email || user.username || "Unknown user"}</p>
        <button onClick={handleLogOut}>Log Out</button>
      </div>

      <div className="card">
        <h2>Create MPC Wallet</h2>
        <button onClick={handleCreateWallet} disabled={isLoading}>
          Create New Wallet
        </button>

        {primaryWallet?.address && (
          <div>
            <p>Wallet Address:</p>
            <div className="address">{primaryWallet?.address}</div>
          </div>
        )}
      </div>

      {primaryWallet?.address && (
        <>
          <div className="card">
            <h2>Import Private Key</h2>
            <input
              type="text"
              value={privateKeyInput}
              onChange={(e) => setPrivateKeyInput(e.target.value)}
              placeholder="Enter private key"
              className="input"
            />
            <button onClick={handleImportPrivateKey} disabled={isLoading}>
              Import Private Key
            </button>
          </div>

          <div className="card">
            <h2>Export Key Shares</h2>
            <button onClick={handleExportKeyShares} disabled={isLoading}>
              Export Key Shares
            </button>
            {exportedKeyShares && (
              <div className="output">
                <pre>{exportedKeyShares}</pre>
              </div>
            )}
          </div>

          <div className="card">
            <h2>Export Private Key</h2>
            <button onClick={handleExportPrivateKey} disabled={isLoading}>
              Export Private Key
            </button>
            {exportedPrivateKey && (
              <div className="output">
                <pre>{exportedPrivateKey}</pre>
              </div>
            )}
          </div>
        </>
      )}

      <div className="card">
        <h2>Sign Message</h2>
        <button onClick={handleSignMessage} disabled={isLoading}>
          Sign Message
        </button>
        {signedMessage && (
          <div className="output">
            <pre>{signedMessage}</pre>
          </div>
        )}
      </div>

      {errorMessage && (
        <div className="card" style={{ backgroundColor: "#ffeeee" }}>
          <h2>Error</h2>
          <p>{errorMessage}</p>
        </div>
      )}
    </div>
  );
};

export default MPCDemo;