---
title: Embedded Wallet Integration
description: Integrate the hosted embedded wallet into a Thru web app and use
  `connect()`, `getSigningContext()`, and `signTransaction()` correctly.
source_url:
  html: https://thru.org/docs/wallet/embedded-wallet-integration/
  md: https://thru.org/docs/wallet/embedded-wallet-integration.md
---

# Embedded Wallet Integration

Use this page when you want one reference page for wiring the hosted embedded wallet into a web app.

## Use This When

- you want the recommended React integration path
- you need to understand the smallest public wallet contract a dApp uses
- you want to connect a dApp, inspect the wallet signing contract, sign a transaction, and then submit it with `@thru/sdk`

## Choose The Right Package Layer

| Entry point | Use it when | Avoid it when |
| - | - | - |
| `@thru/wallet/react` | Your app already uses React and you want provider plus hooks. | You are not using React. |
| `@thru/wallet` | You want a browser-side SDK without React. | You want React provider state or hooks. |
| `@thru/wallet/native/react` | You are integrating the wallet in a React Native app. | You are building a browser-only dApp. |

## Install

For the recommended React path:

```bash
npm install @thru/wallet @thru/sdk
```

For a non-React integration:

```bash
npm install @thru/wallet @thru/sdk
```

## Minimal React Setup

Wrap the app with `ThruProvider` and point it at the hosted wallet iframe.

```tsx
import { ThruProvider } from "@thru/wallet/react";

export function App({ children }: { children: React.ReactNode }) {
  return (
    <ThruProvider
      config={{
        iframeUrl: "https://wallet.thru.org/embedded",
        rpcUrl: "https://grpc-web.alphanet.thruput.org",
      }}
    >
      {children}
    </ThruProvider>
  );
}
```

## Minimal Connect Flow

`connect()` is the dApp entrypoint. The wallet resolves the request against the iframe, origin, and app metadata.

```tsx
import { useWallet } from "@thru/wallet/react";

export function ConnectButton() {
  const { connect, isConnected, isConnecting } = useWallet();

  if (isConnected) {
    return <button disabled>Wallet connected</button>;
  }

  return (
    <button
      onClick={() =>
        connect({
          metadata: {
            appId: window.location.origin,
            appName: "My Thru App",
            appUrl: window.location.origin,
          },
        })
      }
      disabled={isConnecting}
    >
      {isConnecting ? "Connecting..." : "Connect wallet"}
    </button>
  );
}
```

## Minimal Sign-And-Submit Flow

Use `getSigningContext()` before you build the transaction. The selected wallet account is the managed account the user sees, but the actual fee payer and signer can be the embedded manager profile.

`signTransaction()` accepts either:

- signing payload bytes from `transaction.toWireForSigning()`
- raw transaction bytes from `transaction.toWire()`

It always returns canonical raw transaction bytes encoded as base64, ready for direct submission.

```tsx
import { useThru, useWallet } from "@thru/wallet/react";

function bytesToBase64(bytes: Uint8Array): string {
  let binary = "";
  for (let i = 0; i < bytes.length; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

function base64ToBytes(value: string): Uint8Array {
  const binary = atob(value);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes;
}

export function SubmitSignedTransaction({
  transaction,
}: {
  transaction: { toWireForSigning(): Uint8Array; toWire(): Uint8Array };
}) {
  const { thru } = useThru();
  const { wallet } = useWallet();

  return (
    <button
      onClick={async () => {
        if (!thru || !wallet) throw new Error("Wallet not ready");

        const signingContext = await wallet.getSigningContext();

        console.log("Managed account", signingContext.selectedAccountPublicKey);
        console.log("Network signer", signingContext.signerPublicKey);
        console.log("Fee payer", signingContext.feePayerPublicKey);

        // Build the transaction with signingContext.feePayerPublicKey rather than
        // assuming the selected managed account is also the network signer.
        const signingPayloadBase64 = bytesToBase64(transaction.toWireForSigning());
        const rawSignedBase64 = await wallet.signTransaction(signingPayloadBase64);
        const signature = await thru.transactions.send(base64ToBytes(rawSignedBase64));

        console.log("Submitted transaction", signature);
      }}
    >
      Sign and submit
    </button>
  );
}
```

## Signing Context

Call `wallet.getSigningContext()` before building transactions that need exact signer or fee-payer information.

The current embedded wallet contract returns a managed-fee-payer shape:

```ts
type ThruSigningContext = {
  mode: "managed_fee_payer";
  selectedAccountPublicKey: string | null;
  feePayerPublicKey: string;
  signerPublicKey: string;
  acceptedInputEncodings: [
    "signing_payload_base64",
    "raw_transaction_base64",
  ];
  outputEncoding: "raw_transaction_base64";
};
```

Use it to answer two questions before signing:

- which managed account the user thinks they are acting as
- which public key actually signs and pays for network submission

## What The dApp Owns

The dApp is responsible for:

- deciding when to call `connect()`
- calling `getSigningContext()` before building transactions that depend on signer or fee-payer identity
- building the unsigned transaction bytes with the correct fee payer
- calling `signTransaction()` with a base64 payload
- submitting the returned raw transaction bytes directly
- showing the right status while the wallet UI is open

The wallet is responsible for:

- presenting connection and approval UI
- unlocking with passkey if required
- selecting the current wallet account
- returning the current signing contract for the embedded environment
- returning canonical raw transaction bytes after signing

## Important Assumptions

- the iframe URL must be a trusted wallet origin: `https://wallet.thru.org` or localhost during development
- `signTransaction()` expects a non-empty base64 payload in one of the accepted encodings
- `getSigningContext().feePayerPublicKey` is the source of truth for the network fee payer
- the wallet contract is intentionally narrow: connect, disconnect, account selection, and transaction signing

## Open Next

- [Approval and Signing](https://thru.org/docs/wallet/approval-and-signing.md) to understand what happens after a dApp calls `connect()` or `signTransaction()`
- [Troubleshooting](https://thru.org/docs/wallet/troubleshooting.md) if the request flow stalls or the transaction never appears on-chain
