This guide shows how to bring Dynamic’s streamlined wallet authentication to Linera’s real-time Web3 applications, enabling seamless user onboarding for high-performance applications that serve thousands of simultaneous users without degrading experience or security. The Value: Whether you’re building real-time payment systems, on-chain games with live multiplayer action, decentralized physical infrastructure (DePIN) networks, or social data feeds that update instantly, Dynamic provides the frictionless authentication layer your users expect. Get embedded wallets, social logins, and enterprise-grade security working seamlessly within Linera’s WebAssembly environment. What You’ll Learn: The essential components needed to integrate Dynamic’s wallet authentication with Linera’s real-time applications - enabling users to connect instantly through familiar social logins and interact with time-sensitive transactions, live auctions, micro-rewards, and other real-time Web3 experiences that Linera enables. With these components, your users get instant, familiar authentication while your Linera applications leverage the platform’s unique ability to process time-sensitive transactions from many simultaneous users and devices.

Quick Start

Get started immediately with our complete working example that demonstrates Dynamic’s instant wallet authentication powering real-time interactions on Linera’s high-performance blockchain. Linera + Dynamic Real-Time Counter Demo This Vite demo project demonstrates the foundation for real-time Web3 applications:
  • Cross-origin isolation setup for Linera’s WebAssembly performance
  • Dynamic’s frictionless wallet authentication for instant user onboarding
  • Custom signer bridge enabling seamless transaction signing
  • Live counter application with real-time state updates - the building block for real-time games, live auctions, and instant payment systems

Essential Components

To power real-time Web3 applications with Dynamic’s authentication on Linera, you need three key components that enable instant user onboarding and seamless interaction with high-performance blockchain applications. At a glance:
  1. Cross-Origin Isolation Shim - JavaScript script that enables Dynamic’s social auth flows to work within Linera’s secure WebAssembly environment, ensuring users can authenticate instantly without security compromises
  2. Linera Adapter - TypeScript class that manages the connection between Dynamic wallets and Linera’s real-time blockchain infrastructure, handling the complex setup needed for high-throughput applications
  3. Dynamic Signer - TypeScript class that bridges Dynamic’s wallet signing with Linera’s time-sensitive transaction requirements, enabling fast signing for real-time interactions

1. Cross-Origin Isolation Shim

Linera’s real-time Web3 applications require cross-origin isolation for high-performance WebAssembly execution, but Dynamic’s seamless authentication flow uses iframes that can conflict with these requirements. This shim script resolves the conflict by automatically marking Dynamic’s auth iframes as credentialless, enabling instant social login without compromising Linera’s performance. Why it’s needed: Linera’s WebAssembly modules require crossOriginIsolated to be true for optimal performance in real-time applications, which is enabled by setting specific security headers (Cross-Origin-Embedder-Policy: credentialless, Cross-Origin-Opener-Policy: same-origin, and Cross-Origin-Resource-Policy: cross-origin). However, Dynamic’s authentication iframes from dynamicauth.com don’t have the required headers, causing them to be blocked. What it does: The shim intercepts iframe creation and automatically sets the credentialless attribute on Dynamic’s auth iframes, allowing them to load within the secure context required by Linera’s high-performance WebAssembly environment - essential for real-time games, instant payments, and live social feeds. Add this script to your index.html before any other scripts:
<script>
(() => {
  const matchesAuthHost = (url) => {
    try {
      const u = new URL(url, document.baseURI);
      return u.hostname === "relay.dynamicauth.com" || /\.dynamicauth\.com$/.test(u.hostname);
    } catch {
      return false;
    }
  };

  const markCredentialless = (iframe) => {
    try {
      iframe.credentialless = true;
    } catch { }
  };

  // Intercept direct assignments to iframe.src
  try {
    const desc = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, "src");
    if (desc && desc.set) {
      const originalSet = desc.set;
      Object.defineProperty(HTMLIFrameElement.prototype, "src", {
        configurable: true,
        enumerable: desc.enumerable,
        get: desc.get,
        set(value) {
          if (matchesAuthHost(value)) markCredentialless(this);
          return originalSet.call(this, value);
        },
      });
    }
  } catch { }

  // Intercept setAttribute('src', ...)
  try {
    const originalSetAttribute = Element.prototype.setAttribute;
    Element.prototype.setAttribute = function (name, value) {
      if (
        this instanceof HTMLIFrameElement &&
        name &&
        name.toLowerCase() === "src" &&
        matchesAuthHost(value)
      ) {
        markCredentialless(this);
      }
      return originalSetAttribute.call(this, name, value);
    };
  } catch { }

  // Fallback: observe added iframes and src attribute changes
  try {
    const checkAndMark = (iframe) => {
      const value = iframe.getAttribute("src") || iframe.src;
      if (value && matchesAuthHost(value)) markCredentialless(iframe);
    };
    const observer = new MutationObserver((mutations) => {
      for (const m of mutations) {
        if (m.type === "childList") {
          for (const node of m.addedNodes) {
            if (node instanceof HTMLIFrameElement) {
              checkAndMark(node);
            } else if (node && node.querySelectorAll) {
              node.querySelectorAll("iframe").forEach(checkAndMark);
            }
          }
        } else if (
          m.type === "attributes" &&
          m.target instanceof HTMLIFrameElement &&
          m.attributeName === "src"
        ) {
          checkAndMark(m.target);
        }
      }
    });
    observer.observe(document.documentElement, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ["src"],
    });
  } catch { }
})();
</script>

