diff --git a/.github/workflows/webgl-package.yml b/.github/workflows/webgl-package.yml
new file mode 100644
index 0000000..670217c
--- /dev/null
+++ b/.github/workflows/webgl-package.yml
@@ -0,0 +1,18 @@
+name: WebGL package
+on:
+ pull_request:
+ paths: ['WebGL~/**', '.github/workflows/webgl-package.yml']
+ push:
+ branches: [main]
+ paths: ['WebGL~/**', '.github/workflows/webgl-package.yml']
+permissions:
+ contents: read
+jobs:
+ distribution:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ - run: node --test 'WebGL~/Tools/install.test.mjs'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e39f3be..2949120 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## WebGL preview — 2026-09-16
+
+- Publish the current complete Pera/Lute and optional TxnLab/MetaMask Embedded
+ Wallets package under `WebGL~`, with configurable in-game presentation and
+ verified C# session acknowledgment. Runtime members remain identical to the
+ canonical September integration; this is a reusable distribution update.
+- Add a dependency-free, offline installer that verifies hashes, preserves
+ Unity metadata, binds game receipts and refuses unreviewed replacements.
+- Add an email/shared-identity setup guide, fullscreen sample and explicit
+ validation limits. Native exports remain outside this package.
+- Preserve the legacy v1 root UPM package, archive its documentation and pin its
+ installer to v1.2.0. Existing legacy pull requests are unchanged.
+
## [1.2.0] - 2026-07-10
### Added
diff --git a/Installer~/BlockmakerInstaller.cs b/Installer~/BlockmakerInstaller.cs
index 3d1ebe6..1a934a0 100644
--- a/Installer~/BlockmakerInstaller.cs
+++ b/Installer~/BlockmakerInstaller.cs
@@ -5,7 +5,7 @@
///
/// Drop this file into your Unity project's Assets folder.
-/// It will install the Blockmaker SDK and its dependencies automatically.
+/// Legacy v1.2.0 installer. New WebGL games should use WebGL~/README.md.
/// You can delete this file after installation.
///
[InitializeOnLoad]
@@ -79,7 +79,7 @@ static void Install(string manifestPath)
var braceIdx = content.IndexOf('{', depsIdx);
if (braceIdx >= 0)
{
- var insert = "\n \"com.blockmaker.sdk\": \"https://github.com/blockmaker-ai/blockmaker-unity.git\",\n \"com.nethereum.unity\": \"4.19.2\",\n \"com.reown.sign.nethereum\": \"1.6.0\",\n \"com.reown.sign.unity\": \"1.6.0\",";
+ var insert = "\n \"com.blockmaker.sdk\": \"https://github.com/blockmaker-ai/blockmaker-unity.git#v1.2.0\",\n \"com.nethereum.unity\": \"4.19.2\",\n \"com.reown.sign.nethereum\": \"1.6.0\",\n \"com.reown.sign.unity\": \"1.6.0\",";
content = content.Substring(0, braceIdx + 1) + insert + content.Substring(braceIdx + 1);
}
}
diff --git a/LEGACY_V1.md b/LEGACY_V1.md
new file mode 100644
index 0000000..4acd1dc
--- /dev/null
+++ b/LEGACY_V1.md
@@ -0,0 +1,304 @@
+# Legacy v1 documentation
+
+This is the historical v1.2.0 guide. Its server OTP/Magic and native-platform
+claims do not describe the current WebGL package. Do not use these email
+paths for new integrations. Start with the [current SDK guide](README.md).
+Existing projects can pin v1.2.0 while planning an explicit migration.
+
+# Blockmaker Unity SDK
+
+Add Algorand wallet auth and transaction signing to your Unity game. Open-source, free, built for Unity 6+.
+
+## Supported Wallets
+
+| Wallet | How it works |
+|--------|-------------|
+| **Pera** | QR code scan (WalletConnect v1). On WebGL the SDK uses Pera's official [@perawallet/connect](https://github.com/perawallet/connect); mobile browsers get an "Open in wallet app" deep link |
+| **Defly** | QR code scan (WalletConnect v2), mobile deep link per Defly's official SDK |
+| **X-Chain** *(beta)* | Any EVM wallet via [xChain Accounts](https://github.com/algorandfoundation/xchain-accounts). On WebGL the SDK discovers every wallet installed in the player's browser (EIP-6963) and shows it in the picker with its real icon — MetaMask, Rainbow, Coinbase Wallet, Trust + more; native builds connect via WalletConnect QR. Off by default — set `enableEvmXChain` in your BlockmakerConfig |
+| **Email** | Server-managed OTP out of the box (all platforms); optionally Magic SDK on WebGL with your own key |
+
+> Currently Algorand **mainnet** only.
+
+## Installation
+
+### One-click install
+
+1. **[Download BlockmakerInstaller.cs](https://raw.githubusercontent.com/blockmaker-ai/blockmaker-unity/main/Installer~/BlockmakerInstaller.cs)** (right-click → Save As)
+2. Drag it into your Unity project (anywhere in the Assets folder)
+3. A dialog will appear — click **Install**
+4. Unity installs everything automatically. Done.
+
+You can delete the installer file after installation.
+
+
+Manual install (if you prefer)
+
+Open `Packages/manifest.json` in your project folder and add:
+
+**In `"dependencies"`:**
+```json
+"com.blockmaker.sdk": "https://github.com/blockmaker-ai/blockmaker-unity.git#v1.2.0",
+"com.nethereum.unity": "4.19.2",
+"com.reown.sign.nethereum": "1.6.0",
+"com.reown.sign.unity": "1.6.0",
+```
+
+**At the bottom of the file, before the last `}`:**
+```json
+,
+"scopedRegistries": [
+ {
+ "name": "OpenUPM",
+ "url": "https://package.openupm.com",
+ "scopes": ["com.reown", "com.nethereum", "com.cysharp"]
+ }
+]
+```
+
+Save and reopen Unity.
+
+
+
+## Setup
+
+Go to **Blockmaker > Setup Scene** in the menu bar. This automatically:
+
+- Creates a BlockmakerConfig asset
+- Adds BlockmakerAuth with the config wired up
+- Adds the auth UI with all UXML assets pre-assigned
+- Creates PanelSettings if needed
+- Adds a Connect Wallet button
+
+Hit Play — the wallet system is ready. No API keys or signups needed.
+
+### Sample scene
+
+Want to see it working first? In **Package Manager > Blockmaker SDK > Samples**, import the **Wallet Demo**. Add the `WalletDemo` script to an empty GameObject and hit Play — includes wallet connection and transaction signing.
+
+## Usage
+
+### Connect a wallet
+
+```csharp
+using Blockmaker;
+
+// Pera (QR code)
+BlockmakerAuth.Instance.ConnectWallet("Pera",
+ identity => Debug.Log($"Connected: {identity.Address}"),
+ error => Debug.Log(error)
+);
+
+// Defly (QR code)
+BlockmakerAuth.Instance.ConnectWallet("Defly",
+ identity => Debug.Log($"Connected: {identity.Address}"),
+ error => Debug.Log(error)
+);
+
+// X-Chain — any EVM wallet (beta). On WebGL this auto-picks the player's
+// last-used browser wallet; native builds show a WalletConnect QR.
+// Off by default — set enableEvmXChain in your BlockmakerConfig.
+BlockmakerAuth.Instance.ConnectEvm(
+ identity => Debug.Log($"Connected: {identity.Address}"), // Algorand address, derived from the EVM key
+ error => Debug.Log(error)
+);
+
+// Email login — built-in server OTP (works everywhere, no keys needed)
+BlockmakerAuth.Instance.RequestEmailOTP("player@example.com",
+ () => Debug.Log("Code sent — check your inbox"),
+ error => Debug.Log(error)
+);
+BlockmakerAuth.Instance.VerifyEmailOTP("player@example.com", "123456",
+ identity => Debug.Log($"Signed in: {identity.Address}"),
+ error => Debug.Log(error)
+);
+
+// Email login via Magic SDK — WebGL only, requires magicPublishableKey in your config
+// (without a key the built-in auth UI automatically uses the OTP flow above)
+BlockmakerAuth.Instance.ConnectMagicEmail("player@example.com",
+ identity => Debug.Log($"Signed in: {identity.Address}"),
+ error => Debug.Log(error)
+);
+```
+
+### Async/await
+
+```csharp
+var identity = await BlockmakerAuth.Instance.ConnectWalletAsync("Pera");
+var evmIdentity = await BlockmakerAuth.Instance.ConnectEvmAsync();
+```
+
+### Sign transactions
+
+```csharp
+var identity = BlockmakerAuth.Instance.Identity;
+
+// Single transaction
+yield return identity.SignTransaction(unsignedTxnBase64,
+ signed => Debug.Log("Signed!"),
+ error => Debug.Log(error)
+);
+
+// Atomic group
+yield return identity.SignTransactions(unsignedTxnsBase64,
+ signed => Debug.Log($"Signed {signed.Length} transactions"),
+ error => Debug.Log(error)
+);
+```
+
+### Check state
+
+```csharp
+BlockmakerAuth.Instance.HasWallet // true if a wallet is connected
+BlockmakerAuth.Instance.Address // Algorand address
+BlockmakerAuth.Instance.CanSign // true if the wallet can sign right now
+BlockmakerAuth.Instance.IsLoggedIn // true for Email or SelfCustody tier
+BlockmakerAuth.Instance.Tier // Guest, Email, or SelfCustody
+```
+
+### Listen for changes
+
+```csharp
+void OnEnable()
+{
+ BlockmakerAuth.OnIdentityChanged += HandleIdentityChanged;
+}
+
+void OnDisable()
+{
+ BlockmakerAuth.OnIdentityChanged -= HandleIdentityChanged;
+}
+
+void HandleIdentityChanged(IBlockmakerIdentity identity)
+{
+ Debug.Log($"Identity changed: {identity.ProviderName} — {identity.Address}");
+}
+```
+
+### Session restore & sign-in status
+
+Self-custody sign-in is **two approvals**: connect, then a signature that logs the
+player into the backend. The SDK exposes everything a custom UI needs to render that
+honestly:
+
+```csharp
+// Boot-time restore: don't guess — wait for the restore to settle.
+if (BlockmakerAuth.SessionRestoreSettled) { /* auth state is known */ }
+BlockmakerAuth.OnSessionRestoreSettled += () =>
+ Debug.Log($"Restore settled — tier: {BlockmakerAuth.Instance.Tier}");
+
+// Progress messages while the player must act in their wallet app.
+BlockmakerAuth.OnAuthStatus += msg => statusLabel.text = msg;
+
+// The step-2 (login signature) window: offer RESEND / CANCEL instead of a dead end.
+if (BlockmakerAuth.CanRetryWalletLogin)
+{
+ BlockmakerAuth.Instance.RetryWalletLogin(); // push a fresh sign-in request
+ // or: BlockmakerAuth.Instance.CancelWalletLogin();
+}
+
+// Skip best-effort backend calls that would 401 for guests.
+if (BlockmakerClient.Instance.HasBackendSession) { /* safe to call player APIs */ }
+```
+
+The built-in auth UI already implements all of this (a "STEP 2 OF 2" panel with
+RESEND / CANCEL).
+
+### WebGL: self-hosting the wallet JS (optional)
+
+On WebGL the SDK loads its wallet libraries from jsDelivr at runtime — zero setup.
+For immunity to ad-blockers and CDN outages you can ship the bundles with your game:
+build IIFE bundles that assign `window.BmPeraVendor = { PeraWalletConnect, algosdk }`
+and/or `window.BmWCVendor = { SignClient, QRCode }`, and load them via `