2. Linera Adapter

The Linera adapter manages the connection between Dynamic wallets and Linera’s real-time blockchain infrastructure, orchestrating the complex initialization needed for high-performance Web3 applications that can handle thousands of simultaneous users. Why it’s needed: Real-time Web3 applications on Linera require specific initialization steps (WASM loading, faucet setup, chain claiming) that must be perfectly coordinated with Dynamic’s instant authentication flow. The adapter provides a clean interface for managing this complex setup process, ensuring users can jump into real-time games, live auctions, or instant payment systems without delays. What it does:
  • Initializes Linera’s high-performance WebAssembly modules for real-time execution
  • Creates and connects Linera wallets using Dynamic’s authenticated user
  • Manages chain claiming and application setup for time-sensitive applications
  • Provides connection state management and callbacks optimized for real-time interactions
Create linera-adapter.ts:
import initLinera, {
  Faucet,
  Client,
  Wallet,
  Application,
} from "@linera/client";
import type { Wallet as DynamicWallet } from "@dynamic-labs/sdk-react-core";
import { DynamicSigner } from "./dynamic-signer";

export interface LineraProvider {
  client: Client;
  wallet: Wallet;
  faucet: Faucet;
  address: string;
  chainId: string;
}

export class LineraAdapter {
  private static instance: LineraAdapter | null = null;
  private provider: LineraProvider | null = null;
  private application: Application | null = null;
  private wasmInitPromise: Promise<unknown> | null = null;
  private connectPromise: Promise<LineraProvider> | null = null;
  private onConnectionChange?: () => void;

  private constructor() {}

  static getInstance(): LineraAdapter {
    if (!LineraAdapter.instance) LineraAdapter.instance = new LineraAdapter();
    return LineraAdapter.instance;
  }

  async connect(
    dynamicWallet: DynamicWallet,
    rpcUrl: string
  ): Promise<LineraProvider> {
    if (this.provider) return this.provider;
    if (this.connectPromise) return this.connectPromise;

    if (!dynamicWallet) {
      throw new Error("Dynamic wallet is required for Linera connection");
    }

    try {
      this.connectPromise = (async () => {
        const { address } = dynamicWallet;
        console.log("🔗 Connecting with Dynamic wallet:", address);

        try {
          if (!this.wasmInitPromise) this.wasmInitPromise = initLinera();
          await this.wasmInitPromise;
          console.log("✅ Linera WASM modules initialized successfully");
        } catch (e) {
          const msg = e instanceof Error ? e.message : String(e);
          if (msg.includes("storage is already initialized")) {
            console.warn(
              "⚠️ Linera storage already initialized; continuing without re-init"
            );
          } else {
            throw e;
          }
        }

        const faucet = await new Faucet(rpcUrl);
        const wallet = await faucet.createWallet();
        const chainId = await faucet.claimChain(wallet, address);

        const signer = await new DynamicSigner(dynamicWallet);
        const client = await new Client(wallet, signer);
        console.log("✅ Linera wallet created successfully!");

        this.provider = {
          client,
          wallet,
          faucet,
          chainId,
          address: dynamicWallet.address,
        };

        this.onConnectionChange?.();
        return this.provider;
      })();

      const provider = await this.connectPromise;
      return provider;
    } catch (error) {
      console.error("Failed to connect to Linera:", error);
      throw new Error(
        `Failed to connect to Linera network: ${
          error instanceof Error ? error.message : "Unknown error"
        }`
      );
    } finally {
      this.connectPromise = null;
    }
  }

  async setApplication(appId: string) {
    if (!this.provider) throw new Error("Not connected to Linera");
    if (!appId) throw new Error("Application ID is required");

    const application = await this.provider.client
      .frontend()
      .application(appId);

    if (!application) throw new Error("Failed to get application");
    console.log("✅ Linera application set successfully!");
    this.application = application;
    this.onConnectionChange?.();
  }

  async queryApplication<T>(query: object): Promise<T> {
    if (!this.application) throw new Error("Application not set");

    const result = await this.application.query(JSON.stringify(query));
    const response = JSON.parse(result);

    console.log("✅ Linera application queried successfully!");
    return response as T;
  }

  getProvider(): LineraProvider {
    if (!this.provider) throw new Error("Provider not set");
    return this.provider;
  }

  getFaucet(): Faucet {
    if (!this.provider?.faucet) throw new Error("Faucet not set");
    return this.provider.faucet;
  }

  getWallet(): Wallet {
    if (!this.provider?.wallet) throw new Error("Wallet not set");
    return this.provider.wallet;
  }

  getApplication(): Application {
    if (!this.application) throw new Error("Application not set");
    return this.application;
  }

  isChainConnected(): boolean {
    return this.provider !== null;
  }

  isApplicationSet(): boolean {
    return this.application !== null;
  }

  onConnectionStateChange(callback: () => void): void {
    this.onConnectionChange = callback;
  }

  offConnectionStateChange(): void {
    this.onConnectionChange = undefined;
  }

  reset(): void {
    this.application = null;
    this.provider = null;
    this.connectPromise = null;
    this.onConnectionChange?.();
  }
}

// Export singleton instance
export const lineraAdapter = LineraAdapter.getInstance();

3. Dynamic Signer

The Dynamic signer bridges Dynamic’s familiar wallet signing experience with Linera’s time-sensitive transaction requirements, enabling users to approve real-time payments, game moves, auction bids, and other time-critical operations seamlessly. Why it’s needed: Real-time applications require fast, reliable transaction signing that doesn’t interrupt the user experience. Linera expects a specific Signer interface for optimal performance, while Dynamic wallets provide familiar signing flows that users already trust. The signer bridges these two worlds, ensuring smooth authentication for time-sensitive transactions. What it does:
  • Implements Linera’s Signer interface using Dynamic’s trusted wallet capabilities
  • Handles proper message signing without double-hashing (critical for real-time transaction validity)
  • Provides address verification and key management optimized for high-frequency operations
  • Uses personal_sign directly to minimize signing delays in real-time applications
Create dynamic-signer.ts:
import type { Signer } from "@linera/client";
import type { Wallet as DynamicWallet } from "@dynamic-labs/sdk-react-core";
import { isEthereumWallet } from "@dynamic-labs/ethereum";

export class DynamicSigner implements Signer {
  private dynamicWallet: DynamicWallet;

  constructor(dynamicWallet: DynamicWallet) {
    this.dynamicWallet = dynamicWallet;
  }

  async address(): Promise<string> {
    return this.dynamicWallet.address;
  }

  async containsKey(owner: string): Promise<boolean> {
    const walletAddress = this.dynamicWallet.address;
    return owner.toLowerCase() === walletAddress.toLowerCase();
  }

  async sign(owner: string, value: Uint8Array): Promise<string> {
    const address: `0x${string}` = owner as `0x${string}`;
    const primaryWallet = this.dynamicWallet.address;

    if (!primaryWallet || !owner) {
      throw new Error("No primary wallet found");
    }

    if (owner.toLowerCase() !== primaryWallet.toLowerCase()) {
      throw new Error("Owner does not match primary wallet");
    }

    try {
      const msgHex: `0x${string}` = `0x${uint8ArrayToHex(value)}`;

      // IMPORTANT: The value parameter is already pre-hashed, and the standard `signMessage`
      // method would hash it again, resulting in a double-hash. To avoid this, we bypass
      // the standard signing flow and use `personal_sign` directly on the wallet client.
      // DO NOT USE: this.dynamicWallet.signMessage(msgHex) - it would cause double-hashing

      // Note: First cast the wallet to an Ethereum wallet to get the wallet client
      if (!isEthereumWallet(this.dynamicWallet)) throw new Error();
      const walletClient = await this.dynamicWallet.getWalletClient();
      const signature = await walletClient.request({
        method: "personal_sign",
        params: [msgHex, address],
      });

      if (!signature) throw new Error("Failed to sign message");
      return signature;
    } catch (error: any) {
      console.error("Failed to sign message:", error);
      throw new Error(
        `Dynamic signature request failed: ${error?.message || error}`
      );
    }
  }
}

function uint8ArrayToHex(bytes: Uint8Array): string {
  return Array.from(bytes)
    .map((b: number) => b.toString(16).padStart(2, "0"))
    .join("");
}

Implementation Notes

Framework Compatibility

Linera’s high-performance WebAssembly architecture currently requires Vite for optimal real-time application development. Vite provides the fastest hot-reload experience for iterating on real-time games, live auctions, and instant payment interfaces.
npx create-dynamic-app@latest my-realtime-linera-app --framework react --chains ethereum

Critical Setup Requirements

  1. Your real-time application must serve with these security headers for Linera’s WebAssembly performance:
    "Cross-Origin-Embedder-Policy": "credentialless",
    "Cross-Origin-Opener-Policy": "same-origin",
    "Cross-Origin-Resource-Policy": "cross-origin",
    
  2. The iframe shim must be loaded before any other scripts to ensure instant authentication without blocking real-time interactions
  3. Configure your Linera RPC URL and application IDs for your specific deployment (testnet recommended for development of real-time applications)

Integration Flow for Real-Time Applications

  1. User authenticates instantly through Dynamic’s social login or embedded wallets
  2. Linera adapter connects using the authenticated wallet and claims a microchain for real-time operations
  3. Dynamic signer handles all transaction signing with minimal latency for time-sensitive operations
  4. Your application can now process real-time payments, game state updates, auction bids, and social interactions using Dynamic’s familiar UX on Linera’s high-performance infrastructure